Skip to content

Commit 2f05a46

Browse files
i18n(platform-objects): ship the platform objects, Setup, Studio and Account in French (#31233)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiR7jUqx4egncChjbUD2S9oH)_ Part of #30512 Fixes #28107 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:给 `@objectstack/platform-objects` 加上法语(fr):平台对象、Studio 的元数据表单、Setup/Studio/Account 导航、系统概览仪表板和用户/组织记录页的内嵌文字,做法与刚合并的 ar(#31120)逐文件一致。 - 为什么改:语言设为 fr 的用户在主页动态里读到「A modifié User « … » : Banned et Ban Reason」,法语句子里夹着英文字段名(#28107);fr 也是 #30512 要补的第三个语言。 - 风险与回滚:只新增 fr 文件、注册行和测试里的语言列表,不改任何已有语言的文字;新文字同样走 source-hash 回退。回滚即 revert 本 PR。 - 先看:`packages/platform-objects/src/apps/translations/fr.ts`(导航钉住项),`packages/plugins/plugin-audit/src/activity-summary-fr-sys-user-labels.test.ts`(#28107 的守护测试),`fr.metadata-forms.generated.ts` 里 repeater 行属性那几处带上父名的译法。 - 两分钟验证:`cd packages/plugins/plugin-audit && npx vitest run src/activity-summary-fr-sys-user-labels.test.ts`,预期 5 个测试通过,句子为「A modifié Utilisateur « Carl Edited » : Banni et Motif du bannissement」。 - 需要你做的:生成文件让 PR 超过 3000 行,座位契约复审 ACCEPT 后请人工合并。 ## What changes - New `fr` bundles in `packages/platform-objects/src/apps/translations`: `fr.objects.generated.ts` (1697 leaves), `fr.metadata-forms.generated.ts` (1263 leaves), their provenance table `fr.source-hashes.generated.ts` (183 deliberate byte copies of `en`, listed under Acceptance notes), the hand-authored `fr.ts` (140 leaves) and `fr.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: 36 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 (`Mes approbations en attente`), `nav_assignment_rules` as `sys_assignment_rule`'s plural label (`Règles d’attribution`), each `nav_settings_*` (including `nav_settings_access_policy`) as its service-settings hub title, `nav_settings_hub` as the console's `backToHub` (`Tous les paramètres`), and the four #30935 keys: `grp_account_ai_assistant` = `Assistant IA`; both `nav_connect_agent` entries and `pages.connect_agent.label` = `Connecter un assistant IA`. - `fr.source-hashes.ts` also records `nav_settings_access_policy`, which the `ar` and `zh-Hant` tables leave legacy-trusted. - Changeset `.changeset/30512-platform-objects-fr.md` (patch). ## Tests - `fr` added to the locale lists of every test #31120 widened for `ar` (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 `fr`. - New `packages/plugins/plugin-audit/src/activity-summary-fr-sys-user-labels.test.ts` (#28107): an i18n service holding what a booted kernel holds (this plugin's bundle and the platform Setup bundle, every locale) composes a `sys_user` update summary for a reader whose locale is `fr` or `fr-FR`. The reader gets `A modifié Utilisateur « Carl Edited » : Banni et Motif du bannissement`, and a tracked change names `Motif du bannissement`; an `en` control keeps `Updated User "Carl Edited": Banned and Ban Reason`. Ablation: with the platform `fr` bundle left out of the service, three cases fail with `A modifié User « Carl Edited » : Banned et Ban Reason`, the sentence #28107 reported. ## Acceptance notes - Every leaf is translated. The 183 byte copies of `en` the extract records are French words spelled like the English (`Description`, `Type`, `Source`, `Version`, `Navigation`, `Configuration`, `Kanban`, …), identifiers and header names (`ID`, `API`, `JWKS`, `JWKS URI`, `Message-ID`, `Reply-To`, `Cc`, `cron`, `snake_case`), lowercase option values that read the same in French (`marketing`, `notification`, `workflow`, `package`), and the five JSON or scope examples kept verbatim as every locale keeps them. - Where a catalog test refuses an `en` echo (repeater row properties, dashboard header children, System Overview captions), the leaf names its parent instead (`ID du nœud`, `Description de l’option`, `Type de variable`, `Boutons d’action`, `Nombre de sessions`), as `es-ES` does. - Typography follows `ui/packages/i18n` `fr`: a plain space before `: ; ? !`, plain spaces inside « », the typographic apostrophe, sentence case. - `Position` on the add-record button is `Position` (the place); the job position is `Poste`. A hook's `Body` is `Corps`. Adjectives agree with the object they qualify (`Toutes`, `Révoquées`, `Désactivée`, …). - Business units are `Unités commerciales`; no `sys_business_unit*` label says `Département` (`bu-rename-consistency`). ## Verification - `os i18n extract packages/platform-objects/scripts/i18n-extract.config.ts --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar,fr --fill=default --source-hashes`: regenerates the two `fr` bundles byte-identical and leaves every other locale unchanged, before and after the merge of `origin/main`. - `vitest run` in platform-objects: 119 files, 1746 tests passed. plugin-approvals `approval-status-vocabulary` (6), plugin-security `people-org-assignment-nav-contribution` (5), service-settings `setup-nav-hub-parity` (15), cli `platform-page-i18n-parity` (27) and the new plugin-audit test (5) passed. - platform-objects `typecheck`: exit 0. - `dispatch-gates --ran` at dd9303a (after the merge of `origin/main`): 64 derived families, 64 run, 0 NOT-MEASURED, all exit 0 (including `check:i18n`, `check:dts-closure`, `check:dual-build-cjs-loads`). - `pnpm lint`: exit 0. "Lint · docs & code guards" lane: 52 of its 53 run steps exit 0 locally (including `check:i18n-stale-fill` and `check:cli-command-ids`); the 53rd is `lane-verdict.mjs`, which reads `GITHUB_JOB` and only measures in CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 300f740 commit 2f05a46

37 files changed

Lines changed: 7492 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 French
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`@objectstack/platform-objects` now carries `fr` beside `en`, `zh-CN`, `ja-JP`, `es-ES`, `zh-Hant` and `ar`: 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. A user whose locale is `fr` no longer reads English `sys_user` field labels inside French sentences, as the Home feed showed them (`A modifié Utilisateur « … » : Banned et Ban Reason`). 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 `fr` in its `--locales`, so `check:i18n` now gates the new bundles. Package 11 of #30512, third of seven locales; `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,ar \
11+
* --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar,fr \
1212
* --fill=default \
1313
* --source-hashes \
1414
* --out=packages/platform-objects/src/apps/translations
@@ -223,12 +223,14 @@ 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';
225225
import { arObjects } from '../src/apps/translations/ar.objects.generated.js';
226+
import { frObjects } from '../src/apps/translations/fr.objects.generated.js';
226227
import { enMetadataForms } from '../src/apps/translations/en.metadata-forms.generated.js';
227228
import { zhCNMetadataForms } from '../src/apps/translations/zh-CN.metadata-forms.generated.js';
228229
import { jaJPMetadataForms } from '../src/apps/translations/ja-JP.metadata-forms.generated.js';
229230
import { esESMetadataForms } from '../src/apps/translations/es-ES.metadata-forms.generated.js';
230231
import { zhHantMetadataForms } from '../src/apps/translations/zh-Hant.metadata-forms.generated.js';
231232
import { arMetadataForms } from '../src/apps/translations/ar.metadata-forms.generated.js';
233+
import { frMetadataForms } from '../src/apps/translations/fr.metadata-forms.generated.js';
232234

233235
// ── Existing hand-authored app/dashboard/page translations ────────────────
234236
// The per-locale assemblers, NOT the `SetupAppTranslations` wrapper. Each one
@@ -253,6 +255,7 @@ import { jaJP } from '../src/apps/translations/ja-JP.js';
253255
import { esES } from '../src/apps/translations/es-ES.js';
254256
import { zhHant } from '../src/apps/translations/zh-Hant.js';
255257
import { ar } from '../src/apps/translations/ar.js';
258+
import { fr } from '../src/apps/translations/fr.js';
256259

257260
/**
258261
* The annotation is load-bearing, not decoration (#10868). `defineStack`
@@ -355,6 +358,7 @@ const config: ObjectStackDefinition = defineStack({
355358
{ 'es-ES': { ...esES, objects: esESObjects, metadataForms: esESMetadataForms } },
356359
{ 'zh-Hant': { ...zhHant, objects: zhHantObjects, metadataForms: zhHantMetadataForms } },
357360
{ 'ar': { ...ar, objects: arObjects, metadataForms: arMetadataForms } },
361+
{ 'fr': { ...fr, objects: frObjects, metadataForms: frMetadataForms } },
358362
],
359363
});
360364

‎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
@@ -32,6 +32,7 @@ describe('System Overview: the null-user bucket reads "System"', () => {
3232
'es-ES': 'Sistema',
3333
'zh-Hant': '系統',
3434
ar: 'النظام',
35+
fr: 'Système',
3536
});
3637
});
3738

‎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': '系統', ar: 'النظام' },
60+
nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema', 'zh-Hant': '系統', ar: 'النظام', fr: 'Système' },
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
@@ -47,18 +47,21 @@ import { jaJP } from './translations/ja-JP.js';
4747
import { esES } from './translations/es-ES.js';
4848
import { zhHant } from './translations/zh-Hant.js';
4949
import { ar } from './translations/ar.js';
50+
import { fr } from './translations/fr.js';
5051
import { enObjects } from './translations/en.objects.generated.js';
5152
import { zhCNObjects } from './translations/zh-CN.objects.generated.js';
5253
import { jaJPObjects } from './translations/ja-JP.objects.generated.js';
5354
import { esESObjects } from './translations/es-ES.objects.generated.js';
5455
import { zhHantObjects } from './translations/zh-Hant.objects.generated.js';
5556
import { arObjects } from './translations/ar.objects.generated.js';
57+
import { frObjects } from './translations/fr.objects.generated.js';
5658
import { enMetadataForms } from './translations/en.metadata-forms.generated.js';
5759
import { zhCNMetadataForms } from './translations/zh-CN.metadata-forms.generated.js';
5860
import { jaJPMetadataForms } from './translations/ja-JP.metadata-forms.generated.js';
5961
import { esESMetadataForms } from './translations/es-ES.metadata-forms.generated.js';
6062
import { zhHantMetadataForms } from './translations/zh-Hant.metadata-forms.generated.js';
6163
import { arMetadataForms } from './translations/ar.metadata-forms.generated.js';
64+
import { frMetadataForms } from './translations/fr.metadata-forms.generated.js';
6265

6366
/** The design-record vocabulary an admin-facing string must not carry. */
6467
const DESIGN_RECORD_VOCABULARY: ReadonlyArray<readonly [string, RegExp]> = [
@@ -133,14 +136,15 @@ function objectSchemas(...modules: Array<Record<string, unknown>>): Map<string,
133136
return byName;
134137
}
135138

136-
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar } as const;
139+
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr } as const;
137140
const OBJECT_BUNDLES = {
138-
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects, ar: arObjects,
141+
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects, ar: arObjects, fr: frObjects,
139142
} as const;
140143
const FORM_BUNDLES = {
141144
en: enMetadataForms, 'zh-CN': zhCNMetadataForms, 'ja-JP': jaJPMetadataForms, 'es-ES': esESMetadataForms,
142145
'zh-Hant': zhHantMetadataForms,
143146
ar: arMetadataForms,
147+
fr: frMetadataForms,
144148
} as const;
145149

146150
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
@@ -17,8 +17,9 @@ import { jaJP } from './translations/ja-JP.js';
1717
import { esES } from './translations/es-ES.js';
1818
import { zhHant } from './translations/zh-Hant.js';
1919
import { ar } from './translations/ar.js';
20+
import { fr } from './translations/fr.js';
2021

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

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

‎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
@@ -112,9 +112,10 @@ describe('(2) the User page lists each per-user store, keyed to its record (#238
112112
'es-ES': 'Preferencias',
113113
'zh-Hant': '偏好設定',
114114
ar: 'التفضيلات',
115+
fr: 'Préférences',
115116
});
116117
expect((tabOf('sys_account')?.label as Rec)?.en).toBe('Linked Accounts');
117-
expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['ar', 'en', 'es-ES', 'ja-JP', 'zh-CN', 'zh-Hant']);
118+
expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['ar', 'en', 'es-ES', 'fr', 'ja-JP', 'zh-CN', 'zh-Hant']);
118119
});
119120
});
120121

‎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
@@ -60,8 +60,9 @@ import { jaJP } from './ja-JP.js';
6060
import { esES } from './es-ES.js';
6161
import { zhHant } from './zh-Hant.js';
6262
import { ar } from './ar.js';
63+
import { fr } from './fr.js';
6364

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

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

‎packages/platform-objects/src/apps/translations/bu-rename-consistency.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { jaJPObjects } from './ja-JP.objects.generated.js';
1616
import { esESObjects } from './es-ES.objects.generated.js';
1717
import { zhHantObjects } from './zh-Hant.objects.generated.js';
1818
import { arObjects } from './ar.objects.generated.js';
19+
import { frObjects } from './fr.objects.generated.js';
1920

2021
const LOCALES = [
2122
{ name: 'en', objs: enObjects as Record<string, any>, old: /\bDepartment\b/ },
@@ -24,6 +25,7 @@ const LOCALES = [
2425
{ name: 'es-ES', objs: esESObjects as Record<string, any>, old: /Departamento/ },
2526
{ name: 'zh-Hant', objs: zhHantObjects as Record<string, any>, old: /部門/ },
2627
{ name: 'ar', objs: arObjects as Record<string, any>, old: /قسم/ },
28+
{ name: 'fr', objs: frObjects as Record<string, any>, old: /Département/ },
2729
];
2830
const OBJECTS = ['sys_business_unit', 'sys_business_unit_member'];
2931

‎packages/platform-objects/src/apps/translations/confirm-question-carryover.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ import { jaJPObjects } from './ja-JP.objects.generated.js';
2727
import { esESObjects } from './es-ES.objects.generated.js';
2828
import { zhHantObjects } from './zh-Hant.objects.generated.js';
2929
import { arObjects } from './ar.objects.generated.js';
30+
import { frObjects } from './fr.objects.generated.js';
3031

3132
const LOCALES = [
3233
['zh-CN', zhCNObjects],
3334
['ja-JP', jaJPObjects],
3435
['es-ES', esESObjects],
3536
['zh-Hant', zhHantObjects],
3637
['ar', arObjects],
38+
['fr', frObjects],
3739
] as const;
3840

3941
/**

0 commit comments

Comments
 (0)