Skip to content

Commit 2ae3e30

Browse files
i18n(platform-objects): ship the platform objects, Setup, Studio and Account in Russian; refresh the stale fr connect-agent subtitle (#31466)
domain:server#3 · os-project-manager · session_01FEkQ5pXkgNXVpCn2GZDjyy Body: seat refresh (ACCEPT) · 2026-11-08T10:44Z <!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X5xwArPxERFNdAjYY2nKcc9)_ Part of #30512 Thread-read: 6719477612 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:给 `@objectstack/platform-objects` 加上俄语(ru):平台对象、Studio 的元数据表单、Setup/Studio/Account 导航、系统概览仪表板和用户/组织记录页的内嵌文字,做法与已合并的 fr(#31233)、pt-BR(#31440)逐文件一致。同一 PR 顺手修好 fr 的 `pages.connect_agent.subtitle`:按当前英文重译并刷新摘要。 - 为什么改:ru 是 #30512 要补的第五个语言(之后是 de、ko)。fr 那条副标题是按旧英文翻的,摘要过期,fr 用户目前看到的是英文回退。 - 风险与回滚:新增 ru 文件、注册行和测试里的语言列表;已有语言只改 fr 的一条副标题和它的摘要,其余 23 个生成文件字节不变;新文字同样走 source-hash 回退。回滚即 revert 本 PR。 - 先看:`packages/platform-objects/src/apps/translations/ru.ts`(导航钉住项),`fr.ts` + `fr.source-hashes.ts` 里 `connect_agent.subtitle` 的两行改动,`ru.source-hashes.generated.ts`(45 条与英文相同的叶子,下面逐类列出)。 - 两分钟验证:`cd packages/platform-objects && npx vitest run src/apps/translations/source-hash.test.ts src/apps/translations/app-nav-translation-parity.test.ts`,预期 2 个文件、111 个测试通过。 - 需要你做的:生成文件让本 PR 超过了 3000 行,所以要请 os-zhuang 或 hotlong 批准(或维护者直接合并)。席位 ACCEPT 是 PR 评论 6720497415,Head-sha `efe02a1756a3e095bfa458add5860f81776726ad`。批准后由席位落地。 ## What changes - New `ru` bundles in `packages/platform-objects/src/apps/translations`: `ru.objects.generated.ts` (1697 leaves), `ru.metadata-forms.generated.ts` (1263 leaves), their provenance table `ru.source-hashes.generated.ts` (45 deliberate byte copies of `en`, listed under Acceptance notes), the hand-authored `ru.ts` (141 leaves) and `ru.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, as #31233 lists them: `nav_account_approvals` reads as plugin-approvals' `my_pending` label (`Ждут моего решения`), `nav_assignment_rules` as `sys_assignment_rule`'s plural label (`Правила назначения`), each `nav_settings_*` (including `nav_settings_access_policy`, `Политика доступа`) as its service-settings hub title, `nav_settings_hub` as the console's `backToHub` (`Все настройки`), and the four #30935 keys: `grp_account_ai_assistant` = `ИИ-ассистент`; both `nav_connect_agent` entries and `pages.connect_agent.label` = `Подключить ИИ-ассистента`. - **fr fix:** `pages.connect_agent.subtitle` in `fr.ts` was translated from the previous English subtitle (it spoke of "any MCP-compatible AI client"), and `fr.source-hashes.ts` recorded that old digest (`b0d7c6d174511bcb`), so the source-hash fallback served `fr` readers the English sentence. It is now retranslated from the current English text (`Travaillez dans ce système depuis Claude, Doubao et d’autres assistants IA. Chacun agit en votre nom : il ne voit et ne modifie que ce que vos propres autorisations permettent.`) and its digest is `51c467af44024a1b`, which equals the digest the table derives from `en` today. No other `fr` leaf changes. - Changeset `.changeset/30512-platform-objects-ru.md` (patch), which also names the `fr` fix. ## Tests - `ru` added beside `pt-BR` to the locale lists of every test #31440 widened (vocabulary, nav parity, dead-key tombstone, source-hash, bu-rename with `old: /Отдел/`, the five identity copy tests, record-page tabs, null label, per-user stores, 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 `ru`. - No new negative pin: #31440's plugin-audit `pt-BR` twin test is outside this claim's file surface, so no `ru` twin is added here (see Acceptance notes). ## Acceptance notes - Every leaf is translated. The 45 byte copies of `en` the extract records are identifiers and header names (`ID` ×24, `API` ×2, `JWKS`, `JWKS URI`, `Message-ID`, `Slug`, `Web`, `snake_case`, the SAML `urn:…:emailAddress` NameID format, the `openid email profile` / `email` / `name` defaults), lowercase option values that are identifiers (`cron`, `dlq`, `api`, `web`), and the five JSON examples kept verbatim as every locale keeps them. - No catalog test refused an `en` echo, so no leaf needed its parent's name added. Where Russian needs the parent to read correctly anyway, the leaf names it (for example `Описание виджета`, `Тип фильтра`, `Размеры виджета`). - Vocabulary follows the existing `ru` plugin bundles: «» quotes, `Должность`, `Бизнес-подразделение`, `Набор разрешений`, `Дашборд`, `Очереди`, `Веб-хуки`, `HTTP-доставки`, `Общий доступ к записям`, `Настройки уведомлений`; hub titles exactly as service-settings spells them (`Доставка почты`, `Брендинг`, `Хранилище файлов`, `ИИ и Embedder`, …). Adjectives and participles agree with the noun they qualify (`Активен`/`Активны`, `Отключён`, `Отозван`, plural view names `Активные`, `Ожидающие`, …). - Business units are `Бизнес-подразделения`; no `sys_business_unit*` label says `Отдел` (`bu-rename-consistency`). `Отдел` survives only as the unit-kind option value, which is not a label. - Deviation: #31440 shipped a plugin-audit `pt-BR` twin of the #28107 activity-summary test. This PR does not add a `ru` twin, because plugin-audit is not in the claim's file surface; it can follow as its own card. ## Verification - `os i18n extract packages/platform-objects/scripts/i18n-extract.config.ts --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar,fr,pt-BR,ru --fill=default --source-hashes --out=packages/platform-objects/src/apps/translations`: a rerun regenerates the `ru` bundles byte-identical and leaves the 23 other generated files unchanged (none of them is in the diff) (`cmp` over every file in the directory). `check-i18n-bundles --filter=platform-objects`: in sync (26 bundles). - Resolved through `SetupAppTranslations`: `fr` `pages.connect_agent.subtitle` now starts `Travaillez dans ce système depuis Claude, Doubao…`; `ru` gets `Работайте с этой системой из Claude, Doubao…`. - `vitest run` in platform-objects: 120 files, 1841 tests passed. plugin-approvals `approval-status-vocabulary` (6), plugin-security `people-org-assignment-nav-contribution` (5), service-settings `setup-nav-hub-parity` (19) and cli `platform-page-i18n-parity` (27) passed. - platform-objects `typecheck`: exit 0. `check:app-nav-i18n`: OK (13 contributors, 9 locales, 2 apps). - `dispatch-gates --ran` at 9c137f2 (after merging `origin/main`): 68 derived families, 68 run, 0 NOT-MEASURED, all exit 0 (including `check:i18n`, `check:i18n-stale-fill`, `check:dts-closure`, `check:dual-build-cjs-loads`, `check:nul-bytes`). - `eslint` over the changed `.ts` paths (scoped, `--no-inline-config`): exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5mqoYjnPaGZhS8jzcF)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 595311b commit 2ae3e30

38 files changed

Lines changed: 7278 additions & 50 deletions
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
i18n(platform-objects): the platform objects, Setup, Studio and Account ship in Russian
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`@objectstack/platform-objects` now carries `ru` beside `en`, `zh-CN`, `ja-JP`, `es-ES`, `zh-Hant`, `ar`, `fr` and `pt-BR`: 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 `ru` no longer reads English `sys_*` labels in the console. 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 `ru` in its `--locales`, so `check:i18n` now gates the new bundles.
11+
12+
The French Connect-an-AI-assistant page subtitle (`pages.connect_agent.subtitle`) is retranslated from the current English text, which names Claude and Doubao, and its recorded digest is refreshed; before this, the source-hash fallback served the English sentence to `fr` users because the French leaf had been translated from an older wording. Package 11 of #30512, fifth of seven locales; `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,fr,pt-BR \
11+
* --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar,fr,pt-BR,ru \
1212
* --fill=default \
1313
* --source-hashes \
1414
* --out=packages/platform-objects/src/apps/translations
@@ -225,6 +225,7 @@ import { zhHantObjects } from '../src/apps/translations/zh-Hant.objects.generate
225225
import { arObjects } from '../src/apps/translations/ar.objects.generated.js';
226226
import { frObjects } from '../src/apps/translations/fr.objects.generated.js';
227227
import { ptBRObjects } from '../src/apps/translations/pt-BR.objects.generated.js';
228+
import { ruObjects } from '../src/apps/translations/ru.objects.generated.js';
228229
import { enMetadataForms } from '../src/apps/translations/en.metadata-forms.generated.js';
229230
import { zhCNMetadataForms } from '../src/apps/translations/zh-CN.metadata-forms.generated.js';
230231
import { jaJPMetadataForms } from '../src/apps/translations/ja-JP.metadata-forms.generated.js';
@@ -233,6 +234,7 @@ import { zhHantMetadataForms } from '../src/apps/translations/zh-Hant.metadata-f
233234
import { arMetadataForms } from '../src/apps/translations/ar.metadata-forms.generated.js';
234235
import { frMetadataForms } from '../src/apps/translations/fr.metadata-forms.generated.js';
235236
import { ptBRMetadataForms } from '../src/apps/translations/pt-BR.metadata-forms.generated.js';
237+
import { ruMetadataForms } from '../src/apps/translations/ru.metadata-forms.generated.js';
236238

237239
// ── Existing hand-authored app/dashboard/page translations ────────────────
238240
// The per-locale assemblers, NOT the `SetupAppTranslations` wrapper. Each one
@@ -259,6 +261,7 @@ import { zhHant } from '../src/apps/translations/zh-Hant.js';
259261
import { ar } from '../src/apps/translations/ar.js';
260262
import { fr } from '../src/apps/translations/fr.js';
261263
import { ptBR } from '../src/apps/translations/pt-BR.js';
264+
import { ru } from '../src/apps/translations/ru.js';
262265

263266
/**
264267
* The annotation is load-bearing, not decoration (#10868). `defineStack`
@@ -363,6 +366,7 @@ const config: ObjectStackDefinition = defineStack({
363366
{ 'ar': { ...ar, objects: arObjects, metadataForms: arMetadataForms } },
364367
{ 'fr': { ...fr, objects: frObjects, metadataForms: frMetadataForms } },
365368
{ 'pt-BR': { ...ptBR, objects: ptBRObjects, metadataForms: ptBRMetadataForms } },
369+
{ ru: { ...ru, objects: ruObjects, metadataForms: ruMetadataForms } },
366370
],
367371
});
368372

‎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
@@ -34,6 +34,7 @@ describe('System Overview: the null-user bucket reads "System"', () => {
3434
ar: 'النظام',
3535
fr: 'Système',
3636
'pt-BR': 'Sistema',
37+
ru: 'Система',
3738
});
3839
});
3940

‎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: 'النظام', fr: 'Système', 'pt-BR': 'Sistema' },
60+
nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema', 'zh-Hant': '系統', ar: 'النظام', fr: 'Système', 'pt-BR': 'Sistema', ru: 'Система' },
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
@@ -49,6 +49,7 @@ import { zhHant } from './translations/zh-Hant.js';
4949
import { ar } from './translations/ar.js';
5050
import { fr } from './translations/fr.js';
5151
import { ptBR } from './translations/pt-BR.js';
52+
import { ru } from './translations/ru.js';
5253
import { enObjects } from './translations/en.objects.generated.js';
5354
import { zhCNObjects } from './translations/zh-CN.objects.generated.js';
5455
import { jaJPObjects } from './translations/ja-JP.objects.generated.js';
@@ -57,6 +58,7 @@ import { zhHantObjects } from './translations/zh-Hant.objects.generated.js';
5758
import { arObjects } from './translations/ar.objects.generated.js';
5859
import { frObjects } from './translations/fr.objects.generated.js';
5960
import { ptBRObjects } from './translations/pt-BR.objects.generated.js';
61+
import { ruObjects } from './translations/ru.objects.generated.js';
6062
import { enMetadataForms } from './translations/en.metadata-forms.generated.js';
6163
import { zhCNMetadataForms } from './translations/zh-CN.metadata-forms.generated.js';
6264
import { jaJPMetadataForms } from './translations/ja-JP.metadata-forms.generated.js';
@@ -65,6 +67,7 @@ import { zhHantMetadataForms } from './translations/zh-Hant.metadata-forms.gener
6567
import { arMetadataForms } from './translations/ar.metadata-forms.generated.js';
6668
import { frMetadataForms } from './translations/fr.metadata-forms.generated.js';
6769
import { ptBRMetadataForms } from './translations/pt-BR.metadata-forms.generated.js';
70+
import { ruMetadataForms } from './translations/ru.metadata-forms.generated.js';
6871

6972
/** The design-record vocabulary an admin-facing string must not carry. */
7073
const DESIGN_RECORD_VOCABULARY: ReadonlyArray<readonly [string, RegExp]> = [
@@ -139,16 +142,17 @@ function objectSchemas(...modules: Array<Record<string, unknown>>): Map<string,
139142
return byName;
140143
}
141144

142-
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr, 'pt-BR': ptBR } as const;
145+
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr, 'pt-BR': ptBR, ru } as const;
143146
const OBJECT_BUNDLES = {
144-
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects, ar: arObjects, fr: frObjects, 'pt-BR': ptBRObjects,
147+
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects, ar: arObjects, fr: frObjects, 'pt-BR': ptBRObjects, ru: ruObjects,
145148
} as const;
146149
const FORM_BUNDLES = {
147150
en: enMetadataForms, 'zh-CN': zhCNMetadataForms, 'ja-JP': jaJPMetadataForms, 'es-ES': esESMetadataForms,
148151
'zh-Hant': zhHantMetadataForms,
149152
ar: arMetadataForms,
150153
fr: frMetadataForms,
151154
'pt-BR': ptBRMetadataForms,
155+
ru: ruMetadataForms,
152156
} as const;
153157

154158
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
@@ -19,8 +19,9 @@ import { zhHant } from './translations/zh-Hant.js';
1919
import { ar } from './translations/ar.js';
2020
import { fr } from './translations/fr.js';
2121
import { ptBR } from './translations/pt-BR.js';
22+
import { ru } from './translations/ru.js';
2223

23-
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr, 'pt-BR': ptBR } as const;
24+
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr, 'pt-BR': ptBR, ru } as const;
2425

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

‎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
@@ -114,9 +114,10 @@ describe('(2) the User page lists each per-user store, keyed to its record (#238
114114
ar: 'التفضيلات',
115115
fr: 'Préférences',
116116
'pt-BR': 'Preferências',
117+
ru: 'Предпочтения',
117118
});
118119
expect((tabOf('sys_account')?.label as Rec)?.en).toBe('Linked Accounts');
119-
expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['ar', 'en', 'es-ES', 'fr', 'ja-JP', 'pt-BR', 'zh-CN', 'zh-Hant']);
120+
expect(Object.keys((tabOf('sys_account')?.label as Rec) ?? {}).sort()).toEqual(['ar', 'en', 'es-ES', 'fr', 'ja-JP', 'pt-BR', 'ru', 'zh-CN', 'zh-Hant']);
120121
});
121122
});
122123

‎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
@@ -62,8 +62,9 @@ import { zhHant } from './zh-Hant.js';
6262
import { ar } from './ar.js';
6363
import { fr } from './fr.js';
6464
import { ptBR } from './pt-BR.js';
65+
import { ru } from './ru.js';
6566

66-
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr, 'pt-BR': ptBR } as const;
67+
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr, 'pt-BR': ptBR, ru } as const;
6768

6869
/** Every statically declared nav item of an app, depth-first, with its label. */
6970
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
@@ -18,6 +18,7 @@ import { zhHantObjects } from './zh-Hant.objects.generated.js';
1818
import { arObjects } from './ar.objects.generated.js';
1919
import { frObjects } from './fr.objects.generated.js';
2020
import { ptBRObjects } from './pt-BR.objects.generated.js';
21+
import { ruObjects } from './ru.objects.generated.js';
2122

2223
const LOCALES = [
2324
{ name: 'en', objs: enObjects as Record<string, any>, old: /\bDepartment\b/ },
@@ -28,6 +29,7 @@ const LOCALES = [
2829
{ name: 'ar', objs: arObjects as Record<string, any>, old: /قسم/ },
2930
{ name: 'fr', objs: frObjects as Record<string, any>, old: /Département/ },
3031
{ name: 'pt-BR', objs: ptBRObjects as Record<string, any>, old: /Departamento/ },
32+
{ name: 'ru', objs: ruObjects as Record<string, any>, old: /Отдел/ },
3133
];
3234
const OBJECTS = ['sys_business_unit', 'sys_business_unit_member'];
3335

‎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
@@ -29,6 +29,7 @@ import { zhHantObjects } from './zh-Hant.objects.generated.js';
2929
import { arObjects } from './ar.objects.generated.js';
3030
import { frObjects } from './fr.objects.generated.js';
3131
import { ptBRObjects } from './pt-BR.objects.generated.js';
32+
import { ruObjects } from './ru.objects.generated.js';
3233

3334
const LOCALES = [
3435
['zh-CN', zhCNObjects],
@@ -38,6 +39,7 @@ const LOCALES = [
3839
['ar', arObjects],
3940
['fr', frObjects],
4041
['pt-BR', ptBRObjects],
42+
['ru', ruObjects],
4143
] as const;
4244

4345
/**

0 commit comments

Comments
 (0)