@stacksjs/defaults 0.75.144 → 0.75.146

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,7 +35,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
35
35
  | `@stacksjs/composables` | [`stacks-composables`](../stacks-composables/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/composables/src/index.ts) | 18 |
36
36
  | `@stacksjs/config` | [`stacks-config`](../stacks-config/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/config/src/index.ts) | 15 |
37
37
  | `@stacksjs/cron` | [`stacks-cron`](../stacks-cron/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cron/src/index.ts) | 4 |
38
- | `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) | 113 |
38
+ | `@stacksjs/database` | [`stacks-database`](../stacks-database/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index.ts) | 115 |
39
39
  | `@stacksjs/datetime` | [`stacks-datetime`](../stacks-datetime/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/datetime/src/index.ts) | 12 |
40
40
  | `@stacksjs/defaults` | [`stacks-build`](../stacks-build/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/defaults/package.json) | 1 |
41
41
  | `@stacksjs/deploy` | [`stacks-deploy`](../stacks-deploy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/deploy/package.json) | 1 |
@@ -63,7 +63,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
63
63
  | `@stacksjs/mobile` | [`stacks-mobile`](../stacks-mobile/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/mobile/src/index.ts) | 12 |
64
64
  | `@stacksjs/model-meta` | [`stacks-models`](../stacks-models/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/model-meta/src/index.ts) | 1 |
65
65
  | `@stacksjs/newsletter` | [`stacks-newsletter`](../stacks-newsletter/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/newsletter/src/index.ts) | 7 |
66
- | `@stacksjs/notifications` | [`stacks-notifications`](../stacks-notifications/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts) | 13 |
66
+ | `@stacksjs/notifications` | [`stacks-notifications`](../stacks-notifications/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts) | 14 |
67
67
  | `@stacksjs/objects` | [`stacks-objects`](../stacks-objects/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/objects/src/index.ts) | 4 |
68
68
  | `@stacksjs/orm` | [`stacks-orm`](../stacks-orm/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/orm/src/index.ts) | 60 |
69
69
  | `@stacksjs/pagination` | [`stacks-pagination`](../stacks-pagination/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/pagination/src/index.ts) | 0 |
@@ -351,7 +351,7 @@ Declared runtime exports: `BUN_BROKEN_MYSQL_POOL_MESSAGE`, `BUN_BROKEN_POSTGRES_
351
351
 
352
352
  Local source modules: [src/affected-rows.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/affected-rows.ts), [src/auth-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/auth-tables.ts), [src/broken-pool.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/broken-pool.ts), [src/custom/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/custom/index.ts), [src/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/database.ts), [src/datetime-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/datetime-columns.ts), [src/ddl-constraints.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ddl-constraints.ts), [src/defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/defaults.ts), [src/dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/dialect.ts), [src/driver-config.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/driver-config.ts), [src/drivers/dynamodb.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/dynamodb.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/drivers/index.ts), [src/ensure-database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ensure-database.ts), [src/erd.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/erd.ts), [src/fk-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/fk-audit.ts), [src/framework-renames.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/framework-renames.ts), [src/hand-written-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/hand-written-migrations.ts), [src/ignored-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/ignored-migrations.ts), [src/index-shape.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/index-shape.ts), [src/managed-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/managed-columns.ts), [src/migration-dialect.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-dialect.ts), [src/migration-ledger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-ledger.ts), [src/migration-path.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migration-path.ts), [src/migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/migrations.ts), [src/model-sources.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/model-sources.ts), [src/notification-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/notification-tables.ts), [src/package-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-migrations.ts), [src/package-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/package-models.ts), [src/query-logger.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/query-logger.ts), [src/rbac-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/rbac-tables.ts), [src/relation-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/relation-columns.ts), [src/replicas.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/replicas.ts), [src/row-lock.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/row-lock.ts), [src/safe-migrations.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/safe-migrations.ts), [src/schema-align.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-align.ts), [src/schema-drift.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/schema-drift.ts), [src/seeder.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/seeder.ts), [src/shadowed-models.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/shadowed-models.ts), [src/sql-helpers.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/sql-helpers.ts), [src/trait-tables.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/trait-tables.ts), [src/transaction-context.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/transaction-context.ts), [src/types.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/types.ts), [src/unique-audit.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/unique-audit.ts), [src/utc-defaults.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utc-defaults.ts), [src/utils.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/utils.ts), [src/uuid-columns.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/uuid-columns.ts), [src/vschema.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/src/vschema.ts).
353
353
 
354
- Evidence directory: [113 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
354
+ Evidence directory: [115 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/database/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
355
355
 
356
356
  ### @stacksjs/datetime
357
357
 
@@ -607,11 +607,11 @@ Evidence directory: [7 retained tests](https://github.com/stacksjs/stacks/blob/m
607
607
 
608
608
  Guidance: [`stacks-notifications`](../stacks-notifications/SKILL.md). Source: [storage/framework/core/notifications/src/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/index.ts).
609
609
 
610
- Declared runtime exports: `BroadcastNotificationDriver`, `DEFAULT_SMS_OPT_OUT`, `DEFAULT_UNSUBSCRIBE_NOTICE`, `DatabaseNotificationDriver`, `SMS_SENDER_MAX_LENGTH`, `accountNotificationTemplates`, `bulkSetPreferences`, `commerceNotificationTemplates`, `createNotificationTemplates`, `defineNotificationTemplates`, `emailParagraphs`, `ensureSuccessfulEmailResult`, `ensureSuccessfulNotificationResults`, `filterChannelsByPreferences`, `getNotificationPreferences`, `interpolateNotificationText`, `listUnsubscribeHeaders`, `makeDeliveryRecord`, `notification`, `notificationChannel`, `notificationEmailHtml`, `notificationEmailMessage`, `notificationEmailText`, `notificationSmsBody`, `notificationTemplateVariables`, `notificationUnsubscribeLinks`, `notificationUnsubscribeToken`, `notify`, `parseNotificationUnsubscribeToken`, `recordNotificationDelivery`, `renderNotificationTemplate`, `resolveDeliveryRecipient`, `resolveNotificationLocale`, `setNotificationPreference`, `smsSenderPrefix`, `subscriptionNotificationTemplates`, `unsubscribeTokenFromRequest`, `unsubscribeWithToken`, `useBroadcast`, `useChat`, `useDatabase`, `useEmail`, `useNotification`, `usePush`, `useSMS`, `wherePreferenceCategory`.
610
+ Declared runtime exports: `BroadcastNotificationDriver`, `DEFAULT_SMS_OPT_OUT`, `DEFAULT_UNSUBSCRIBE_NOTICE`, `DatabaseNotificationDriver`, `NOTIFICATION_FRAME_STRINGS`, `SMS_SENDER_MAX_LENGTH`, `accountNotificationTemplates`, `bulkSetPreferences`, `commerceNotificationTemplates`, `createNotificationTemplates`, `defineNotificationTemplates`, `emailParagraphs`, `ensureSuccessfulEmailResult`, `ensureSuccessfulNotificationResults`, `filterChannelsByPreferences`, `getNotificationPreferences`, `interpolateNotificationText`, `listUnsubscribeHeaders`, `makeDeliveryRecord`, `notification`, `notificationChannel`, `notificationEmailHtml`, `notificationEmailMessage`, `notificationEmailText`, `notificationFrameStrings`, `notificationLanguageTag`, `notificationSmsBody`, `notificationTemplateVariables`, `notificationUnsubscribeLinks`, `notificationUnsubscribeToken`, `notify`, `parseNotificationUnsubscribeToken`, `recordNotificationDelivery`, `renderNotificationTemplate`, `resolveDeliveryRecipient`, `resolveNotificationLocale`, `setNotificationPreference`, `smsSenderPrefix`, `subscriptionNotificationTemplates`, `unsubscribeTokenFromRequest`, `unsubscribeWithToken`, `useBroadcast`, `useChat`, `useDatabase`, `useEmail`, `useNotification`, `usePush`, `useSMS`, `wherePreferenceCategory`.
611
611
 
612
- Local source modules: [src/delivery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/delivery.ts), [src/drivers/broadcast.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/broadcast.ts), [src/drivers/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/database.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/index.ts), [src/email-body.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/email-body.ts), [src/preferences.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/preferences.ts), [src/templates.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/templates.ts), [src/unsubscribe.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/unsubscribe.ts).
612
+ Local source modules: [src/delivery.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/delivery.ts), [src/drivers/broadcast.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/broadcast.ts), [src/drivers/database.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/database.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/drivers/index.ts), [src/email-body.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/email-body.ts), [src/frame-strings.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/frame-strings.ts), [src/preferences.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/preferences.ts), [src/templates.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/templates.ts), [src/unsubscribe.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/src/unsubscribe.ts).
613
613
 
614
- Evidence directory: [13 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
614
+ Evidence directory: [14 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/notifications/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
615
615
 
616
616
  ### @stacksjs/objects
617
617
 
@@ -807,7 +807,7 @@ Guidance: [`stacks-sms`](../stacks-sms/SKILL.md). Source: [storage/framework/cor
807
807
 
808
808
  Declared runtime exports: `SMS`, `TwilioDriver`, `VonageDriver`, `createTwilioDriver`, `createVonageDriver`.
809
809
 
810
- Local source modules: [src/compliance.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/compliance.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/drivers/index.ts), [src/drivers/twilio.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/drivers/twilio.ts), [src/drivers/vonage.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/drivers/vonage.ts), [src/inbound.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/inbound.ts), [src/opt-out.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/opt-out.ts), [src/sms.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/sms.ts).
810
+ Local source modules: [src/compliance.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/compliance.ts), [src/drivers/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/drivers/index.ts), [src/drivers/twilio.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/drivers/twilio.ts), [src/drivers/vonage.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/drivers/vonage.ts), [src/inbound.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/inbound.ts), [src/keywords.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/keywords.ts), [src/opt-out.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/opt-out.ts), [src/sms.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/src/sms.ts).
811
811
 
812
812
  Evidence directory: [5 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/sms/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
813
813
 
@@ -41,8 +41,9 @@ optional `error`. A resolved call does not imply every channel succeeded.
41
41
  | push | pushTokens, a string or string array | `@stacksjs/push`, default Expo |
42
42
  | broadcast | broadcastChannel or userId | `@stacksjs/realtime` |
43
43
 
44
- Payload: `{ body, subject?, data?, action? }`. Email treats body as text and
45
- renders it through the framework layout. Use the exported `NotificationAction`
44
+ Payload: `{ body, subject?, data?, action?, locale?, strings? }`. Email treats
45
+ body as text and renders it through the framework layout (see "The frame's
46
+ language" for `locale`). Use the exported `NotificationAction`
46
47
  type for an action link. The broadcast fallback is `private-user.{userId}` or
47
48
  public `notifications` when no user/channel is supplied. Its delivery is
48
49
  best-effort; a missing realtime server is not a durable notification queue.
@@ -119,6 +120,37 @@ body already starts with it); pass it whenever several businesses share one
119
120
  sending number, so the person can tell who wrote. STOP only becomes an opt-out the app sees when inbound
120
121
  SMS is wired up; see `stacks-sms`.
121
122
 
123
+ ## The frame's language
124
+
125
+ The subject, body and button label are the caller's. The words around them
126
+ are the frame's: the transactional footer ("This is an automated message from
127
+ {app}. Please do not reply to this email."), the copyright line, the marketing
128
+ notice, the **Unsubscribe** / **Email preferences** labels (also in the text
129
+ part), the button label when the action has none ("Open") and a marketing
130
+ text's opt-out line. `payload.locale` puts all of them in the reader's
131
+ language and sets the email's `<html lang>`:
132
+
133
+ ```ts
134
+ const language = user.locale // 'de', 'de-AT', 'en', ...
135
+ await notify({ userId, email }, { ...templates.render('order_receipt', variables, language), locale: language }, ['email'])
136
+ ```
137
+
138
+ - Built in: `en` and `de` (`NOTIFICATION_FRAME_STRINGS`). Fallback like the
139
+ templates: `de-AT` → `de` → `en`. No locale renders exactly the English the
140
+ frame always had (a fixture test holds it byte for byte).
141
+ - Another language: pass the whole set as `payload.strings`
142
+ (`NotificationFrameStrings`: `automatedNotice`, `copyright`,
143
+ `unsubscribeNotice`, `unsubscribe`, `preferences`, `action`, `smsOptOut`). A
144
+ partial set overrides only what it names. `{app}` and `{year}` are filled in.
145
+ - Precedence: `unsubscribe.notice` and `payload.smsOptOut` still win over the
146
+ frame's notice and opt-out line.
147
+ - The builders take the same fields: `notificationEmailMessage`,
148
+ `notificationEmailHtml`, `notificationEmailText`, `notificationSmsBody`;
149
+ `notificationFrameStrings(locale, overrides)` resolves a set.
150
+ - An app's own `resources/emails/notification.stx` must read the new props
151
+ (`lang`, `automatedNotice`, `copyrightNotice`, `unsubscribeLabel`,
152
+ `preferencesLabel`) to be localized; `<EmailLayout lang>` sets `<html lang>`.
153
+
122
154
  ## Templates: one catalog of localized messages
123
155
 
124
156
  `@stacksjs/notifications/templates` (pure, browser-safe; also exported from the
@@ -200,7 +232,8 @@ delivery log, not extra columns on the inbox.
200
232
  signatures. `preferences.ts`, `delivery.ts` and `drivers/database.ts`
201
233
  own their persistence; the query builder is bun-query-builder.
202
234
  `notifications.test.ts`, `delivery-tracking.test.ts`,
203
- `broadcast.test.ts` and `email-body.test.ts` cover retained behavior.
235
+ `broadcast.test.ts`, `email-body.test.ts` and `frame-locale.test.ts` cover
236
+ retained behavior; `frame-strings.ts` holds the frame's languages.
204
237
  For provider status, consult `config/src/capabilities.ts` and each channel
205
238
  skill; a constructed send request is not a live delivery assertion.
206
239
 
@@ -393,7 +393,10 @@ interface VerificationResult {
393
393
  STOP/START/HELP, persists opt-out or opt-in, and returns the reply an inbound
394
394
  webhook should send. `optOutPhone`, `optInPhone`,
395
395
  `isPhoneOptedOut` and `normalizePhone` use the persistent
396
- `sms_opt_outs` table. Apply its model migration first. Sending through the
396
+ `sms_opt_outs` table. Apply its model migration first. `optOutPhone` is one
397
+ insert-or-ignore on the unique `phone` (`ON CONFLICT DO NOTHING`, `INSERT
398
+ IGNORE` on MySQL), so repeated or concurrent STOPs leave one row and keep the
399
+ first opt-out's time and reason. Sending through the
397
400
  generic notification SMS channel checks opt-outs; inspect the selected direct
398
401
  send path before assuming a low-level provider call applies that policy.
399
402
 
@@ -403,6 +406,15 @@ normalizes its payload. `classifySmsIntent/smsComplianceReply` are pure helpers;
403
406
  they do not themselves persist consent. Use the persistent handleInboundSms
404
407
  workflow when consent state must affect future sends.
405
408
 
409
+ Keywords are recognised in every language `SMS_KEYWORDS` has, on every
410
+ message (`matchSmsKeyword(body)` returns the kind and the language): English
411
+ `STOP`/`STOPALL`/`UNSUBSCRIBE`/`CANCEL`/`END`/`QUIT`, `START`/`YES`/`UNSTOP`,
412
+ `HELP`/`INFO`, and German `STOPP`/`ABMELDEN`/`ABBESTELLEN`, `STARTEN`,
413
+ `HILFE`. Only the first word counts, in any case. `classifyInboundSms`,
414
+ `handleInboundSms` and `classifySmsIntent` all use these lists, and
415
+ `inboundSmsReply(kind, { locale })` answers in the keyword's language. Add a
416
+ language by adding its keywords and replies in `src/keywords.ts`.
417
+
406
418
  For a Twilio incoming-message webhook, `handleTwilioInbound(request, options)`
407
419
  does the whole job and returns the TwiML `Response`: it reads the form body,
408
420
  verifies `X-Twilio-Signature` with `TWILIO_AUTH_TOKEN` against `APP_URL` plus
@@ -411,8 +423,12 @@ URL is not what Twilio signed), records STOP/START in `sms_opt_outs`, then
411
423
  calls `onConsentChange({ phone, kind })` so the app can switch its own
412
424
  preferences, and `onMessage(message)` for every verified text. Without a token
413
425
  it answers 503, and a bad signature 403, changing nothing. It does not reply to
414
- keywords unless `reply: true`, because Twilio's own opt-out handling already
415
- answers STOP/START/HELP and a second reply arrives twice. Twilio Advanced
426
+ keywords unless asked, because Twilio's own opt-out handling already answers
427
+ the English STOP/START/HELP and a second reply arrives twice. `reply:
428
+ 'unhandled'` confirms only the keywords Twilio does not know (STOPP and the
429
+ other German ones), in their language, so every keyword gets exactly one
430
+ confirmation; `reply: true` answers all of them. `onConsentChange` and
431
+ `onMessage` receive the keyword's `locale`. Twilio Advanced
416
432
  Opt-Out's `OptOutType` field wins over the keyword list (`inboundSmsKind`), so
417
433
  custom keywords configured there count. `inboundSmsReply(kind)` is the reply
418
434
  copy `handleInboundSms` uses. Route it with `skipCsrf()` and point the number's
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.144",
5
+ "version": "0.75.146",
6
6
  "description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.144",
5
+ "version": "0.75.146",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@iconify-json/f7": "^1.2.2",
57
57
  "@iconify-json/hugeicons": "^1.2.35",
58
- "@stacksjs/mobile": "0.75.144",
58
+ "@stacksjs/mobile": "0.75.146",
59
59
  "@stacksjs/sanitizer": "^0.2.422",
60
60
  "@xterm/addon-fit": "^0.11.0",
61
61
  "@xterm/xterm": "^6.0.0",
@@ -37,6 +37,7 @@ interface EmailLayoutProps {
37
37
  brandUrl?: string
38
38
  accent?: string
39
39
  footer?: string
40
+ lang?: string
40
41
  }
41
42
 
42
43
  const props = defineProps<EmailLayoutProps>()
@@ -49,6 +50,9 @@ const brand = props.brand || ''
49
50
  const brandUrl = props.brandUrl || ''
50
51
  const accent = props.accent || '#3451b2'
51
52
  const footer = props.footer || ''
53
+ // The language the email is written in (`<html lang>`), for screen readers
54
+ // and mail clients' translate offers.
55
+ const lang = props.lang || 'en'
52
56
  const fontStack = '-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,\'Helvetica Neue\',Arial,sans-serif'
53
57
 
54
58
  const responsiveCss = [
@@ -73,7 +77,7 @@ const responsiveCss = [
73
77
  </script>
74
78
 
75
79
  <!DOCTYPE html>
76
- <html lang="en">
80
+ <html lang="{{ lang }}">
77
81
  <head>
78
82
  <meta charset="UTF-8">
79
83
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -6,6 +6,8 @@
6
6
  // mailers (stacksjs/stacks#1901); an app's own resources/emails/notification
7
7
  // wins over this one. A notification with `unsubscribe` (marketing) says why
8
8
  // it came and links to Unsubscribe in the footer instead of "do not reply".
9
+ // Every fixed word of the frame comes in as a prop, so a `locale` passed to
10
+ // notify() makes the whole email that language.
9
11
  const appName = props.appName || 'Stacks'
10
12
  const primaryColor = props.primaryColor || '#3451b2'
11
13
  const subject = props.subject || appName
@@ -17,9 +19,19 @@ const unsubscribeUrl = props.unsubscribeUrl || ''
17
19
  const unsubscribeNotice = props.unsubscribeNotice || 'You are receiving this because these emails are turned on in your settings.'
18
20
  const preferencesUrl = props.preferencesUrl || ''
19
21
  const year = props.year || new Date().getFullYear()
22
+ // The frame's fixed words, in the reader's language when notify() was given
23
+ // a `locale` (see NotificationFrameStrings in @stacksjs/notifications). The
24
+ // English here is only for a render without them. `{app}` and `{year}` are
25
+ // filled in below.
26
+ const lang = props.lang || 'en'
27
+ const fill = text => String(text).split('{app}').join(appName).split('{year}').join(String(year))
28
+ const automatedNotice = fill(props.automatedNotice || 'This is an automated message from {app}. Please do not reply to this email.')
29
+ const copyrightNotice = fill(props.copyrightNotice || '{year} {app}. All rights reserved.')
30
+ const unsubscribeLabel = props.unsubscribeLabel || 'Unsubscribe'
31
+ const preferencesLabel = props.preferencesLabel || 'Email preferences'
20
32
  </script>
21
33
 
22
- <EmailLayout title="{{ subject }}" preheader="{{ preheader }}">
34
+ <EmailLayout title="{{ subject }}" preheader="{{ preheader }}" lang="{{ lang }}">
23
35
  <template #header>
24
36
  <tr>
25
37
  <td style="background-color:{{ primaryColor }};padding:30px 40px;text-align:center;">
@@ -42,11 +54,11 @@ const year = props.year || new Date().getFullYear()
42
54
  <td style="padding:20px 40px;background-color:#f9fafb;border-top:1px solid #e5e7eb;">
43
55
  @if(unsubscribeUrl)
44
56
  <p style="margin:0;color:#6b7280;font-size:12px;text-align:center;">{{ unsubscribeNotice }}</p>
45
- <p style="margin:8px 0 0 0;color:#6b7280;font-size:12px;text-align:center;"><a href="{{ unsubscribeUrl }}" style="color:#6b7280;text-decoration:underline;">Unsubscribe</a>@if(preferencesUrl) &middot; <a href="{{ preferencesUrl }}" style="color:#6b7280;text-decoration:underline;">Email preferences</a>@endif</p>
57
+ <p style="margin:8px 0 0 0;color:#6b7280;font-size:12px;text-align:center;"><a href="{{ unsubscribeUrl }}" style="color:#6b7280;text-decoration:underline;">{{ unsubscribeLabel }}</a>@if(preferencesUrl) &middot; <a href="{{ preferencesUrl }}" style="color:#6b7280;text-decoration:underline;">{{ preferencesLabel }}</a>@endif</p>
46
58
  @else
47
- <p style="margin:0;color:#6b7280;font-size:12px;text-align:center;">This is an automated message from {{ appName }}. Please do not reply to this email.</p>
59
+ <p style="margin:0;color:#6b7280;font-size:12px;text-align:center;">{{ automatedNotice }}</p>
48
60
  @endif
49
- <p style="margin:10px 0 0 0;color:#9ca3af;font-size:11px;text-align:center;">&copy; {{ year }} {{ appName }}. All rights reserved.</p>
61
+ <p style="margin:10px 0 0 0;color:#9ca3af;font-size:11px;text-align:center;">&copy; {{ copyrightNotice }}</p>
50
62
  </td>
51
63
  </tr>
52
64
  </template>