Skip to content

Commit 4485536

Browse files
feat(ui): Studio inspector strings (page block, action, dataset, flow node, app nav, widget) move into the studio i18n namespace (#25922 slice S2) (#26780)
<!-- 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.inspector` 下页面区块、动作、数据集、流程节点、应用导航、仪表盘组件六组共 520 个键(en 与 zh 各一份)从 app-shell 的私有表搬进 `@object-ui/i18n` 的 `studio` 命名空间,文案一字未改,调用点不动。 - 为什么改:执行 #25922 裁决 6431032267(Q2 = A,6431234244 维持),这是 (M) 的第 2 片(S2),沿用第 1 片 #26593 建好的懒加载命名空间。 - 风险与回滚:与第 1 片相同,zh 用户首次打开这些检查器时标签会先显示英文、zh 分片到达后同一挂载重渲染为中文;回滚即 revert 本 PR,行原样回到 app-shell。 - 先看:`ui/packages/i18n/src/namespaces/studio/en.ts` 末尾新加的 S2 段,再看 `previews/__tests__/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 S2 of the (M) item.** Every `engine.inspector.pageBlock.*`, `.action.*`, `.dataset.*`, `.flowNode.*`, `.appNav.*` and `.widget.*` row (520 keys per language, same set and order in `en` and `zh`) leaves `ENGINE_STRINGS_EN` / `ENGINE_STRINGS_ZH` in `ui/packages/app-shell/src/views/metadata-admin/i18n.ts` and is appended to `STUDIO_EN` / `STUDIO_ZH` in `ui/packages/i18n/src/namespaces/studio/{en,zh}.ts`, text byte-for-byte unchanged. The move was done by an AST pass (each property with its leading comment block, multi-line values kept whole); a heading comment marks the S2 section in each catalogue. Three comment blocks sat at the head of a mixed run: two move with the rows they head ("Action default (home) inspector", "Dashboard filter bindings"), one stays (the audit-failure note); the `option.location.global_nav` retirement note follows the pageBlock rows it describes. - **No runtime change.** `lookupString` already asks the table first and the namespace second (slice 1), so `t` / `tFormat` / `tOptional` and every call site are untouched. No new file, so the console's `i18n-ns-studio-en` / `i18n-ns-studio-zh` chunk groups need no change; the namespace's only non-test importer is still app-shell's `i18n.ts`. - **Changeset:** `.changeset/25922-studio-i18n-inspectors.md`, `@object-ui/i18n` patch. - ⛔ Untouched: `ui/packages/i18n/src/locales/*.ts`, `ui/.github/**`, root `ci.yml` (its `check:i18n-designer-parity` step stays until S7). ## Why Ruling 6431032267 Q2 = A (upheld by 6431234244): the designer table becomes one lazy `@object-ui/i18n` namespace, moved in slices; S2 is the second row of #26593's slice table. ## Tests New: - `ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.studioNamespace-25922.test.ts`: per family (six named floors, so one family draining cannot hide behind another), the namespace carries the family in `en` and `zh`, and `t()` answers every key with the namespace's value in `en-US`, `zh-CN` and `de-DE` (a language with no catalogue reads `en`). The oracle is the catalogues read through `loadStudioCatalogue`. Forced edits (the zh rows these suites assert synchronously now arrive in the lazily fetched chunk, so each makes `zh` resident with a top-level `await loadStudioCatalogue('zh')`; no assertion changed): - `inspectors/DashboardWidgetInspector.test.tsx`, `inspectors/DatasetDefaultInspector.firstPickKeepsCount-12848.test.tsx`, `inspectors/FlowNodeInspector.renameNode-11827.test.tsx`, `inspectors/PageBlockInspector.colorLabelling.test.tsx`, `inspectors/PageBlockInspector.i18n.test.tsx`, `inspectors/PageBlockInspector.pageHeaderBreadcrumb-11173.test.tsx`, `inspectors/PageBlockInspector.visibleWhen.test.tsx`, `previews/__tests__/block-config.test.ts` (all under `ui/packages/app-shell/src/views/metadata-admin/`). - `previews/__tests__/block-config-i18n.test.ts`: the pageBlock retirement gate (objectui#8368) read its key sets from the app-shell table by line range; it now reads each locale from BOTH homes (table + `STUDIO_EN` / `STUDIO_ZH`, same extractor), skips the namespace catalogues as definition sites in the literal-reader scan, and adds a guard that the homes are non-empty and no key is counted twice. Plus the zh preload. `zh-base-term.i18n-13131.test.ts` already reads the namespace's whole `zh` catalogue (slice 1), so it covers the moved rows with no edit. ## Acceptance notes - **Every moved key resolves identically before and after.** One-off measurement (not committed): the 520 + 520 values were read by AST from the base table at `dc9d1ff8`, then compared with `t()` and `tOptional()` on this branch after `zh` is resident, in `en-US`, `en`, `de-DE`, `fr` (against the base `en` value) and `zh-CN`, `zh`, `zh-TW` (against the base `zh` value): 3,640 lookups, 0 differing. - **Ablations** (`scripts/ablation-replace.mjs`, mutation landed by anchor count and blob, restored byte-identical to `HEAD`): - `lookupString` without its `?? studioString(key, locale)` arm: the new pin goes red in all six families and the equivalence check reports 3,640 of 3,640 lookups differing. - one moved zh row altered (`engine.inspector.widget.kind`): the equivalence check reports exactly 3 differing lookups (`zh-CN`, `zh`, `zh-TW`); the pin stays green, as designed, since its oracle is the catalogue itself. - **Eager bundle: not re-measured locally.** The namespace's only non-test importer is app-shell's `i18n.ts`, statically, so the moved `en` rows sit behind the same import edge before and after; the `zh` rows stay in the lazy `i18n-ns-studio-zh` chunk. CI's `Build & E2E UI` runs the eager-closure budget on the built console. - `check:i18n-drift` reads both homes over the range and reports 0 `en` values changed, 0 keys added or removed. ## Verification All at `18f4d0f6` unless noted. <details><summary>Affected tests</summary> - 294 test files (every app-shell test naming a zh locale, every file naming one of the six families, the i18n namespace tests, the ui i18n script tests), under `os-verify-lock`, before the forced edits: 287 passed, 7 failed (the seven suites above, all zh-synchronous). After the edits the 11 touched or new files: `Test Files 11 passed (11)`, `Tests 163 passed (163)`, VERDICT command-exit 0. - `pnpm --filter @object-ui/i18n --filter @object-ui/app-shell run type-check` (both tsconfigs each): VERDICT command-exit 0. </details> <details><summary>Gates</summary> - `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`, `check:i18n-keys`, `check:new-line-citations` (0 new citations): all exit 0. `node scripts/check-nul-bytes.mjs`: exit 0. - Changed lines: 2,469 (+1,309 / −1,160) over 14 files, under the 3,000 threshold. </details> ## Human Minimum - [x] 1. No `packages/spec` change. - [x] 2. No new names beyond one test file. - [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 13e85be commit 4485536

14 files changed

Lines changed: 1309 additions & 1160 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 Studio inspectors' page block, action, dataset, flow node, app navigation and dashboard widget strings move into the `studio` namespace
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Second slice of the move ruled on objectstack#25922** (6431032267, Q2 = A; kept by 6431234244). The 520 keys under `engine.inspector.pageBlock.*`, `engine.inspector.action.*`, `engine.inspector.dataset.*`, `engine.inspector.flowNode.*`, `engine.inspector.appNav.*` and `engine.inspector.widget.*` now live in `@object-ui/i18n/namespaces/studio` instead of `@object-ui/app-shell`'s private table, text unchanged in both `en` and `zh`. 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 first slice. No export changes.

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

Lines changed: 0 additions & 1146 deletions
Large diffs are not rendered by default.

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.test.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ vi.mock('../previews/useDatasetCatalog', () => ({
3131
}));
3232

3333
import { DashboardWidgetInspector } from './DashboardWidgetInspector';
34+
// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio`
35+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
36+
// Resident before any case, so the zh rows below read synchronously.
37+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
38+
await loadStudioCatalogue('zh');
3439

3540
afterEach(cleanup);
3641

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/DatasetDefaultInspector.firstPickKeepsCount-12848.test.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ import { registerDefaultMetadataSchemas } from '../default-schemas';
8080
import { registerBuiltinPreviews } from '../previews';
8181
import { registerBuiltinInspectors } from '.';
8282
import { DatasetDefaultInspector, objectChangePatch } from './DatasetDefaultInspector';
83+
// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio`
84+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
85+
// Resident before any case, so the zh rows below read synchronously.
86+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
87+
await loadStudioCatalogue('zh');
8388

8489
const DATASET_JSON_SCHEMA = z.toJSONSchema(getMetadataTypeSchema('dataset') as z.ZodType, {
8590
io: 'input',

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.renameNode-11827.test.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ import { FlowNodeInspector } from './FlowNodeInspector';
3131
import { encodeNestedNodeId } from './flow-nested-selection';
3232
import { t, tFormat, type SupportedLocale } from '../i18n';
3333
import type { MetadataSelection } from '../preview-registry';
34+
// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio`
35+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
36+
// Resident before any case, so the zh rows below read synchronously.
37+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
38+
await loadStudioCatalogue('zh');
3439

3540
afterEach(cleanup);
3641

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.colorLabelling.test.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ const state = vi.hoisted(() => ({
4545
vi.mock('../useMetadata', () => ({ useMetadataClient: () => state.metadataClient }));
4646

4747
import { PageBlockInspector } from './PageBlockInspector';
48+
// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio`
49+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
50+
// Resident before any case, so the zh rows below read synchronously.
51+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
52+
await loadStudioCatalogue('zh');
4853

4954
afterEach(cleanup);
5055

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.i18n.test.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ vi.mock('../useMetadata', () => ({
4848
}));
4949

5050
import { PageBlockInspector } from './PageBlockInspector';
51+
// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio`
52+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
53+
// Resident before any case, so the zh rows below read synchronously.
54+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
55+
await loadStudioCatalogue('zh');
5156

5257
afterEach(cleanup);
5358

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.pageHeaderBreadcrumb-11173.test.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ vi.mock('../useMetadata', () => ({
4141
}));
4242

4343
import { PageBlockInspector } from './PageBlockInspector';
44+
// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio`
45+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
46+
// Resident before any case, so the zh rows below read synchronously.
47+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
48+
await loadStudioCatalogue('zh');
4449

4550
afterEach(cleanup);
4651

‎ui/packages/app-shell/src/views/metadata-admin/inspectors/PageBlockInspector.visibleWhen.test.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ import { SchemaRenderer, PredicateScopeProvider } from '@object-ui/react';
3434
import { PageBlockInspector } from './PageBlockInspector';
3535
import type { MetadataInspectorProps } from '../inspector-registry';
3636
import type { SupportedLocale } from '../i18n';
37+
// The inspector's `engine.inspector.*` rows live in `@object-ui/i18n`'s `studio`
38+
// namespace, whose `zh` catalogue is fetched on demand (objectstack#25922).
39+
// Resident before any case, so the zh rows below read synchronously.
40+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
41+
await loadStudioCatalogue('zh');
3742

3843
afterEach(cleanup);
3944

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* objectstack#25922 slice S2 — the page block, action, dataset, flow node, app
5+
* navigation and dashboard widget inspector rows (`engine.inspector.{pageBlock,
6+
* action,dataset,flowNode,appNav,widget}.*`) live in `@object-ui/i18n`'s
7+
* `studio` namespace, and app-shell's `t()` still answers every one of them the
8+
* way it did when they sat in its own table.
9+
*
10+
* What would break it:
11+
* - a row copied back into the app-shell table with a different value — the
12+
* table is asked first, so it would shadow the namespace silently;
13+
* - the namespace fallback dropped from `lookupString` — every moved key
14+
* would echo back as itself;
15+
* - a family emptied out of the namespace, which every per-key assertion
16+
* below would then pass over nothing. The floors are per FAMILY, by name,
17+
* so one family draining cannot hide behind another one growing.
18+
*
19+
* The oracle is the namespace's own catalogues, read through
20+
* `loadStudioCatalogue` (the module's public door), not a copy of their text.
21+
*/
22+
import { describe, expect, it } from 'vitest';
23+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
24+
import { t } from '../i18n';
25+
26+
const EN = await loadStudioCatalogue('en');
27+
// Resident before any case, so the zh lookups below read synchronously.
28+
const ZH = await loadStudioCatalogue('zh');
29+
30+
/** Family → the fewest keys it may carry (measured at the move, rounded down). */
31+
const S2_FAMILY_FLOOR: Readonly<Record<string, number>> = Object.freeze({
32+
pageBlock: 160,
33+
action: 120,
34+
dataset: 80,
35+
flowNode: 40,
36+
appNav: 40,
37+
widget: 40,
38+
});
39+
40+
const familyKeys = (family: string): string[] =>
41+
Object.keys(EN).filter((key) => key.startsWith(`engine.inspector.${family}.`));
42+
43+
describe('S2 inspector rows answer from the studio namespace (objectstack#25922)', () => {
44+
it.each(Object.entries(S2_FAMILY_FLOOR))('%s: the namespace carries the family, in en and zh', (family, floor) => {
45+
const keys = familyKeys(family);
46+
expect(keys.length).toBeGreaterThanOrEqual(floor);
47+
expect(keys.filter((key) => !(key in ZH))).toEqual([]);
48+
});
49+
50+
it.each(Object.keys(S2_FAMILY_FLOOR))('%s: t() answers every key with the namespace value', (family) => {
51+
const wrong = familyKeys(family).flatMap((key) => {
52+
const out: string[] = [];
53+
if (t(key, 'en-US') !== EN[key]) out.push(`en-US ${key}: ${t(key, 'en-US')}`);
54+
if (t(key, 'zh-CN') !== ZH[key]) out.push(`zh-CN ${key}: ${t(key, 'zh-CN')}`);
55+
// A language the namespace has no catalogue for reads English, as it did
56+
// from the app-shell table.
57+
if (t(key, 'de-DE') !== EN[key]) out.push(`de-DE ${key}: ${t(key, 'de-DE')}`);
58+
return out;
59+
});
60+
expect(wrong).toEqual([]);
61+
});
62+
});

0 commit comments

Comments
 (0)