Skip to content

Commit eb68b98

Browse files
i18n(platform-objects): ship the platform objects, Setup, Studio and Account in Arabic (#31120)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiP9RS1KjicEPyef7VNhFzFa)_ Part of #30512 Clause-②: no Metadata-protocol: none ## 维护者速读 - 业务影响:阿拉伯语(ar)用户打开平台对象、Setup、Studio 和 Account 时,界面不再是英文。 - 范围:只动 `@objectstack/platform-objects`,按 #30951 加 zh-Hant 的同一套路加 ar;spec、ui、邮件模板都不碰。 - 翻译:从 main 的 en 源重新翻译 3078 个叶子,术语对齐 ui/packages/i18n 的 ar;只有 17 个标识符(JWKS、cron、JSON 示例等)保留英文。 - 跨包对齐:审批、分配规则、各设置页的导航名与对应包的 ar 标题逐字一致,并由现有测试守住。 - 风险:只新增语言与测试里的语言列表,不改任何既有语言的文本;新文本同样走 source-hash 回退。 - 合并:生成文件让 PR 超过 3000 行,需要座位契约复审后由人工合并。 ## What changes - New `ar` bundles in `packages/platform-objects/src/apps/translations`: `ar.objects.generated.ts` (1676 leaves), `ar.metadata-forms.generated.ts` (1263 leaves), their provenance table `ar.source-hashes.generated.ts` (16 deliberate echoes), the hand-authored `ar.ts` (139 leaves) and `ar.source-hashes.ts` recording the `en` digest of every hand leaf. - Registration: the extract config's `--locales` docstring and `translations:` baseline, `setup.translation.ts`, the `apps` barrel, and `metadata-translations/index.ts`. - Inline maps: 35 in `sys-user.page.ts`, 6 in `sys-organization.page.ts`, and the System Overview null label in `system.datasets.ts`. - Wording pins: `nav_account_approvals` reads as plugin-approvals' `my_pending` label, `nav_assignment_rules` as `sys_assignment_rule`'s plural label, each `nav_settings_*` as its service-settings hub title, and the four #30935 keys (`grp_account_ai_assistant`, both `nav_connect_agent` entries, `pages.connect_agent.label`). - Changeset `.changeset/30512-platform-objects-ar.md` (patch). ## Tests - `ar` added to the locale lists of the platform-objects tests that #30951 widened for zh-Hant (vocabulary, nav parity, dead-key tombstone, source-hash, copy tests, record-page tabs, plugin translation loads), plus plugin-approvals `approval-status-vocabulary`, plugin-security `people-org-assignment-nav-contribution` and service-settings `setup-nav-hub-parity`. The cli `platform-page-i18n-parity` LOCALE_KEY shape already accepts `ar`. ## Acceptance notes - Every leaf is translated; the only byte copies of `en` are identifiers and code examples, and the extract records them in the provenance table. - Lists of Latin identifiers use the Arabic comma, and no proclitic `و` is glued to a Latin token. - `Position` on the add-record button is الموضع and the job position is المنصب; a hook's `Body` is المتن. ## Verification - `os i18n extract ... --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar --fill=default --source-hashes`: regenerates the ar files byte-identical and leaves the other locales unchanged. - `vitest run` in platform-objects: 117 files, 1683 tests passed. The plugin-approvals, plugin-security, service-settings and cli parity tests passed. - `dispatch-gates --ran`: 64 derived families, 64 run, 0 NOT-MEASURED, all exit 0 (including `check:i18n`). - `pnpm lint`, platform-objects `typecheck`, and all 57 commands of the "Lint · docs & code guards" lane (including `check:cli-command-ids`): exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 21c1f74 commit eb68b98

36 files changed

Lines changed: 7233 additions & 48 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
i18n(platform-objects): the platform objects, Setup, Studio and Account ship in Arabic
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`@objectstack/platform-objects` now carries `ar` beside `en`, `zh-CN`, `ja-JP`, `es-ES` and `zh-Hant`: 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 Arabic 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 `ar` in its `--locales`, so `check:i18n` now gates the new bundles. Package 11 of #30512, second of seven locales; `fr`, `pt-BR`, `ru`, `de` and `ko` follow.

‎packages/platform-objects/scripts/i18n-extract.config.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* pointed at this file via `bundle-require`:
99
*
1010
* os i18n extract packages/platform-objects/scripts/i18n-extract.config.ts \
11-
* --locales=zh-CN,ja-JP,es-ES,zh-Hant \
11+
* --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar \
1212
* --fill=default \
1313
* --source-hashes \
1414
* --out=packages/platform-objects/src/apps/translations
@@ -222,11 +222,13 @@ import { zhCNObjects } from '../src/apps/translations/zh-CN.objects.generated.js
222222
import { jaJPObjects } from '../src/apps/translations/ja-JP.objects.generated.js';
223223
import { esESObjects } from '../src/apps/translations/es-ES.objects.generated.js';
224224
import { zhHantObjects } from '../src/apps/translations/zh-Hant.objects.generated.js';
225+
import { arObjects } from '../src/apps/translations/ar.objects.generated.js';
225226
import { enMetadataForms } from '../src/apps/translations/en.metadata-forms.generated.js';
226227
import { zhCNMetadataForms } from '../src/apps/translations/zh-CN.metadata-forms.generated.js';
227228
import { jaJPMetadataForms } from '../src/apps/translations/ja-JP.metadata-forms.generated.js';
228229
import { esESMetadataForms } from '../src/apps/translations/es-ES.metadata-forms.generated.js';
229230
import { zhHantMetadataForms } from '../src/apps/translations/zh-Hant.metadata-forms.generated.js';
231+
import { arMetadataForms } from '../src/apps/translations/ar.metadata-forms.generated.js';
230232

231233
// ── Existing hand-authored app/dashboard/page translations ────────────────
232234
// The per-locale assemblers, NOT the `SetupAppTranslations` wrapper. Each one
@@ -250,6 +252,7 @@ import { zhCN } from '../src/apps/translations/zh-CN.js';
250252
import { jaJP } from '../src/apps/translations/ja-JP.js';
251253
import { esES } from '../src/apps/translations/es-ES.js';
252254
import { zhHant } from '../src/apps/translations/zh-Hant.js';
255+
import { ar } from '../src/apps/translations/ar.js';
253256

254257
/**
255258
* The annotation is load-bearing, not decoration (#10868). `defineStack`
@@ -351,6 +354,7 @@ const config: ObjectStackDefinition = defineStack({
351354
{ 'ja-JP': { ...jaJP, objects: jaJPObjects, metadataForms: jaJPMetadataForms } },
352355
{ 'es-ES': { ...esES, objects: esESObjects, metadataForms: esESMetadataForms } },
353356
{ 'zh-Hant': { ...zhHant, objects: zhHantObjects, metadataForms: zhHantMetadataForms } },
357+
{ 'ar': { ...ar, objects: arObjects, metadataForms: arMetadataForms } },
354358
],
355359
});
356360

‎packages/platform-objects/src/apps/dashboards/system-overview-null-label.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('System Overview: the null-user bucket reads "System"', () => {
3131
'ja-JP': 'システム',
3232
'es-ES': 'Sistema',
3333
'zh-Hant': '系統',
34+
ar: 'النظام',
3435
});
3536
});
3637

‎packages/platform-objects/src/apps/dashboards/system.datasets.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const SysAuditLogDataset = defineDataset({
5757
label: 'User',
5858
field: 'user_id',
5959
type: 'lookup',
60-
nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema', 'zh-Hant': '系統' },
60+
nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema', 'zh-Hant': '系統', ar: 'النظام' },
6161
},
6262
],
6363
measures: [{ name: 'event_count', label: 'Events', aggregate: 'count' }],

‎packages/platform-objects/src/apps/setup-copy-vocabulary.test.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,19 @@ import { zhCN } from './translations/zh-CN.js';
4646
import { jaJP } from './translations/ja-JP.js';
4747
import { esES } from './translations/es-ES.js';
4848
import { zhHant } from './translations/zh-Hant.js';
49+
import { ar } from './translations/ar.js';
4950
import { enObjects } from './translations/en.objects.generated.js';
5051
import { zhCNObjects } from './translations/zh-CN.objects.generated.js';
5152
import { jaJPObjects } from './translations/ja-JP.objects.generated.js';
5253
import { esESObjects } from './translations/es-ES.objects.generated.js';
5354
import { zhHantObjects } from './translations/zh-Hant.objects.generated.js';
55+
import { arObjects } from './translations/ar.objects.generated.js';
5456
import { enMetadataForms } from './translations/en.metadata-forms.generated.js';
5557
import { zhCNMetadataForms } from './translations/zh-CN.metadata-forms.generated.js';
5658
import { jaJPMetadataForms } from './translations/ja-JP.metadata-forms.generated.js';
5759
import { esESMetadataForms } from './translations/es-ES.metadata-forms.generated.js';
5860
import { zhHantMetadataForms } from './translations/zh-Hant.metadata-forms.generated.js';
61+
import { arMetadataForms } from './translations/ar.metadata-forms.generated.js';
5962

6063
/** The design-record vocabulary an admin-facing string must not carry. */
6164
const DESIGN_RECORD_VOCABULARY: ReadonlyArray<readonly [string, RegExp]> = [
@@ -130,13 +133,14 @@ function objectSchemas(...modules: Array<Record<string, unknown>>): Map<string,
130133
return byName;
131134
}
132135

133-
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant } as const;
136+
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar } as const;
134137
const OBJECT_BUNDLES = {
135-
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects,
138+
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects, ar: arObjects,
136139
} as const;
137140
const FORM_BUNDLES = {
138141
en: enMetadataForms, 'zh-CN': zhCNMetadataForms, 'ja-JP': jaJPMetadataForms, 'es-ES': esESMetadataForms,
139142
'zh-Hant': zhHantMetadataForms,
143+
ar: arMetadataForms,
140144
} as const;
141145

142146
describe('Setup copy carries no design-record vocabulary', () => {

‎packages/platform-objects/src/apps/setup-nav-distinct-labels.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import { zhCN } from './translations/zh-CN.js';
1616
import { jaJP } from './translations/ja-JP.js';
1717
import { esES } from './translations/es-ES.js';
1818
import { zhHant } from './translations/zh-Hant.js';
19+
import { ar } from './translations/ar.js';
1920

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

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

‎packages/platform-objects/src/apps/setup-nav-per-user-stores.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,10 @@ describe('(2) the User page lists each per-user store, keyed to its record (#238
111111
'ja-JP': '設定',
112112
'es-ES': 'Preferencias',
113113
'zh-Hant': '偏好設定',
114+
ar: 'التفضيلات',
114115
});
115116
expect((tabOf('sys_account')?.label as Rec)?.en).toBe('Linked Accounts');
116-
expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['en', 'es-ES', 'ja-JP', 'zh-CN', 'zh-Hant']);
117+
expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['ar', 'en', 'es-ES', 'ja-JP', 'zh-CN', 'zh-Hant']);
117118
});
118119
});
119120

‎packages/platform-objects/src/apps/translations/app-nav-translation-parity.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ import { zhCN } from './zh-CN.js';
5959
import { jaJP } from './ja-JP.js';
6060
import { esES } from './es-ES.js';
6161
import { zhHant } from './zh-Hant.js';
62+
import { ar } from './ar.js';
6263

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

6566
/** Every statically declared nav item of an app, depth-first, with its label. */
6667
function navItems(app: { navigation?: unknown[] }): Array<{ id: string; label?: string }> {

0 commit comments

Comments
 (0)