Skip to content

Commit 49b5059

Browse files
feat(ui): the rest of the Studio strings and the metadata preview panes move into the studio i18n namespace (#25922 slice S5) (#26942)
<!-- claude-projects-attribution: {"github_login":"os-warren"} --> _Requested by **Warren** · [project thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8PeP4DzppH3b1kDPCjihHVx)_ Part of #25922 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:Studio 设计器表里除 `engine.studio.inspector` 之外的全部 `engine.studio.*`(309 键,含上一片顺延的 rules),加上 19 个元数据预览面板家族(`engine.*Preview`、`engine.previewShell`,287 键),共 en、zh 各 596 键,从 app-shell 私有表搬进 `@object-ui/i18n` 的 `studio` 命名空间,文案一字未改,调用点不动。 - 为什么改:执行 #25922 裁决 6431032267(Q2 = A,6431234244 维持),这是 (M) 的第 5 片(S5),派单 6453707890。 - 风险与回滚:与前几片相同,zh 用户首次打开这些页面时标签先显示英文、zh 分片到达后同一挂载重渲染为中文;回滚即 revert 本 PR。 - 先看:`ui/packages/i18n/src/namespaces/studio/en.ts` 末尾的 S5 段,再看钉测试新增的家族下限与 `AddFieldPopover.studioNamespace-25922` 的改写。 - 两分钟验证:`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 S5 of the (M) item.** These rows leave `ENGINE_STRINGS_EN` / `ENGINE_STRINGS_ZH` in `ui/packages/app-shell/src/views/metadata-admin/i18n.ts` and are appended to `STUDIO_EN` / `STUDIO_ZH` in `ui/packages/i18n/src/namespaces/studio/{en,zh}.ts`, text byte-for-byte unchanged. Each family has the same keys in `en` and `zh`, and none is zh-only: | group | families (keys per language) | keys | |---|---|---| | the rest of `engine.studio.*` | `rules` 63, `interfaces` 33, `designer` 25, `landing` 17, `actions` 15, `owd` 15, `pkg` 13, `app` 12, `refusal` 12, `held` 11, `nav` 10, `draftMark` 10, `api` 10, `slot` 7, `formDnd` 7, `hooks` 6, `org` 5, `pillar` 4, `createItem` 2, `newAutoTrigger` 1, and 31 single keys (`engine.studio.publish`, `engine.studio.cancel`, …) | 309 | | the metadata preview panes | `actionPreview` 57, `permissionPreview` 37, `validationPreview` 36, `jobPreview` 20, `agentPreview` 19, `skillPreview` 16, `datasetPreview` 15, `toolPreview` 14, `picklistPreview` 13, `dashboardPreview` 9, `appPreview` 9, `emailTemplatePreview` 9, `pagePreview` 8, `screenPreview` 7, `positionPreview` 6, `reportPreview` 6, `viewPreview` 4, `objectPreview` 1, `previewShell` 1 | 287 | | **total** | | **596** | The `engine.studio.*` rows are the order's scope. Only `engine.studio.inspector.*` stays, because open PR #26804 adds rows to it. For the other families I took the preview panes: 574 key lines against the order's "about 650". I left out `docPreview`, `bookPreview`, `flowPreview` and the page-block and translation previews: - `engine.pageBlockCanvas.*` and `engine.translationPreview.*` are excluded by the order (#26915 and #26831). - `flowPreview` belongs with the other flow families in S6. - Dropping `docPreview` and `bookPreview` keeps the PR at 2,922 changed lines, under the 3,000 threshold. The full preview set would have crossed it. I checked the patches of every open PR that touches either home (#26804, #26831, #26916): none touches a moved family. - The S2–S4 AST pass, with a heading comment marking the S5 section in each catalogue. - **No runtime change.** `lookupString` already asks the table first and the namespace second, so `t` / `tFormat` / `tOptional` and every call site are untouched. No new source file, so the console chunk groups need no change. - **Readers.** Every non-test reader of a moved family is under app-shell's `views/metadata-admin/` or `views/studio-design/`. None calls `t()` at module scope. Every dynamic key (`engine.studio.rules.event.${…}`, `engine.studio.pillar.${…}`, `engine.studio.if.kind.${…}`, `engine.studio.access.explain.*.${…}`) goes through `t()`. There is nothing to stop and report. - **Main merged once** to pick up #26915, which added `engine.pageBlockCanvas` rows to the namespace files. The merge was clean, and the i18n gates and the namespace tests were re-run after it. - **Changeset:** `.changeset/25922-studio-i18n-studio-previews.md`, `@object-ui/i18n` patch. - ⛔ Untouched: `ui/packages/i18n/src/locales/*.ts`, `ui/.github/**`, root `ci.yml`, `engine.studio.inspector.*`, `engine.pageBlockCanvas.*`, `engine.translationPreview.*`. ## Why Ruling 6431032267 Q2 = A (upheld by 6431234244): the designer table becomes one lazy `@object-ui/i18n` namespace, moved in slices. ## Tests - **Pin widened:** `inspectors/inspectorStrings.studioNamespace-25922.test.ts` gains floors for `engine.studio.` as a whole (600), for `rules`, `interfaces` and `designer`, and for each of the 19 preview families. Every key in each family must resolve through `t()` to the namespace value in `en-US`, `zh-CN` and `de-DE`. - **zh preloads (forced edits).** Each suite below reads a moved S5 zh row synchronously, so it now loads the `zh` catalogue first with a top-level `await loadStudioCatalogue('zh')`. No assertion changed. - 10 dom suites: - in `metadata-admin/previews/`: `AppPreview.areas-11027`, `DashboardPreview.i18nTitle-10847`, `ScreenPreview.fieldProjection-11190`, `automationPreviews.i18n-10862`, `previewStrings.i18n-10848` - in `metadata-admin/`: `studio-locale.i18n` - in `studio-design/`: `CreateItemDialog.notDerived-13389`, `ObjectFormDesigner.dndAnnouncements-11802`, `ObjectFormDesigner.roleDescription-11872`, `StudioDesignSurface.interfacePolish` - 2 unit suites: `studio-design/StudioDraftMark.test.ts` and `studio-design/metadataError.authorRefusal-11785.test.ts`. Both are in the `unit` project (`isolate: false`). Each passed in the full run only because an earlier file had already loaded `zh`, and failed when run alone. - **AddFieldPopover rewritten (forced edit).** `studio-design/AddFieldPopover.studioNamespace-25922.test.tsx` from slice 1 contrasted the namespace with "a key still in the app-shell table". No `engine.studio.*` key that this popover renders is left in the table. The test now checks the same zh flow (English first, then Chinese on the same mount) for the field labels, read from the namespace directly, and for the chrome key `engine.studio.cancel`, read through `t()`. ### The sweep I delayed the `zh` loader in `namespaces/studio/index.ts` by 4000 ms (`scripts/ablation-replace.mjs --hold`, restored byte-identical). Under that delay I ran every app-shell test that names zh (437 files), then each of the 54 unit-project files alone. - **Before the fixes:** 12 files failed in the full run and 2 failed when run alone. - One of the 12 is `i18n/src/__tests__/studio-namespace-lazy.test.tsx`. It fails by construction under the delay, because its `findByText` waits 1 s against a 4 s delay. - The other 13 are the suites listed above. - **All 13 come from S5 itself.** I checked out main's two homes, kept main's versions of the 13 test files, and ran them under the same delay. All 13 pass on main, so this sweep found no leftover from S1–S4. - **After the fixes:** - The 13 fixed files, the pin and the equivalence check pass with the delay and at normal speed (`Tests 211 passed (211)` both times). - The two unit suites pass alone, delayed and normal. - After the merge, the new main test `PageBlockCanvas.addListView-26794`, the pin and both `block-config` tests pass under the delay. ## Acceptance notes - **Every moved key resolves identically before and after.** This was a one-off measurement and is not committed. I read the 596 + 596 base values by AST from the table at `57820085`. I compared them with `t()` and `tOptional()` on this branch, with `zh` resident: the base `en` value in `en-US`, `en`, `de-DE` and `fr`, and the base `zh` value in `zh-CN`, `zh` and `zh-TW`. Result: 4,172 lookups, 0 differing. - **Ablations** (mutation landed by anchor count and blob, restored byte-identical to `HEAD`): - Removing the `?? studioString(key, locale)` arm from `lookupString` turns 46 tests red: the pin's `t()` case for all 44 families, its zh-only case, and the equivalence check. The equivalence check reports 4,172 of 4,172 lookups differing. - Altering one moved zh row (`engine.actionPreview.empty`) makes the equivalence check report exactly 3 differing lookups (`zh-CN`, `zh`, `zh-TW`). The pin stays green, as designed. - **Eager bundle:** not re-measured locally, as in S2–S4. CI's `Build & E2E UI` measures it. ## Verification At `1e3fed1d (the merge of main into the two slice commits)`. <details><summary>Tests and gates</summary> - `pnpm --filter @object-ui/i18n --filter @object-ui/app-shell run type-check`: VERDICT command-exit 0. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 34 commands, each exit 0, including `pnpm --dir ui exec vitest run scripts/` (`Test Files 178 passed | 2 skipped (180)`). `--ran`: 34 derived, 34 run, 0 NOT-MEASURED. - ui `check:i18n-designer-parity`, `check:i18n-dead-keys`, `check:i18n-drift` (0 `en` values changed) and `check:i18n-keys` all exit 0, before and after the main merge. `check:new-line-citations`: exit 0 (0 new citations). `node scripts/check-nul-bytes.mjs`: exit 0. - Changed lines: 2,922 (+1,520 / −1,402) over 18 files, under the 3,000 threshold. </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_01JQwxbX9LnJhjTQPJEmoBYi)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3217fc5 commit 49b5059

18 files changed

Lines changed: 1520 additions & 1402 deletions
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 rest of the Studio strings and the metadata preview panes move into the `studio` namespace
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Fifth slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). Every remaining `engine.studio.*` key except `engine.studio.inspector.*`, and the preview panes' `engine.*Preview.*` and `engine.previewShell.*` keys (the doc, book, flow, page-block and translation previews stay for a later slice), 596 keys in both `en` and `zh`, now live in `@object-ui/i18n/namespaces/studio` instead of `@object-ui/app-shell`'s private table, text unchanged. Studio reads the same strings; a Chinese viewer sees these 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: 0 additions & 1379 deletions
Large diffs are not rendered by default.

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

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

33
/**
4-
* objectstack#25922 slices S2 to S4 — the designer rows that moved into
4+
* objectstack#25922 slices S2 to S5 — the designer rows that moved into
55
* `@object-ui/i18n`'s `studio` namespace (every `engine.inspector.*` row;
6-
* `engine.packages.*`, `engine.form.*`, `engine.edit.*`; five `engine.studio.*`
7-
* pillars, the data source pages and the field types), and app-shell's `t()` /
8-
* `tOptional()` still answer every one of them the way they did when the rows
9-
* sat in its own table.
6+
* `engine.packages.*`, `engine.form.*`, `engine.edit.*`; `engine.studio.*` but
7+
* its inspector; the data source pages, the field types and the metadata
8+
* preview panes), and app-shell's `t()` / `tOptional()` still answer every one
9+
* of them the way they did when the rows sat in its own table.
1010
*
1111
* What would break it:
1212
* - a row copied back into the app-shell table with a different value — the
@@ -54,6 +54,30 @@ const FAMILY_FLOOR: Readonly<Record<string, number>> = Object.freeze({
5454
'engine.fieldType.': 45,
5555
'engine.fieldTypeDesc.': 45,
5656
'engine.fieldCategory.': 9,
57+
// S5 — the rest of engine.studio (its inspector aside) and the preview panes
58+
'engine.studio.': 600,
59+
'engine.studio.rules.': 55,
60+
'engine.studio.interfaces.': 30,
61+
'engine.studio.designer.': 20,
62+
'engine.actionPreview.': 50,
63+
'engine.permissionPreview.': 35,
64+
'engine.validationPreview.': 30,
65+
'engine.jobPreview.': 18,
66+
'engine.agentPreview.': 15,
67+
'engine.skillPreview.': 15,
68+
'engine.datasetPreview.': 12,
69+
'engine.toolPreview.': 12,
70+
'engine.picklistPreview.': 12,
71+
'engine.dashboardPreview.': 9,
72+
'engine.appPreview.': 9,
73+
'engine.emailTemplatePreview.': 9,
74+
'engine.pagePreview.': 8,
75+
'engine.screenPreview.': 7,
76+
'engine.positionPreview.': 6,
77+
'engine.reportPreview.': 6,
78+
'engine.viewPreview.': 4,
79+
'engine.objectPreview.': 1,
80+
'engine.previewShell.': 1,
5781
});
5882

5983
/**

‎ui/packages/app-shell/src/views/metadata-admin/previews/AppPreview.areas-11027.test.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ import { render, screen, cleanup } from '@testing-library/react';
2323
import { t } from '../i18n';
2424
import { AppPreview } from './AppPreview';
2525

26+
// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
27+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
28+
// Resident before any case, so the zh rows below read synchronously.
29+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
30+
await loadStudioCatalogue('zh');
31+
2632
afterEach(cleanup);
2733

2834
const NAV = [{ id: 'accounts', type: 'object', label: 'Accounts', objectName: 'account' }];

‎ui/packages/app-shell/src/views/metadata-admin/previews/DashboardPreview.i18nTitle-10847.test.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ vi.mock('@object-ui/react', async (importOriginal) => {
3838
import { DashboardPreview } from './DashboardPreview';
3939
import type { MetadataSelection } from '../preview-registry';
4040

41+
// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
42+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
43+
// Resident before any case, so the zh rows below read synchronously.
44+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
45+
await loadStudioCatalogue('zh');
46+
4147
afterEach(cleanup);
4248

4349
type Patch = Record<string, unknown>;

‎ui/packages/app-shell/src/views/metadata-admin/previews/ScreenPreview.fieldProjection-11190.test.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ import { ScreenPreview } from './ScreenPreview';
3232
import { buildScreenSpec } from './screen-spec';
3333
import { ScreenView, initialScreenValues, type ScreenFieldSpec, type ScreenSpec } from '../../ScreenView';
3434

35+
// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
36+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
37+
// Resident before any case, so the zh rows below read synchronously.
38+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
39+
await loadStudioCatalogue('zh');
40+
3541
afterEach(cleanup);
3642

3743
/**

‎ui/packages/app-shell/src/views/metadata-admin/previews/automationPreviews.i18n-10862.test.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ import { SkillPreview } from './SkillPreview';
4040
import { ToolPreview } from './ToolPreview';
4141
import { JobPreview } from './JobPreview';
4242

43+
// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
44+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
45+
// Resident before any case, so the zh rows below read synchronously.
46+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
47+
await loadStudioCatalogue('zh');
48+
4349
afterEach(() => {
4450
cleanup();
4551
vi.unstubAllGlobals();

‎ui/packages/app-shell/src/views/metadata-admin/previews/previewStrings.i18n-10848.test.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ import { FlowSimulatorPanel } from './FlowSimulatorPanel';
9999
import { FlowNodeInspector } from '../inspectors/FlowNodeInspector';
100100
import type { SimEdge, SimNode } from './simulator/flow-sim-types';
101101

102+
// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
103+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
104+
// Resident before any case, so the zh rows below read synchronously.
105+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
106+
await loadStudioCatalogue('zh');
107+
102108
afterEach(() => {
103109
cleanup();
104110
state.canvas = 'draw';

‎ui/packages/app-shell/src/views/metadata-admin/studio-locale.i18n.test.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ import { createI18n, I18nProvider } from '@object-ui/i18n';
2323
import { t } from './i18n';
2424
import { AppNavCanvas } from './previews/AppNavCanvas';
2525

26+
// The designer rows this suite reads in zh live in `@object-ui/i18n`'s `studio`
27+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
28+
// Resident before any case, so the zh rows below read synchronously.
29+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
30+
await loadStudioCatalogue('zh');
31+
2632
afterEach(cleanup);
2733

2834
// Representative keys spanning shared chrome, every pillar, and AppNavCanvas.

‎ui/packages/app-shell/src/views/studio-design/AddFieldPopover.studioNamespace-25922.test.tsx‎

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,24 @@
44
* objectstack#25922, (M) slice 1 — a Studio designer screen reads the strings
55
* that moved into `@object-ui/i18n`'s `studio` namespace.
66
*
7-
* The Data pillar's "Add field" popover mixes both homes on one screen: its
8-
* field labels (`designer.field.label` / `.apiName` / `.type`) moved into the
9-
* namespace, its Cancel button (`engine.studio.cancel`) is still in the
10-
* app-shell table (its title and Create button followed in slice S4). It takes its locale from `useMetadataLocale()`, the hook
11-
* every designer component uses, so this is the path a real screen takes:
7+
* The Data pillar's "Add field" popover reads the namespace through both
8+
* doors: its field labels (`designer.field.label` / `.apiName` / `.type`,
9+
* slice 1) straight from it, its chrome (`engine.studio.*`, slices S4 and S5)
10+
* through app-shell's `t()`, which answers from the namespace once the
11+
* designer table no longer holds the row. It takes its locale from
12+
* `useMetadataLocale()`, the hook every designer component uses, so this is
13+
* the path a real screen takes:
1214
*
1315
* - zh: the namespace's `zh` catalogue is NOT resident when the screen first
1416
* renders (this file runs in an isolated `dom` project and preloads
1517
* nothing). The first paint reads the moved labels in English — the
1618
* fallback, never the raw key — and the same mount re-renders in Chinese
17-
* once the catalogue lands. The table's keys read Chinese throughout.
18-
* - de: a language neither home ships renders English from both.
19+
* once the catalogue lands, chrome included.
20+
* - de: a language the namespace does not ship renders English through both
21+
* doors.
1922
*
20-
* Expectations are read from the namespace and the table rather than typed
21-
* out, so no case restates a translation.
23+
* Expectations are read from the namespace and `t()` rather than typed out, so
24+
* no case restates a translation.
2225
*/
2326

2427
import '@testing-library/jest-dom/vitest';
@@ -39,8 +42,8 @@ afterEach(cleanup);
3942

4043
/** Keys this screen renders from the namespace. */
4144
const MOVED = ['designer.field.label', 'designer.field.type'] as const;
42-
/** A key this screen renders from the app-shell table. */
43-
const TABLE = 'engine.studio.cancel';
45+
/** A chrome key this screen reads through app-shell's `t()`. */
46+
const CHROME = 'engine.studio.cancel';
4447

4548
function Screen({ lang }: { lang: string }) {
4649
return (
@@ -58,9 +61,8 @@ const en = (key: string) => studioString(key, 'en')!;
5861

5962
describe('AddFieldPopover — strings from the studio namespace (objectstack#25922)', () => {
6063
it('zh: English while the catalogue is in flight, then Chinese on the same mount', async () => {
61-
// The premise: these keys are the namespace's, and the title is the table's.
62-
for (const key of MOVED) expect(en(key), key).toBeDefined();
63-
expect(studioString(TABLE, 'en')).toBeUndefined();
64+
// The premise: every one of these keys is the namespace's.
65+
for (const key of [...MOVED, CHROME]) expect(en(key), key).toBeDefined();
6466
expect(isStudioCatalogueLoaded('zh-CN')).toBe(false);
6567

6668
render(<Screen lang="zh" />);
@@ -77,14 +79,14 @@ describe('AddFieldPopover — strings from the studio namespace (objectstack#259
7779
for (const key of MOVED) expect(zh[key], key).not.toBe(en(key));
7880
expect(await screen.findByText(zh['designer.field.label'])).toBeInTheDocument();
7981
for (const key of MOVED) expect(popover()).toHaveTextContent(zh[key]);
80-
expect(popover()).toHaveTextContent(t(TABLE, 'zh-CN'));
81-
expect(t(TABLE, 'zh-CN')).not.toBe(t(TABLE, 'en-US'));
82+
expect(popover()).toHaveTextContent(t(CHROME, 'zh-CN'));
83+
expect(t(CHROME, 'zh-CN')).not.toBe(t(CHROME, 'en-US'));
8284
});
8385

84-
it('de: a third locale renders English from both homes', () => {
86+
it('de: a third locale renders English through both doors', () => {
8587
render(<Screen lang="de" />);
8688
for (const key of MOVED) expect(popover()).toHaveTextContent(en(key));
87-
expect(popover()).toHaveTextContent(t(TABLE, 'en-US'));
89+
expect(popover()).toHaveTextContent(t(CHROME, 'en-US'));
8890
for (const key of MOVED) expect(t(key, 'de')).toBe(en(key));
8991
});
9092
});

0 commit comments

Comments
 (0)