Skip to content

Commit deac7f4

Browse files
i18n(platform-objects): ship the platform objects, Setup, Studio and Account in Brazilian Portuguese (#31440)
domain:server#3 · os-project-manager · session_01FEkQ5pXkgNXVpCn2GZDjyy Body: seat final 速读 · 2026-11-08T08:25Z <!-- 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 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:给 `@objectstack/platform-objects` 加上巴西葡萄牙语(pt-BR):平台对象、Studio 的元数据表单、Setup/Studio/Account 导航、系统概览仪表板和用户/组织记录页的内嵌文字,做法与已合并的 fr(#31233)逐文件一致。 - 为什么改:pt-BR 是 #30512 要补的第四个语言;语言设为 pt-BR 的用户此前在主页动态里读到葡语句子夹着英文字段名(`Banned`、`Ban Reason`)。 - 风险与回滚:只新增 pt-BR 文件、注册行和测试里的语言列表,不改任何已有语言的文字(其余 21 个生成文件字节不变);新文字同样走 source-hash 回退。回滚即 revert 本 PR。 - 先看:`packages/platform-objects/src/apps/translations/pt-BR.ts`(导航钉住项),`packages/plugins/plugin-audit/src/activity-summary-pt-br-sys-user-labels.test.ts`(新守护测试),`pt-BR.metadata-forms.generated.ts` 里 `widgets.layout` 带上父名的译法(`Layout do widget`)。 - 两分钟验证:`cd packages/plugins/plugin-audit && npx vitest run src/activity-summary-pt-br-sys-user-labels.test.ts`,预期 3 个测试通过,句子为「Atualizou Usuário "Carl Edited": Banido e Motivo do banimento」。 - 需要你做的:请 os-zhuang 或 hotlong 批准本 PR。席位契约复审 ACCEPT 在评论 6719237445,`Head-sha: 1d1308b`。PR 含生成文件,超过 3000 行,需人工批准。批准后由席位重合 main、重跑导航 i18n 检查,然后落地。 ## What changes - New `pt-BR` bundles in `packages/platform-objects/src/apps/translations`: `pt-BR.objects.generated.ts` (1697 leaves), `pt-BR.metadata-forms.generated.ts` (1263 leaves), their provenance table `pt-BR.source-hashes.generated.ts` (88 deliberate byte copies of `en`, listed under Acceptance notes), the hand-authored `pt-BR.ts` (141 leaves) and `pt-BR.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` (`Sistema`). - Wording pins, as #31233 lists them: `nav_account_approvals` reads as plugin-approvals' `my_pending` label (`Minhas aprovações pendentes`), `nav_assignment_rules` as `sys_assignment_rule`'s plural label (`Regras de atribuição`), each `nav_settings_*` (including `nav_settings_access_policy`, `Política de acesso`) as its service-settings hub title, `nav_settings_hub` as the console's `backToHub` (`Todas as configurações`), and the four #30935 keys: `grp_account_ai_assistant` = `Assistente de IA`; both `nav_connect_agent` entries and `pages.connect_agent.label` = `Conectar um assistente de IA`. - Changeset `.changeset/30512-platform-objects-pt-br.md` (patch). ## Tests - `pt-BR` added beside `fr` to the locale lists of every test #31233 widened (vocabulary, nav parity, dead-key tombstone, source-hash, bu-rename, 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`. No test listing the platform locales was added on main since #31233. The cli `platform-page-i18n-parity` LOCALE_KEY shape already accepts `pt-BR`. - New `packages/plugins/plugin-audit/src/activity-summary-pt-br-sys-user-labels.test.ts`, the `pt-BR` twin of the #28107 `fr` test: an i18n service holding what a booted kernel holds composes a `sys_user` update summary for a reader whose locale is `pt-BR`, and gets `Atualizou Usuário "Carl Edited": Banido e Motivo do banimento`; a tracked change names `Motivo do banimento`. Ablation: with the platform `pt-BR` bundle left out of the service, two cases fail with `Atualizou User "Carl Edited": Banned e Ban Reason` and `Ban Reason: spam → abuse`, the English labels inside Portuguese glue; restored, 3 of 3 pass. ## Acceptance notes - Every leaf is translated. The 88 byte copies of `en` the extract records are words Brazilian Portuguese uses as they are (`Status` ×8, `Namespace` ×6, `Token` ×3, `Kanban`, `Gantt`, `Layout`, `Widget`, `Widgets`, `Hook`, `Handler`, `Patch`, `Tags`, `Avatar`, `Slug`, `Global`, `Manual`, `App`, `Web`, `Feeds`, `Shards`), identifiers and header names (`ID` ×24, `API` ×2, `JWKS`, `JWKS URI`, `Message-ID`, `Reply-To` ×2, `Cc`, `snake_case`, the SAML `urn:…:emailAddress` NameID format, the `openid email profile` / `email` / `name` defaults), lowercase option values that are identifiers (`marketing`, `workflow`, `cron`, `dlq`, `manual`, `api`, `web`), and the five JSON examples kept verbatim as every locale keeps them. - Where a catalog test refuses an `en` echo (`repeater-row-properties` on `dashboard:widgets.layout`), the leaf names its parent instead (`Layout do widget`), as `fr` and `es-ES` do. - Vocabulary follows `ui/packages/i18n` `pt-BR`: straight quotes, `Excluir`, `Salvar`, `Painel`, `Visualização`, `Função`, `Proprietário`, `Locatário`; Brazilian forms throughout (`você`, `arquivo`, `usuário`, `equipe`). The job position is `Cargo`; the add-record button's `Position` is `Posição` (the place). Adjectives agree with the noun they qualify (`Todos`/`Todas`, `Ativos`/`Ativas`, `Revogada`, `Desativados`, …). - Business units are `Unidades de negócios`; no `sys_business_unit*` label says `Departamento` (`bu-rename-consistency`). - `pages.connect_agent.subtitle` is translated from the current `en` text, so its digest in `pt-BR.source-hashes.ts` is current. The `fr` table records the digest of the previous `en` subtitle, so `fr` readers get the `en` subtitle through the fallback today; that is existing `fr` behaviour, left alone here. ## 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 --fill=default --source-hashes --out=packages/platform-objects/src/apps/translations`: regenerates the `pt-BR` bundles byte-identical and leaves the 21 other generated files unchanged (sha256 over all 23, before and after), both before and after the merge of `origin/main`. `check-i18n-bundles --filter=platform-objects`: in sync (23 bundles). - `vitest run` in platform-objects: 120 files, 1796 tests passed. plugin-approvals `approval-status-vocabulary` (6), plugin-security `people-org-assignment-nav-contribution` (5), service-settings `setup-nav-hub-parity` (17), cli `platform-page-i18n-parity` (27) and the new plugin-audit test (3) passed. - platform-objects `typecheck`: exit 0. - `dispatch-gates --ran` at 1d1308b (after the second merge of `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 touched packages' changed paths (`pnpm lint` spelling, scoped): 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 4941c40 commit deac7f4

37 files changed

Lines changed: 7386 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 Brazilian Portuguese
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`@objectstack/platform-objects` now carries `pt-BR` beside `en`, `zh-CN`, `ja-JP`, `es-ES`, `zh-Hant`, `ar` and `fr`: 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 `pt-BR` no longer reads English `sys_user` field labels inside Portuguese sentences. 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 `pt-BR` in its `--locales`, so `check:i18n` now gates the new bundles. Package 11 of #30512, fourth of seven locales; `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,fr \
11+
* --locales=zh-CN,ja-JP,es-ES,zh-Hant,ar,fr,pt-BR \
1212
* --fill=default \
1313
* --source-hashes \
1414
* --out=packages/platform-objects/src/apps/translations
@@ -224,13 +224,15 @@ 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';
226226
import { frObjects } from '../src/apps/translations/fr.objects.generated.js';
227+
import { ptBRObjects } from '../src/apps/translations/pt-BR.objects.generated.js';
227228
import { enMetadataForms } from '../src/apps/translations/en.metadata-forms.generated.js';
228229
import { zhCNMetadataForms } from '../src/apps/translations/zh-CN.metadata-forms.generated.js';
229230
import { jaJPMetadataForms } from '../src/apps/translations/ja-JP.metadata-forms.generated.js';
230231
import { esESMetadataForms } from '../src/apps/translations/es-ES.metadata-forms.generated.js';
231232
import { zhHantMetadataForms } from '../src/apps/translations/zh-Hant.metadata-forms.generated.js';
232233
import { arMetadataForms } from '../src/apps/translations/ar.metadata-forms.generated.js';
233234
import { frMetadataForms } from '../src/apps/translations/fr.metadata-forms.generated.js';
235+
import { ptBRMetadataForms } from '../src/apps/translations/pt-BR.metadata-forms.generated.js';
234236

235237
// ── Existing hand-authored app/dashboard/page translations ────────────────
236238
// The per-locale assemblers, NOT the `SetupAppTranslations` wrapper. Each one
@@ -256,6 +258,7 @@ import { esES } from '../src/apps/translations/es-ES.js';
256258
import { zhHant } from '../src/apps/translations/zh-Hant.js';
257259
import { ar } from '../src/apps/translations/ar.js';
258260
import { fr } from '../src/apps/translations/fr.js';
261+
import { ptBR } from '../src/apps/translations/pt-BR.js';
259262

260263
/**
261264
* The annotation is load-bearing, not decoration (#10868). `defineStack`
@@ -359,6 +362,7 @@ const config: ObjectStackDefinition = defineStack({
359362
{ 'zh-Hant': { ...zhHant, objects: zhHantObjects, metadataForms: zhHantMetadataForms } },
360363
{ 'ar': { ...ar, objects: arObjects, metadataForms: arMetadataForms } },
361364
{ 'fr': { ...fr, objects: frObjects, metadataForms: frMetadataForms } },
365+
{ 'pt-BR': { ...ptBR, objects: ptBRObjects, metadataForms: ptBRMetadataForms } },
362366
],
363367
});
364368

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

‎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' },
60+
nullLabel: { en: 'System', 'zh-CN': '系统', 'ja-JP': 'システム', 'es-ES': 'Sistema', 'zh-Hant': '系統', ar: 'النظام', fr: 'Système', 'pt-BR': 'Sistema' },
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
@@ -48,20 +48,23 @@ import { esES } from './translations/es-ES.js';
4848
import { zhHant } from './translations/zh-Hant.js';
4949
import { ar } from './translations/ar.js';
5050
import { fr } from './translations/fr.js';
51+
import { ptBR } from './translations/pt-BR.js';
5152
import { enObjects } from './translations/en.objects.generated.js';
5253
import { zhCNObjects } from './translations/zh-CN.objects.generated.js';
5354
import { jaJPObjects } from './translations/ja-JP.objects.generated.js';
5455
import { esESObjects } from './translations/es-ES.objects.generated.js';
5556
import { zhHantObjects } from './translations/zh-Hant.objects.generated.js';
5657
import { arObjects } from './translations/ar.objects.generated.js';
5758
import { frObjects } from './translations/fr.objects.generated.js';
59+
import { ptBRObjects } from './translations/pt-BR.objects.generated.js';
5860
import { enMetadataForms } from './translations/en.metadata-forms.generated.js';
5961
import { zhCNMetadataForms } from './translations/zh-CN.metadata-forms.generated.js';
6062
import { jaJPMetadataForms } from './translations/ja-JP.metadata-forms.generated.js';
6163
import { esESMetadataForms } from './translations/es-ES.metadata-forms.generated.js';
6264
import { zhHantMetadataForms } from './translations/zh-Hant.metadata-forms.generated.js';
6365
import { arMetadataForms } from './translations/ar.metadata-forms.generated.js';
6466
import { frMetadataForms } from './translations/fr.metadata-forms.generated.js';
67+
import { ptBRMetadataForms } from './translations/pt-BR.metadata-forms.generated.js';
6568

6669
/** The design-record vocabulary an admin-facing string must not carry. */
6770
const DESIGN_RECORD_VOCABULARY: ReadonlyArray<readonly [string, RegExp]> = [
@@ -136,15 +139,16 @@ function objectSchemas(...modules: Array<Record<string, unknown>>): Map<string,
136139
return byName;
137140
}
138141

139-
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr } as const;
142+
const LOCALES = { en, 'zh-CN': zhCN, 'ja-JP': jaJP, 'es-ES': esES, 'zh-Hant': zhHant, ar, fr, 'pt-BR': ptBR } as const;
140143
const OBJECT_BUNDLES = {
141-
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects, ar: arObjects, fr: frObjects,
144+
en: enObjects, 'zh-CN': zhCNObjects, 'ja-JP': jaJPObjects, 'es-ES': esESObjects, 'zh-Hant': zhHantObjects, ar: arObjects, fr: frObjects, 'pt-BR': ptBRObjects,
142145
} as const;
143146
const FORM_BUNDLES = {
144147
en: enMetadataForms, 'zh-CN': zhCNMetadataForms, 'ja-JP': jaJPMetadataForms, 'es-ES': esESMetadataForms,
145148
'zh-Hant': zhHantMetadataForms,
146149
ar: arMetadataForms,
147150
fr: frMetadataForms,
151+
'pt-BR': ptBRMetadataForms,
148152
} as const;
149153

150154
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
@@ -18,8 +18,9 @@ import { esES } from './translations/es-ES.js';
1818
import { zhHant } from './translations/zh-Hant.js';
1919
import { ar } from './translations/ar.js';
2020
import { fr } from './translations/fr.js';
21+
import { ptBR } from './translations/pt-BR.js';
2122

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

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

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

‎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
@@ -61,8 +61,9 @@ import { esES } from './es-ES.js';
6161
import { zhHant } from './zh-Hant.js';
6262
import { ar } from './ar.js';
6363
import { fr } from './fr.js';
64+
import { ptBR } from './pt-BR.js';
6465

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

6768
/** Every statically declared nav item of an app, depth-first, with its label. */
6869
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
@@ -17,6 +17,7 @@ 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';
1919
import { frObjects } from './fr.objects.generated.js';
20+
import { ptBRObjects } from './pt-BR.objects.generated.js';
2021

2122
const LOCALES = [
2223
{ name: 'en', objs: enObjects as Record<string, any>, old: /\bDepartment\b/ },
@@ -26,6 +27,7 @@ const LOCALES = [
2627
{ name: 'zh-Hant', objs: zhHantObjects as Record<string, any>, old: /部門/ },
2728
{ name: 'ar', objs: arObjects as Record<string, any>, old: /قسم/ },
2829
{ name: 'fr', objs: frObjects as Record<string, any>, old: /Département/ },
30+
{ name: 'pt-BR', objs: ptBRObjects as Record<string, any>, old: /Departamento/ },
2931
];
3032
const OBJECTS = ['sys_business_unit', 'sys_business_unit_member'];
3133

‎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
@@ -28,6 +28,7 @@ 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';
3030
import { frObjects } from './fr.objects.generated.js';
31+
import { ptBRObjects } from './pt-BR.objects.generated.js';
3132

3233
const LOCALES = [
3334
['zh-CN', zhCNObjects],
@@ -36,6 +37,7 @@ const LOCALES = [
3637
['zh-Hant', zhHantObjects],
3738
['ar', arObjects],
3839
['fr', frObjects],
40+
['pt-BR', ptBRObjects],
3941
] as const;
4042

4143
/**

0 commit comments

Comments
 (0)