Skip to content
Open
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-platform-objects-locales-zh-hant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@objectstack/platform-objects': patch
---

i18n(platform-objects): the platform objects, Setup, Studio and Account ship in Traditional Chinese

Clause-②: no
Metadata-protocol: none

`@objectstack/platform-objects` now carries `zh-Hant` beside `en`, `zh-CN`, `ja-JP` and `es-ES`: every `sys_*` object (labels, fields, help, options, views, actions and their dialogs), the metadata-type configuration forms Studio opens, the Setup, Studio and Account navigation, the System Overview dashboard and its datasets, the platform pages' titles, and the inline copy on the user and organization record pages. An administrator or developer reading the platform in Traditional Chinese no longer sees it in English. The new bundles pass through the same source-hash fallback as the existing ones, and every hand-authored leaf records the digest of the source it was translated from. The extract config documents `zh-Hant` in its `--locales`, so `check:i18n` now gates the new bundles. Package 11 of #30512, first of seven locales; `ar`, `fr`, `pt-BR`, `ru`, `de` and `ko` follow.
9 changes: 5 additions & 4 deletions packages/cli/test/platform-page-i18n-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -738,11 +738,12 @@ const RECORD_PAGES: Array<Record<string, any>> = Object.values(
const SHIPPED_LOCALES = Object.keys(SetupAppTranslations as Record<string, unknown>);

/**
* A key is a locale code (`en`, `zh-CN`). Deliberately a shape test rather than
* a membership test against `SHIPPED_LOCALES`: a map carrying a locale the
* bundle does not ship is still an inline map, and must still be judged.
* A key is a locale code (`en`, `zh-CN`, and the platform locale table's
* script-subtag tag `zh-Hant`). Deliberately a shape test rather than a
* membership test against `SHIPPED_LOCALES`: a map carrying a locale the bundle
* does not ship is still an inline map, and must still be judged.
*/
const LOCALE_KEY = /^[a-z]{2}(-[A-Z]{2})?$/;
const LOCALE_KEY = /^[a-z]{2}(-[A-Z][a-z]{3})?(-[A-Z]{2})?$/;

/** An inline `I18nLabel` map: every key a locale code, every value a string. */
const isInlineLocaleMap = (value: unknown): value is Record<string, string> => {
Expand Down
6 changes: 5 additions & 1 deletion packages/platform-objects/scripts/i18n-extract.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* pointed at this file via `bundle-require`:
*
* os i18n extract packages/platform-objects/scripts/i18n-extract.config.ts \
* --locales=zh-CN,ja-JP,es-ES \
* --locales=zh-CN,ja-JP,es-ES,zh-Hant \
* --fill=default \
* --source-hashes \
* --out=packages/platform-objects/src/apps/translations
Expand Down Expand Up @@ -221,10 +221,12 @@ import { enObjects } from '../src/apps/translations/en.objects.generated.js';
import { zhCNObjects } from '../src/apps/translations/zh-CN.objects.generated.js';
import { jaJPObjects } from '../src/apps/translations/ja-JP.objects.generated.js';
import { esESObjects } from '../src/apps/translations/es-ES.objects.generated.js';
import { zhHantObjects } from '../src/apps/translations/zh-Hant.objects.generated.js';
import { enMetadataForms } from '../src/apps/translations/en.metadata-forms.generated.js';
import { zhCNMetadataForms } from '../src/apps/translations/zh-CN.metadata-forms.generated.js';
import { jaJPMetadataForms } from '../src/apps/translations/ja-JP.metadata-forms.generated.js';
import { esESMetadataForms } from '../src/apps/translations/es-ES.metadata-forms.generated.js';
import { zhHantMetadataForms } from '../src/apps/translations/zh-Hant.metadata-forms.generated.js';

// ── Existing hand-authored app/dashboard/page translations ────────────────
// The per-locale assemblers, NOT the `SetupAppTranslations` wrapper. Each one
Expand All @@ -247,6 +249,7 @@ import { en } from '../src/apps/translations/en.js';
import { zhCN } from '../src/apps/translations/zh-CN.js';
import { jaJP } from '../src/apps/translations/ja-JP.js';
import { esES } from '../src/apps/translations/es-ES.js';
import { zhHant } from '../src/apps/translations/zh-Hant.js';

/**
* The annotation is load-bearing, not decoration (#10868). `defineStack`
Expand Down Expand Up @@ -347,6 +350,7 @@ const config: ObjectStackDefinition = defineStack({
{ 'zh-CN': { ...zhCN, objects: zhCNObjects, metadataForms: zhCNMetadataForms } },
{ 'ja-JP': { ...jaJP, objects: jaJPObjects, metadataForms: jaJPMetadataForms } },
{ 'es-ES': { ...esES, objects: esESObjects, metadataForms: esESMetadataForms } },
{ 'zh-Hant': { ...zhHant, objects: zhHantObjects, metadataForms: zhHantMetadataForms } },
],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('System Overview: the null-user bucket reads "System"', () => {
'zh-CN': '系统',
'ja-JP': 'システム',
'es-ES': 'Sistema',
'zh-Hant': '系統',
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const SysAuditLogDataset = defineDataset({
label: 'User',
field: 'user_id',
type: 'lookup',
nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema' },
nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema', 'zh-Hant': '系統' },
},
],
measures: [{ name: 'event_count', label: 'Events', aggregate: 'count' }],
Expand Down
10 changes: 8 additions & 2 deletions packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,17 @@ import { en } from './translations/en.js';
import { zhCN } from './translations/zh-CN.js';
import { jaJP } from './translations/ja-JP.js';
import { esES } from './translations/es-ES.js';
import { zhHant } from './translations/zh-Hant.js';
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 { enMetadataForms } from './translations/en.metadata-forms.generated.js';
import { zhCNMetadataForms } from './translations/zh-CN.metadata-forms.generated.js';
import { jaJPMetadataForms } from './translations/ja-JP.metadata-forms.generated.js';
import { esESMetadataForms } from './translations/es-ES.metadata-forms.generated.js';
import { zhHantMetadataForms } from './translations/zh-Hant.metadata-forms.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 @@ -127,10 +130,13 @@ function objectSchemas(...modules: Array<Record<string, unknown>>): Map<string,
return byName;
}

const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES } as const;
const OBJECT_BUNDLES = { en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects } as const;
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant } as const;
const OBJECT_BUNDLES = {
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects,
} as const;
const FORM_BUNDLES = {
en: enMetadataForms, 'zh-CN': zhCNMetadataForms, 'ja-JP': jaJPMetadataForms, 'es-ES': esESMetadataForms,
'zh-Hant': zhHantMetadataForms,
} as const;

describe('Setup copy carries no design-record vocabulary', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import { en } from './translations/en.js';
import { zhCN } from './translations/zh-CN.js';
import { jaJP } from './translations/ja-JP.js';
import { esES } from './translations/es-ES.js';
import { zhHant } from './translations/zh-Hant.js';

const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES } as const;
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant } as const;

type NavItem = { id?: string; label?: string };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ describe('(2) the User page lists each per-user store, keyed to its record (#238
'zh-CN': '偏好设置',
'ja-JP': '設定',
'es-ES': 'Preferencias',
'zh-Hant': '偏好設定',
});
expect((tabOf('sys_account')?.label as Rec)?.en).toBe('Linked Accounts');
expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['en', 'es-ES', 'ja-JP', 'zh-CN']);
expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['en', 'es-ES', 'ja-JP', 'zh-CN', 'zh-Hant']);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ import { en } from './en.js';
import { zhCN } from './zh-CN.js';
import { jaJP } from './ja-JP.js';
import { esES } from './es-ES.js';
import { zhHant } from './zh-Hant.js';

const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES } as const;
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant } as const;

/** Every statically declared nav item of an app, depth-first, with its label. */
function navItems(app: { navigation?: unknown[] }): Array<{ id: string; label?: string }> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import { enObjects } from './en.objects.generated.js';
import { zhCNObjects } from './zh-CN.objects.generated.js';
import { jaJPObjects } from './ja-JP.objects.generated.js';
import { esESObjects } from './es-ES.objects.generated.js';
import { zhHantObjects } from './zh-Hant.objects.generated.js';

const LOCALES = [
{ name: 'en', objs: enObjects as Record<string, any>, old: /\bDepartment\b/ },
{ name: 'zh-CN', objs: zhCNObjects as Record<string, any>, old: /部门/ },
{ name: 'ja-JP', objs: jaJPObjects as Record<string, any>, old: /部門/ },
{ name: 'es-ES', objs: esESObjects as Record<string, any>, old: /Departamento/ },
{ name: 'zh-Hant', objs: zhHantObjects as Record<string, any>, old: /部門/ },
];
const OBJECTS = ['sys_business_unit', 'sys_business_unit_member'];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import { enObjects } from './en.objects.generated.js';
import { zhCNObjects } from './zh-CN.objects.generated.js';
import { jaJPObjects } from './ja-JP.objects.generated.js';
import { esESObjects } from './es-ES.objects.generated.js';
import { zhHantObjects } from './zh-Hant.objects.generated.js';

const LOCALES = [
['zh-CN', zhCNObjects],
['ja-JP', jaJPObjects],
['es-ES', esESObjects],
['zh-Hant', zhHantObjects],
] as const;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ import { enMetadataForms } from './en.metadata-forms.generated.js';
import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js';
import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js';
import { esESMetadataForms } from './es-ES.metadata-forms.generated.js';
import { zhHantMetadataForms } from './zh-Hant.metadata-forms.generated.js';

const LOCALES = [
{ name: 'en', forms: enMetadataForms as Record<string, any> },
{ name: 'zh-CN', forms: zhCNMetadataForms as Record<string, any> },
{ name: 'ja-JP', forms: jaJPMetadataForms as Record<string, any> },
{ name: 'es-ES', forms: esESMetadataForms as Record<string, any> },
{ name: 'zh-Hant', forms: zhHantMetadataForms as Record<string, any> },
];

const HEADER_CHILDREN = ['header.showTitle', 'header.showDescription', 'header.actions'];
Expand Down
1 change: 1 addition & 0 deletions packages/platform-objects/src/apps/translations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { en } from './en.js';
export { zhCN } from './zh-CN.js';
export { jaJP } from './ja-JP.js';
export { esES } from './es-ES.js';
export { zhHant } from './zh-Hant.js';

/**
* The staleness mechanism (#8765 Option B, extended to the generated bundles by
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import { enMetadataForms } from './en.metadata-forms.generated.js';
import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js';
import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js';
import { esESMetadataForms } from './es-ES.metadata-forms.generated.js';
import { zhHantMetadataForms } from './zh-Hant.metadata-forms.generated.js';

const LOCALES = [
{ name: 'en', forms: enMetadataForms as Record<string, any> },
{ name: 'zh-CN', forms: zhCNMetadataForms as Record<string, any> },
{ name: 'ja-JP', forms: jaJPMetadataForms as Record<string, any> },
{ name: 'es-ES', forms: esESMetadataForms as Record<string, any> },
{ name: 'zh-Hant', forms: zhHantMetadataForms as Record<string, any> },
];

describe('ADR-0090 D2/D3 — metadata-form translations use the v2 vocabulary', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ import { enMetadataForms } from './en.metadata-forms.generated.js';
import { zhCNMetadataForms } from './zh-CN.metadata-forms.generated.js';
import { jaJPMetadataForms } from './ja-JP.metadata-forms.generated.js';
import { esESMetadataForms } from './es-ES.metadata-forms.generated.js';
import { zhHantMetadataForms } from './zh-Hant.metadata-forms.generated.js';

const LOCALES = [
{ name: 'en', forms: enMetadataForms as Record<string, any> },
{ name: 'zh-CN', forms: zhCNMetadataForms as Record<string, any> },
{ name: 'ja-JP', forms: jaJPMetadataForms as Record<string, any> },
{ name: 'es-ES', forms: esESMetadataForms as Record<string, any> },
{ name: 'zh-Hant', forms: zhHantMetadataForms as Record<string, any> },
];

/** Every `*.form.ts` the spec package exports, by its export name. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,9 @@ import { en } from './en.js';
import { zhCN } from './zh-CN.js';
import { jaJP } from './ja-JP.js';
import { esES } from './es-ES.js';
import { zhHant } from './zh-Hant.js';

const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES } as const;
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant } as const;

/** Removed Setup nav ids. Delete a line here only together with its nav item. */
const DEAD_SETUP_NAV_IDS = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,33 @@ import { en } from './en.js';
import { zhCN } from './zh-CN.js';
import { jaJP } from './ja-JP.js';
import { esES } from './es-ES.js';
import { zhHant } from './zh-Hant.js';
import { zhCNSourceHashes } from './zh-CN.source-hashes.js';
import { jaJPSourceHashes } from './ja-JP.source-hashes.js';
import { esESSourceHashes } from './es-ES.source-hashes.js';
import { zhHantSourceHashes } from './zh-Hant.source-hashes.js';
import { zhCNGeneratedSourceHashes } from './zh-CN.source-hashes.generated.js';
import { jaJPGeneratedSourceHashes } from './ja-JP.source-hashes.generated.js';
import { esESGeneratedSourceHashes } from './es-ES.source-hashes.generated.js';
import { zhHantGeneratedSourceHashes } from './zh-Hant.source-hashes.generated.js';
import { withSourceFallback } from './source-hash.js';

/**
* Setup App — Internationalization (i18n)
*
* Mirrors the CRM example's `per_locale` convention: each language lives
* in its own file (`en.ts`, `zh-CN.ts`, `ja-JP.ts`, `es-ES.ts`) and is
* in its own file (`en.ts`, `zh-CN.ts`, `ja-JP.ts`, `es-ES.ts`, `zh-Hant.ts`) and is
* assembled into a single `TranslationBundle` here.
*
* Loaded into the kernel's i18n service by `plugin-auth` during
* `kernel:ready` (auth is the natural registration point for the Setup
* App — see `auth-plugin.ts`).
*
* Supported locales: en, zh-CN, ja-JP, es-ES.
* Supported locales: en, zh-CN, ja-JP, es-ES, zh-Hant.
*
* ## This assembly is where staleness is resolved (#8765)
*
* The three translated bundles pass through {@link withSourceFallback}, which
* The translated bundles pass through {@link withSourceFallback}, which
* replaces any leaf whose RECORDED source hash disagrees with the current
* `en` source string by that source string. This is the serving seam for the
* package: `plugin.ts` hands exactly this object to the kernel's i18n service
Expand Down Expand Up @@ -69,4 +72,5 @@ export const SetupAppTranslations: TranslationBundle = {
'zh-CN': withSourceFallback(zhCN, en, zhCNSourceHashes, zhCNGeneratedSourceHashes),
'ja-JP': withSourceFallback(jaJP, en, jaJPSourceHashes, jaJPGeneratedSourceHashes),
'es-ES': withSourceFallback(esES, en, esESSourceHashes, esESGeneratedSourceHashes),
'zh-Hant': withSourceFallback(zhHant, en, zhHantSourceHashes, zhHantGeneratedSourceHashes),
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import { en } from './en.js';
import { zhCN } from './zh-CN.js';
import { jaJP } from './ja-JP.js';
import { esES } from './es-ES.js';
import { zhHant } from './zh-Hant.js';
import { zhCNSourceHashes } from './zh-CN.source-hashes.js';
import { jaJPSourceHashes } from './ja-JP.source-hashes.js';
import { esESSourceHashes } from './es-ES.source-hashes.js';
import { zhHantSourceHashes } from './zh-Hant.source-hashes.js';
import { SetupAppTranslations } from './setup.translation.js';
import {
collectFilledFromHashes,
Expand All @@ -39,6 +41,7 @@ import {
import { zhCNGeneratedSourceHashes } from './zh-CN.source-hashes.generated.js';
import { jaJPGeneratedSourceHashes } from './ja-JP.source-hashes.generated.js';
import { esESGeneratedSourceHashes } from './es-ES.source-hashes.generated.js';
import { zhHantGeneratedSourceHashes } from './zh-Hant.source-hashes.generated.js';

// A source bundle and a translation of it, at one nav leaf per app so the
// per-locale independence claim has something to be independent about.
Expand Down Expand Up @@ -187,6 +190,7 @@ describe('the shipped bundles', () => {
['zh-CN', zhCN, zhCNSourceHashes],
['ja-JP', jaJP, jaJPSourceHashes],
['es-ES', esES, esESSourceHashes],
['zh-Hant', zhHant, zhHantSourceHashes],
] as const;

// ⭐ The acceptance criterion, applied to the real data: B introduces NO new
Expand Down Expand Up @@ -384,6 +388,7 @@ describe('the committed provenance companions', () => {
'zh-CN': zhCNGeneratedSourceHashes,
'ja-JP': jaJPGeneratedSourceHashes,
'es-ES': esESGeneratedSourceHashes,
'zh-Hant': zhHantGeneratedSourceHashes,
} as const;

// ⚠️ Deliberately NOT asserted here: "no shipped leaf is stale". That claim
Expand Down
Loading
Loading