Skip to content

Commit 6e5f909

Browse files
feat(ui): the translation preview strings move into the studio i18n namespace (#25922 slice S6b) (#27083)
<!-- claude-projects-attribution: {"github_login":"os-warren"} --> _Requested by **Warren** · [project thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8ScBVj68xfGbS6qVYg8VwB1)_ Part of #25922 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:app-shell 设计器私有表里最后一族 `engine.translationPreview.*`(en、zh 各 23 行,含 #26831 加的 `category.hooks`)搬进 `@object-ui/i18n` 的 `studio` 命名空间,文案一字未改;`ENGINE_STRINGS_EN` / `ENGINE_STRINGS_ZH` 现在是空对象,声明保留到 S7。 - 为什么改:执行 #25922 裁决 6431032267(Q2 = A,6431234244 维持),这是 (M) 的 S6b 片,派单 6460180836;之后只剩 S7(Tier H:删表、退役 `check:i18n-designer-parity` 及其 `ci.yml` 步骤)。 - 风险与回滚:与前几片相同,zh 用户首次打开翻译预览时标签先显示英文、zh 分片到达后同一挂载重渲染为中文;回滚即 revert 本 PR。 - 先看:`ui/packages/i18n/src/namespaces/studio/zh.ts` 末尾的 S6b 段,再看 `block-config-i18n.test.ts` 里改成「两张表为空」的断言。 - 两分钟验证:`cd ui && pnpm run check:i18n-designer-parity && pnpm exec vitest run packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts`,应全绿。 - 需要你做的:无,席位落地。 ## What changes - **Slice S6b of the (M) item.** The last family in `ENGINE_STRINGS_EN` / `ENGINE_STRINGS_ZH` (`ui/packages/app-shell/src/views/metadata-admin/i18n.ts`), `engine.translationPreview.*`, is appended to `STUDIO_EN` / `STUDIO_ZH` in `ui/packages/i18n/src/namespaces/studio/{en,zh}.ts` under an S6b heading comment, text byte-for-byte unchanged, its group comment moving with it. That is 23 rows in each language, not the 22 the order counted: #26831 (merged before this branch) added `engine.translationPreview.category.hooks`, and this PR carries it. - **The table is now empty.** Both `ENGINE_STRINGS_*` declarations stay, as the order requires, each holding a two-line comment that says where the rows live and that slice S7 retires the table. `t()` / `tOptional()`, `lookupString`, `TYPE_LABELS` / `DOMAIN_LABELS`, `FLOW_FIELD_ZH` and `check:i18n-designer-parity` are untouched. - **No runtime change.** `lookupString` asks the empty table first and the namespace second, so `TranslationPreview.tsx` and its tests read the same strings with no edit. The preview's locale comes from its host's `useMetadataLocale()`, which already subscribes to the lazy `zh` catalogue. - **One forced test adaptation.** `previews/__tests__/block-config-i18n.test.ts`, case "reads two disjoint, non-empty tables out of i18n.ts", floored each table at non-empty. It now asserts both tables are empty (a key moved back would shadow the namespace) and keeps its range-disjointness check and its 3,000-key floor over both homes, which is what keeps an empty table from reading as a parser gone quiet. The case name says "both emptied into the namespace". - **Changeset:** `.changeset/25922-studio-i18n-translation-preview.md`, `@object-ui/i18n` patch. - ⛔ Untouched: `ui/packages/i18n/src/locales/*.ts`, `ui/.github/**`, root `ci.yml`, `ui/scripts/check-i18n-designer-table-parity.mjs`. ## Why Ruling 6431032267 Q2 = A (upheld by 6431234244): the designer table becomes one lazy `@object-ui/i18n` namespace, moved in slices. This family was held out of S6 while #26831 was open. ## Tests - **Pin widened:** `inspectors/inspectorStrings.studioNamespace-25922.test.ts` gains a floor of 20 for `engine.translationPreview.` (87 families in all). Every key in the family must be in `zh` and resolve through `t()` to the namespace value in `en-US`, `zh-CN` and `de-DE`. - **Move proof (one-off, not committed):** both homes parsed by AST at `f708cf22` and at this head. In each language: table 23 → 0, namespace 3,186 → 3,209 (`en`) and 3,240 → 3,263 (`zh`); removed set equals added set, 0 values differing, 0 keys in both homes, 0 existing namespace rows dropped or changed. - **Equivalence (one-off, not committed):** all 23 base rows per language, read by AST at `f708cf22`, compared with `t()`, `tOptional()` and `tFormat()` on this branch with `zh` resident, in `en-US`, `en`, `de-DE`, `fr`, no locale, `zh-CN`, `zh` and `zh-TW`: 552 lookups, 0 differing. - **Ablations** (`scripts/ablation-replace.mjs`; mutation landed by anchor count and blob, restored to the `HEAD` blob with `git diff HEAD` empty): - A `zh` row `engine.translationPreview.coverage` planted back in `ENGINE_STRINGS_ZH` with another value: the pin's `t()` case for `engine.translationPreview.` goes red, and the equivalence check reports exactly 9 differing lookups (`t` / `tOptional` / `tFormat` × `zh-CN`, `zh`, `zh-TW`). - The same row deleted from `studio/zh.ts`: the pin's family case and `t()` case go red, and the equivalence check reports the same 9. ### The sweep The `zh` loader in `namespaces/studio/index.ts` was delayed by 4,000 ms (`ablation-replace.mjs --hold`, restored to the `HEAD` blob). Under the delay, every app-shell test naming `zh` or a moved key plus the i18n tests naming `studio` ran (310 files): 3,617 of 3,618 tests passed. The one red is `i18n/src/__tests__/studio-namespace-lazy.test.tsx`, red by construction (its `findByText` waits 1 s against a 4 s delay). No racer. Then each `.test.ts` file that names a moved key or reads the table (6 files: the pin, `block-config-i18n`, `zh-base-term.i18n-13131`, and the dead-keys, designer-parity and en-drift script tests) ran alone, delayed: all green. ## Acceptance notes - **The parity gate on an empty table:** `check:i18n-designer-parity` exits 0 and reports `ENGINE_STRINGS 3209 en / 3263 zh`, the same totals as on `f708cf22`, because its extractor reads the table and the namespace as one table. The AST extractor accepts an empty object literal; the line-range extractor in `block-config-i18n` and the `indexOf` reader in `zh-base-term.i18n-13131` both need the closing `};` on its own line, which is why the empty declarations keep the multi-line form. - **Other open PRs:** none known to add a table row after `f708cf22` (claim 6460205408). - **Eager bundle:** not re-measured locally, as in S2–S6. CI's `Build & E2E UI` measures it. ## Verification At `13e08033`. `origin/main` is 3 commits ahead (#27034, #27031, #27066); none touches either home, the namespace or an i18n gate, and `git merge-tree` is clean. <details><summary>Tests and gates</summary> - `pnpm --filter @object-ui/i18n --filter @object-ui/app-shell run type-check`: exit 0. `pnpm --dir ui run type-check:scripts`: exit 0. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 34 commands, each exit 0 (captured before any pipe), including `pnpm --dir ui exec vitest run scripts/` (`Test Files 176 passed | 2 skipped (178)`). `--ran`: 34 derived, 34 run, 0 NOT-MEASURED. - ui `check:i18n-designer-parity`, `check:i18n-dead-keys`, `check:i18n-drift` ("No en value changed in this range."), `check:i18n-keys`: exit 0. `check:new-line-citations`: exit 0 (0 new). `node scripts/check-nul-bytes.mjs`: exit 0. - Focused set at normal speed (pin, the three `TranslationPreview` readers, `zh-base-term`, `block-config-i18n`, the parity and dead-keys script tests, `studio-namespace`, equivalence): `Tests 451 passed (451)`. - Changed lines: 134 (+78 / −56) over 6 files. </details> ## Human Minimum - [x] 1. No `packages/spec` change. - [x] 2. No new names; the only new file is the changeset. - [x] 3. No formatter ran; `pnpm lint` is CI's. - [x] 4. One card slice, one branch off `main`, one pull request. - [x] 5. Patch changeset with both declaration lines. - [x] 6. n/a. - [x] 7. No hand edits to generated or release-owned files. - [x] 8. English. --- _Generated by [Claude Code](https://claude.ai/code/session_01JQwxbX3S5QrD29Ghii633f)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f2fdac5 commit 6e5f909

6 files changed

Lines changed: 78 additions & 56 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/i18n': patch
3+
---
4+
5+
feat(i18n): the translation preview's strings move into the `studio` namespace
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Last rows of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). The 23 `engine.translationPreview.*` rows in each language, the last family left in `@object-ui/app-shell`'s private designer table, now live in `@object-ui/i18n/namespaces/studio`, text unchanged. The table is now empty; deleting it is a later slice. Studio reads the same strings; a Chinese viewer sees the translation preview's labels in English for the moment the `zh` catalogue takes to load, as with the earlier slices. No export changes.

‎ui/packages/app-shell/src/views/metadata-admin/i18n.ts‎

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -192,57 +192,13 @@ const DOMAIN_LABELS_ZH: Record<string, string> = {
192192
};
193193

194194
const ENGINE_STRINGS_EN: Record<string, string> = {
195-
'engine.translationPreview.empty':
196-
'This bundle is empty — add at least one translated string to see the coverage report.',
197-
// One heading per group of the per-app `TranslationDataSchema`, in its order (objectui#11765).
198-
'engine.translationPreview.category.objects': 'Objects',
199-
'engine.translationPreview.category.picklists': 'Picklists',
200-
'engine.translationPreview.category.apps': 'Apps',
201-
'engine.translationPreview.category.messages': 'Messages',
202-
'engine.translationPreview.category.globalActions': 'Global Actions',
203-
'engine.translationPreview.category.dashboards': 'Dashboards',
204-
'engine.translationPreview.category.datasets': 'Datasets',
205-
'engine.translationPreview.category.reports': 'Reports',
206-
'engine.translationPreview.category.pages': 'Pages',
207-
'engine.translationPreview.category.flows': 'Flows',
208-
'engine.translationPreview.category.hooks': 'Hooks',
209-
'engine.translationPreview.category.positions': 'Positions',
210-
'engine.translationPreview.category.permissions': 'Permission Sets',
211-
'engine.translationPreview.category.books': 'Doc Books',
212-
'engine.translationPreview.category.metadataForms': 'Metadata Forms',
213-
'engine.translationPreview.category.settingsCommon': 'Settings UI',
214-
'engine.translationPreview.coverage': 'Category coverage:',
215-
'engine.translationPreview.totalKeys': '{count} total keys',
216-
'engine.translationPreview.categoryEmpty': 'empty',
217-
'engine.translationPreview.more': '+{count} more…',
218-
'engine.translationPreview.keyCountOne': '{{count} key}',
219-
'engine.translationPreview.keyCountOther': '{{count} keys}',
195+
// Empty since #25922 slice S6b: every row lives in `@object-ui/i18n`'s
196+
// `studio` namespace. Kept until slice S7 retires the table.
220197
};
221198

222199
const ENGINE_STRINGS_ZH: Record<string, string> = {
223-
'engine.translationPreview.empty': '此语言包为空 —— 至少添加一条译文,即可查看覆盖率报告。',
224-
'engine.translationPreview.category.objects': '对象',
225-
'engine.translationPreview.category.picklists': '选项列表',
226-
'engine.translationPreview.category.apps': '应用',
227-
'engine.translationPreview.category.messages': '消息',
228-
'engine.translationPreview.category.globalActions': '全局操作',
229-
'engine.translationPreview.category.dashboards': '仪表盘',
230-
'engine.translationPreview.category.datasets': '数据集',
231-
'engine.translationPreview.category.reports': '报表',
232-
'engine.translationPreview.category.pages': '页面',
233-
'engine.translationPreview.category.flows': '流程',
234-
'engine.translationPreview.category.hooks': '钩子',
235-
'engine.translationPreview.category.positions': '岗位',
236-
'engine.translationPreview.category.permissions': '权限集',
237-
'engine.translationPreview.category.books': '文档书',
238-
'engine.translationPreview.category.metadataForms': '元数据表单',
239-
'engine.translationPreview.category.settingsCommon': '设置界面',
240-
'engine.translationPreview.coverage': '分类覆盖率:',
241-
'engine.translationPreview.totalKeys': '共 {count} 个键',
242-
'engine.translationPreview.categoryEmpty': '空',
243-
'engine.translationPreview.more': '另有 {count} 项…',
244-
'engine.translationPreview.keyCountOne': '{{count} 个键}',
245-
'engine.translationPreview.keyCountOther': '{{count} 个键}',
200+
// Empty since #25922 slice S6b: every row lives in `@object-ui/i18n`'s
201+
// `studio` namespace. Kept until slice S7 retires the table.
246202
};
247203

248204
/** Whether a locale resolves to Chinese (the only non-English catalog). */

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* objectstack#25922 slices S2 to S6 — the designer rows that moved into
4+
* objectstack#25922 slices S2 to S6b — the designer rows that moved into
55
* `@object-ui/i18n`'s `studio` namespace (every `engine.inspector.*` row;
66
* `engine.packages.*`, `engine.form.*`, `engine.edit.*`; all of
77
* `engine.studio.*`; the data source pages, the field types, the metadata
8-
* preview panes, the flow designer and the rest of the Studio chrome), and
8+
* preview panes, the flow designer, the rest of the Studio chrome and, last,
9+
* the translation preview), and
910
* app-shell's `t()` / `tOptional()` still answer every one of them the way they
1011
* did when the rows sat in its own table.
1112
*
@@ -124,6 +125,8 @@ const FAMILY_FLOOR: Readonly<Record<string, number>> = Object.freeze({
124125
'engine.home.': 1,
125126
'engine.cancel': 1,
126127
'engine.close': 1,
128+
// S6b — the translation preview, the table's last family
129+
'engine.translationPreview.': 20,
127130
});
128131

129132
/**

‎ui/packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config-i18n.test.ts‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -673,17 +673,18 @@ function pageBlockKeys(table: EngineStringTable): string[] {
673673
}
674674

675675
describe('a retired key leaves BOTH locale tables (objectui#8368)', () => {
676-
it('reads two disjoint, non-empty tables out of i18n.ts', () => {
676+
it('reads two disjoint tables out of i18n.ts, both emptied into the namespace', () => {
677677
// The extractor is a source parser, so its silent failure mode is finding
678678
// FEWER keys — which would make every assertion below vacuously green over
679679
// an empty corpus. Ranges first: overlapping ones would mean the `};`
680680
// scan ran past a table and both sets became the same set.
681-
// The table drains slice by slice into the namespace (objectstack#25922),
682-
// so its floor is "non-empty"; the 3,000-key floor is held over both homes
683-
// together, below.
681+
// The table drained slice by slice into the namespace (objectstack#25922)
682+
// and is empty since slice S6b: a key back in it would shadow the
683+
// namespace's copy. The 3,000-key floor, held over both homes together
684+
// below, is what keeps an empty table from reading as a parser gone quiet.
684685
expect(EN_TABLE.to).toBeLessThan(ZH_TABLE.from);
685-
expect(EN_TABLE.keys.length).toBeGreaterThan(0);
686-
expect(ZH_TABLE.keys.length).toBeGreaterThan(0);
686+
expect(EN_TABLE.keys).toEqual([]);
687+
expect(ZH_TABLE.keys).toEqual([]);
687688
// No duplicates: a key declared twice would be a silent overwrite, and the
688689
// set arithmetic below would hide it.
689690
expect(EN_TABLE.keys.length).toBe(new Set(EN_TABLE.keys).size);

‎ui/packages/i18n/src/namespaces/studio/en.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3953,6 +3953,33 @@ const STUDIO_EN: Record<string, string> = {
39533953
'engine.appNav.item': 'Item {n}',
39543954
'engine.appNav.itemOne': 'item',
39553955
'engine.appNav.itemOther': 'items',
3956+
// The translation preview, the designer table's last family: held out of S6
3957+
// while a sibling change added its category.hooks row (#25922 slice S6b).
3958+
'engine.translationPreview.empty':
3959+
'This bundle is empty — add at least one translated string to see the coverage report.',
3960+
// One heading per group of the per-app `TranslationDataSchema`, in its order (objectui#11765).
3961+
'engine.translationPreview.category.objects': 'Objects',
3962+
'engine.translationPreview.category.picklists': 'Picklists',
3963+
'engine.translationPreview.category.apps': 'Apps',
3964+
'engine.translationPreview.category.messages': 'Messages',
3965+
'engine.translationPreview.category.globalActions': 'Global Actions',
3966+
'engine.translationPreview.category.dashboards': 'Dashboards',
3967+
'engine.translationPreview.category.datasets': 'Datasets',
3968+
'engine.translationPreview.category.reports': 'Reports',
3969+
'engine.translationPreview.category.pages': 'Pages',
3970+
'engine.translationPreview.category.flows': 'Flows',
3971+
'engine.translationPreview.category.hooks': 'Hooks',
3972+
'engine.translationPreview.category.positions': 'Positions',
3973+
'engine.translationPreview.category.permissions': 'Permission Sets',
3974+
'engine.translationPreview.category.books': 'Doc Books',
3975+
'engine.translationPreview.category.metadataForms': 'Metadata Forms',
3976+
'engine.translationPreview.category.settingsCommon': 'Settings UI',
3977+
'engine.translationPreview.coverage': 'Category coverage:',
3978+
'engine.translationPreview.totalKeys': '{count} total keys',
3979+
'engine.translationPreview.categoryEmpty': 'empty',
3980+
'engine.translationPreview.more': '+{count} more…',
3981+
'engine.translationPreview.keyCountOne': '{{count} key}',
3982+
'engine.translationPreview.keyCountOther': '{{count} keys}',
39563983
};
39573984

39583985
export default STUDIO_EN;

‎ui/packages/i18n/src/namespaces/studio/zh.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3565,6 +3565,31 @@ const STUDIO_ZH: Record<string, string> = {
35653565
'engine.appNav.item': '导航项 {n}',
35663566
'engine.appNav.itemOne': '项',
35673567
'engine.appNav.itemOther': '项',
3568+
// The translation preview, the designer table's last family: held out of S6
3569+
// while a sibling change added its category.hooks row (#25922 slice S6b).
3570+
'engine.translationPreview.empty': '此语言包为空 —— 至少添加一条译文,即可查看覆盖率报告。',
3571+
'engine.translationPreview.category.objects': '对象',
3572+
'engine.translationPreview.category.picklists': '选项列表',
3573+
'engine.translationPreview.category.apps': '应用',
3574+
'engine.translationPreview.category.messages': '消息',
3575+
'engine.translationPreview.category.globalActions': '全局操作',
3576+
'engine.translationPreview.category.dashboards': '仪表盘',
3577+
'engine.translationPreview.category.datasets': '数据集',
3578+
'engine.translationPreview.category.reports': '报表',
3579+
'engine.translationPreview.category.pages': '页面',
3580+
'engine.translationPreview.category.flows': '流程',
3581+
'engine.translationPreview.category.hooks': '钩子',
3582+
'engine.translationPreview.category.positions': '岗位',
3583+
'engine.translationPreview.category.permissions': '权限集',
3584+
'engine.translationPreview.category.books': '文档书',
3585+
'engine.translationPreview.category.metadataForms': '元数据表单',
3586+
'engine.translationPreview.category.settingsCommon': '设置界面',
3587+
'engine.translationPreview.coverage': '分类覆盖率:',
3588+
'engine.translationPreview.totalKeys': '共 {count} 个键',
3589+
'engine.translationPreview.categoryEmpty': '空',
3590+
'engine.translationPreview.more': '另有 {count} 项…',
3591+
'engine.translationPreview.keyCountOne': '{{count} 个键}',
3592+
'engine.translationPreview.keyCountOther': '{{count} 个键}',
35683593
};
35693594

35703595
export default STUDIO_ZH;

0 commit comments

Comments
 (0)