Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/30512-service-messaging-locales-a.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@objectstack/service-messaging': patch
---

i18n(service-messaging): the notification objects and the digest heading ship in Traditional Chinese, Arabic, French and Brazilian Portuguese

Clause-②: no
Metadata-protocol: none

The messaging objects (`sys_inbox_message`, `sys_notification_receipt`, `sys_notification_delivery`, `sys_notification_preference`, `sys_notification_template`, `sys_http_delivery`, `sys_user_device`) and the digest heading "You have {{count}} notifications" now carry `zh-Hant`, `ar`, `fr` and `pt-BR` beside the existing `en`, `zh-CN`, `ja-JP` and `es-ES`, so a recipient in one of those languages reads the notification screens and the digest heading in their own language instead of English. `t()` selects no plural form, so the Arabic heading is worded as a tally (`عدد الإشعارات لديك: {{count}}`), correct for every count. A recipient whose language is `fr-FR` now reads the `fr` pack. Each new locale is registered under its platform-table tag with its source-hash companion and its messages, and the package's extract command lists the tags, so the existing `check:i18n` bundle check covers them. First half of package 5 of #30512; `ru`, `de` and `ko` follow in the second half.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* `--merge` preserves every hand-translated string.
*
* os i18n extract packages/services/service-messaging/scripts/i18n-extract.config.ts \
* --locales=zh-CN,ja-JP,es-ES --fill=default --objects-only --no-metadata-forms \
* --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar,fr,pt-BR --fill=default --objects-only --no-metadata-forms \
* --source-hashes \
* --out=packages/services/service-messaging/src/translations
*
Expand Down Expand Up @@ -49,6 +49,10 @@ import { enObjects } from '../src/translations/en.objects.generated.js';
import { zhCNObjects } from '../src/translations/zh-CN.objects.generated.js';
import { jaJPObjects } from '../src/translations/ja-JP.objects.generated.js';
import { esESObjects } from '../src/translations/es-ES.objects.generated.js';
import { zhHantObjects } from '../src/translations/zh-Hant.objects.generated.js';
import { arObjects } from '../src/translations/ar.objects.generated.js';
import { frObjects } from '../src/translations/fr.objects.generated.js';
import { ptBRObjects } from '../src/translations/pt-BR.objects.generated.js';

/**
* The annotation is load-bearing, not decoration (#10868). `defineStack`
Expand Down Expand Up @@ -79,6 +83,10 @@ const config: ObjectStackDefinition = defineStack({
{ 'zh-CN': { objects: zhCNObjects } },
{ 'ja-JP': { objects: jaJPObjects } },
{ 'es-ES': { objects: esESObjects } },
{ 'zh-Hant': { objects: zhHantObjects } },
{ 'ar': { objects: arObjects } },
{ 'fr': { objects: frObjects } },
{ 'pt-BR': { objects: ptBRObjects } },
],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ const USERS = [
{ id: 'u_en', name: 'Ada', email: 'ada@example.com', phone_number: '+15550000001', locale: 'en' },
{ id: 'u_ja', name: 'Sato', email: 'sato@example.com', phone_number: '+81300000001', locale: 'ja-JP' },
{ id: 'u_none', name: 'Nobody', email: 'nobody@example.com', phone_number: '+15550000002' },
// A language no pack in this deployment holds.
// A language no pack in this deployment holds (Italian is not a platform-table language).
{ id: 'u_it', name: 'Rossi', email: 'rossi@example.com', phone_number: '+390600000001', locale: 'it-IT' },
// A regional tag of a platform-table language: the messaging bundle ships `fr` (#30512).
{ id: 'u_fr', name: 'Dupont', email: 'dupont@example.com', phone_number: '+33100000001', locale: 'fr-FR' },
];

Expand Down Expand Up @@ -487,22 +489,34 @@ describe("#25015 — a digest's heading is rendered in its recipient's language"
i18n: catalog({ messagingBundle: true, defaultLocale: 'zh-CN' }),
});

const [digest] = await digestOf(ql, c, 'u_fr', 2);
const [digest] = await digestOf(ql, c, 'u_it', 2);

expect(digest.title).toBe('您有 2 条通知');
});

it('when neither the recipient\'s language nor the deployment default has a pack, the English literal — said once', async () => {
it('a regional recipient language reads its platform-table pack (`fr-FR` reads `fr`, #30512)', async () => {
const ql = await boot();
const c = compose(ql, {
outbox: 'memory', preferenceResolver: digestAll, deploymentDefault: 'fr-FR',
i18n: catalog({ messagingBundle: true, defaultLocale: 'fr-FR' }),
outbox: 'memory', preferenceResolver: digestAll, deploymentDefault: 'zh-CN',
i18n: catalog({ messagingBundle: true, defaultLocale: 'zh-CN' }),
});

const [digest] = await digestOf(ql, c, 'u_fr', 2);

expect(digest.title).toBe('Vous avez 2 notifications');
});

it('when neither the recipient\'s language nor the deployment default has a pack, the English literal — said once', async () => {
const ql = await boot();
const c = compose(ql, {
outbox: 'memory', preferenceResolver: digestAll, deploymentDefault: 'it-IT',
i18n: catalog({ messagingBundle: true, defaultLocale: 'it-IT' }),
});

const [digest] = await digestOf(ql, c, 'u_it', 2);

expect(digest.title).toBe('You have 2 notifications');
expect(c.warnings.filter((w) => w.includes(`'${DIGEST_HEADING_KEY}'`) && w.includes('fr-FR'))).toHaveLength(1);
expect(c.warnings.filter((w) => w.includes(`'${DIGEST_HEADING_KEY}'`) && w.includes('it-IT'))).toHaveLength(1);
});

it("a one-item digest keeps its item's own title, rendered in the recipient's language", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import { enObjects } from '../translations/en.objects.generated.js';
import { zhCNObjects } from '../translations/zh-CN.objects.generated.js';
import { jaJPObjects } from '../translations/ja-JP.objects.generated.js';
import { esESObjects } from '../translations/es-ES.objects.generated.js';
import { zhHantObjects } from '../translations/zh-Hant.objects.generated.js';
import { arObjects } from '../translations/ar.objects.generated.js';
import { frObjects } from '../translations/fr.objects.generated.js';
import { ptBRObjects } from '../translations/pt-BR.objects.generated.js';

/** The design-record vocabulary an admin-facing string must not carry. */
const DESIGN_RECORD_VOCABULARY: ReadonlyArray<readonly [string, RegExp]> = [
Expand Down Expand Up @@ -90,7 +94,16 @@ function isObjectSchema(v: unknown): v is ObjectLike {
&& !!(v as ObjectLike).fields && typeof (v as ObjectLike).fields === 'object';
}

const OBJECT_BUNDLES = { en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects } as const;
const OBJECT_BUNDLES = {
en: enObjects,
'zh-CN': zhCNObjects,
'ja-JP': jaJPObjects,
'es-ES': esESObjects,
'zh-Hant': zhHantObjects,
'ar': arObjects,
'fr': frObjects,
'pt-BR': ptBRObjects,
} as const;

describe('messaging objects: admin-facing copy carries no design-record vocabulary', () => {
it('the matcher recognises each kind of vocabulary it exists to keep out', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import { enObjects } from '../translations/en.objects.generated.js';
import { zhCNObjects } from '../translations/zh-CN.objects.generated.js';
import { jaJPObjects } from '../translations/ja-JP.objects.generated.js';
import { esESObjects } from '../translations/es-ES.objects.generated.js';
import { zhHantObjects } from '../translations/zh-Hant.objects.generated.js';
import { arObjects } from '../translations/ar.objects.generated.js';
import { frObjects } from '../translations/fr.objects.generated.js';
import { ptBRObjects } from '../translations/pt-BR.objects.generated.js';

interface ViewLike {
columns?: unknown;
Expand All @@ -45,7 +49,16 @@ interface ObjectLike {

const OBJECTS = [NotificationPreference, NotificationTemplate] as unknown as ObjectLike[];

const BUNDLES = { en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects } as const;
const BUNDLES = {
en: enObjects,
'zh-CN': zhCNObjects,
'ja-JP': jaJPObjects,
'es-ES': esESObjects,
'zh-Hant': zhHantObjects,
'ar': arObjects,
'fr': frObjects,
'pt-BR': ptBRObjects,
} as const;

function views(def: ObjectLike): Array<[string, ViewLike]> {
return Object.entries(def.listViews ?? {});
Expand Down
Loading
Loading