Repository navigation
Commit 500bc9d
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9TW2qPuXi9wW2vYi3QL9EbS)_
Part of #25922
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:`@object-ui/i18n` 新增懒加载的 `studio` 命名空间(en 常驻、zh 按需拉取、其他语言回落
en),并把 Studio 设计器表里的 `designer.*` / `perm.*` 共 349 个键(两种语言)迁入;app-shell
的 `t()` 先查原表、再查命名空间,调用点不变。
- 为什么改:按 #25922 裁决 6431032267(Q2 = A,6431234244 维持)把设计器文案搬进 i18n 包,这是
(M) 的第 1 片,只搬机制和第一块连续的表。
- 风险与回滚:zh 用户首次打开设计器时这些标签会先显示英文、zh 分片到达后同一挂载自动重渲染为中文;回滚即 revert 本
PR(表行原样回到 app-shell)。
- 先看:`ui/packages/i18n/src/namespaces/studio/index.ts`,再看 app-shell
`views/metadata-admin/i18n.ts` 的 `lookupString` 与 `useMetadataLocale`。
- 两分钟验证:`cd ui && pnpm run check:i18n-designer-parity && pnpm exec
vitest run packages/i18n/src/__tests__/studio-namespace.test.ts
packages/i18n/src/__tests__/studio-namespace-lazy.test.tsx`。
- 需要你做的:无需操作;剩余切片计划见下文 “Remaining slices”,确认或调整即可。ci.yml 的 parity 步骤本 PR
不动,退役放在最后一片(Tier H)。
## What changes
- **New subpath `@object-ui/i18n/namespaces/studio`**
(`ui/packages/i18n/src/namespaces/studio/{index,en,zh}.ts`,
`package.json` export). Same residency shape as `locales/registry.ts`:
`en` is a static import (the fallback for every key and language), `zh`
is one literal `() => import('./zh.js')`. API: `studioString(key, lang)`
(synchronous; while the serving catalogue is in flight it starts the
fetch and answers from `en`, never the raw key),
`loadStudioCatalogue(lang)` (memoised, a rejected load is not memoised),
`isStudioCatalogueLoaded`, `resolveStudioLanguage` (exact tag, then base
language, else `en`), the `useStudioCatalogue(lang)` hook
(`useSyncExternalStore`, re-renders its caller when a catalogue lands),
`STUDIO_NAMESPACE`, `STUDIO_FALLBACK_LANGUAGE`, `STUDIO_LANGUAGE_CODES`,
`StudioCatalogue`.
- **First slice of the table:** every `designer.*` and `perm.*` row (349
keys per side; one contiguous run on each side of `ENGINE_STRINGS_EN` /
`ENGINE_STRINGS_ZH`) moves out of
`ui/packages/app-shell/src/views/metadata-admin/i18n.ts` into the
namespace, text unchanged.
- **app-shell facade:** the six `pickTable(locale).strings[key]` reads
go through `lookupString(key, locale)` = table first, namespace second,
so `t` / `tFormat` / `tOptional` / `translateNodeLabel` /
`translateNodeHint` / `translateEnumOption` and every call site stay as
they are. The 54 zh-only producer keys are untouched and keep their
`tOptional` read. `useMetadataLocale()` now also calls
`useStudioCatalogue(locale)`, so every designer component re-renders
once `zh` lands.
- **Not in the eager catalogue, not in `all-locales-key-parity`, by
construction.** `locales/en.ts` gains no key; the namespace's files live
outside `locales/` and are not members of `builtInLocales`, which is
what that suite iterates. The `index.ts` docblock and the README say so;
the namespace's own parity is `studio-namespace.test.ts`.
- **Console chunking** (`ui/apps/console/vite.config.ts`): two
priority-84 `advancedChunks` groups, `i18n-ns-studio-en` and
`i18n-ns-studio-zh`, so the namespace is not swept into the eager
`i18n-runtime` group (whose test matches all of `packages/i18n`).
- **Gates read the namespace homes**
(`ui/scripts/check-i18n-dead-keys.mjs` `collectDesignerKeys`, used by
`check-i18n-designer-table-parity.mjs` and `check-i18n-en-drift.mjs`):
when the table module imports `@object-ui/i18n/namespaces/studio` (the
same edge the runtime follows), the homes' `STUDIO_EN` / `STUDIO_ZH`
literals are merged into `ENGINE_STRINGS_EN` / `_ZH` by AST. A home that
cannot be read throws; a key in both homes throws ("exactly one home");
the dead-keys sweep does not count a home literal as a reader. en-drift
reads the homes at the same ref, so the move itself is not drift.
- **README:** the "engine.* carve-out" section is rewritten as "Scope —
the Studio designer strings (the `studio` namespace)", citing rulings
6431032267 and 6431234244; two comments that cited the old heading name
follow it.
- **Changeset:** root `.changeset/25922-studio-i18n-namespace.md`,
`@object-ui/i18n` patch.
- ⛔ Untouched: `ui/.github/**`, root `ci.yml` (its
`check:i18n-designer-parity` step stays), existing keys of the eager
locale packs, `ObjectGrid.tsx`.
## Tests
New:
- `ui/packages/i18n/src/__tests__/studio-namespace.test.ts` (unit):
scoped parity (en ⊆ zh, same `{placeholders}`, zh-only keys only in
documented families, non-empty values), no namespace key defined by
`locales/en.ts` (with a lit control), language resolution table,
resident zh answers zh while `de` / `en-US` answer en, unknown key is
`undefined`.
- `ui/packages/i18n/src/__tests__/studio-namespace-lazy.test.tsx` (dom,
isolated): before the fetch a zh lookup renders en; the same component
re-renders in zh once the chunk lands; `de` renders en and fetches
nothing.
-
`ui/packages/app-shell/src/views/studio-design/AddFieldPopover.studioNamespace-25922.test.tsx`:
a real Studio screen (Data pillar "Add field" popover, which mixes both
homes) through `useMetadataLocale()` — zh first paint is English (never
the key), then the same mount is Chinese; table keys read Chinese
throughout; `de` renders English from both homes.
- `ui/scripts/__tests__/check-i18n-dead-keys.test.ts`: six
`collectDesignerKeys()` home cases (merge, no import → no homes,
unreadable home throws, duplicate key throws, `source` + `readHome`,
sweep ignores home literals).
- `ui/scripts/__tests__/check-eager-closure-budget.test.ts`: the two
namespace modules route to `i18n-ns-studio-zh` / `i18n-ns-studio-en`.
**Forced test edits** (the zh rows these suites assert synchronously now
arrive in a lazily fetched chunk, so each makes zh resident before its
first case with a top-level `await loadStudioCatalogue('zh')`; no
assertion changed):
- `metadata-admin/zh-base-term.i18n-13131.test.ts` — the zh census also
reads the namespace's zh rows (otherwise it would silently stop covering
349 keys).
- Preload only, 17 files:
`metadata-admin/CelTestRunDialog.i18n-10862.test.tsx`,
`metadata-admin/PermissionMatrixEditor.cloneToCustomize.test.tsx`,
`inspectors/FieldTypePicker.search-12516.test.tsx`,
`inspectors/ObjectDefaultInspector.test.tsx`,
`inspectors/ObjectFieldInspector.draft-locale.test.tsx`,
`inspectors/ObjectFieldInspector.labelTranslation-11782.test.tsx`,
`inspectors/ObjectFieldInspector.typePicker-11793.test.tsx`,
`inspectors/ObjectFieldInspector.valueDomain-7597.test.tsx`,
`inspectors/inspectorStrings.hookAction.i18n-10586.test.tsx`,
`inspectors/inspectorStrings.i18n-10586.test.tsx`,
`inspectors/inspectorStrings.i18n-10696.test.tsx`,
`inspectors/inspectorStrings.i18n-10748.test.tsx`,
`previews/PermissionPreview.rlsPolicies-11027.test.tsx`,
`previews/dataPreviews.i18n-10862-s3.test.tsx`,
`previews/field-types.studioSurfaces-11909.test.tsx`,
`previews/flowFieldChrome.i18n-10862-s4.test.tsx`,
`studio-design/BuilderLanding.environmentOverlayCopy-12171.test.ts` (all
under `ui/packages/app-shell/src/views/`).
- Comment only:
`metadata-admin/ResourceEditPage.referencesErrorState.test.tsx` (cites
the README section's new name).
## Acceptance notes
**Console eager bundle does not grow.** Same tree, console `vite build`,
BEFORE = the base `i18n.ts` + `vite.config.ts` restored in place, AFTER
= this branch:
| | before | after | Δ |
|---|---:|---:|---:|
| eager closure, gzip | 2,000,416 B | 1,995,304 B | −5,112 |
| eager closure, raw | 6,817,847 B | 6,796,608 B | −21,239 |
| `i18n` chunk (app-shell table), gzip | 118,144 | 105,711 | −12,433 |
| `i18n-runtime`, gzip | 6,308 | 6,637 | +329 |
| `i18n-ns-studio-en` (new, eager), gzip | — | 6,950 | +6,950 |
| `i18n-ns-studio-zh` (new, **lazy**) | — | 22,332 raw | not in the
closure |
| `i18n-locale-en` (the eager en catalogue) | 169,775 raw / 53,322 gz |
identical, same hash | 0 |
`locales/en.ts` is 270,655 B before and after.
`check-eager-locale-catalogues` and `check-eager-closure-budget` exit 0
on the AFTER dist (aggregate 1948.5 KB of 2431.6 KB).
**Ablations** (`scripts/ablation-replace.mjs`, mutation landed and
restored, `git diff HEAD` empty after each):
- `studioString`'s en fallback → `return undefined;`: 2 red —
`studio-namespace-lazy` "answers from en before the zh catalogue lands",
`AddFieldPopover` "zh: English while the catalogue is in flight".
- `resolveStudioLanguage`'s `?? STUDIO_FALLBACK_LANGUAGE` → `?? (lang ??
"")`: 8 red, including both third-locale (`de`) screen cases.
**Gates:** `check:i18n-designer-parity` green (ENGINE_STRINGS 3121 en /
3175 zh, the union unchanged), `check:i18n-drift` green with 0 keys
added or removed, `check:i18n-dead-keys` and `check:i18n-keys` green.
**Open questions** (implementation choices, not blockers):
1. The namespace is not registered as i18next resources: its values use
the designer's `{token}` syntax, which i18next would interpolate as
literal text. A later slice could convert, but that is a text change to
every row.
2. `useMetadataLocale()` still collapses to `en-US` / `zh-CN`, so a
`zh-TW` viewer reads `zh` (as before this PR).
3. `Clause-②: no` is copied from the claim; the PR adds one subpath
export, which is additive.
### Remaining slices (proposal)
Remaining app-shell table ≈ 6,430 lines across both locales
(engine.inspector 1,644; engine.studio 1,586; engine.packages/form/edit
802; other engine.* 2,398). Changed lines per slice ≈ 2× table lines,
each ≤ ~3,000 and each keeping `check:i18n-designer-parity` green; each
slice adds the zh preload to the zh suites it touches.
| Slice | Content | Table lines |
|---|---|---:|
| S2 | `engine.inspector.{pageBlock, action, dataset, flowNode, appNav,
widget}` | ~1,141 |
| S3 | rest of `engine.inspector` + `engine.packages` / `form` / `edit`
(incl. the zh-only `packages.form.help` keys, still read by `tOptional`)
| ~1,305 |
| S4 | `engine.studio.{access, if, data, rules, auto, settings}` + ~450
of other `engine.*` | ~1,344 |
| S5 | rest of `engine.studio` + ~650 of other `engine.*` | ~1,342 |
| S6 | the remaining `engine.*` | ~1,300 |
| S7 (Tier H, needs maintainer approval) | `TYPE_LABELS` /
`DOMAIN_LABELS` and the zh side tables (`FLOW_FIELD_ZH` …); delete the
app-shell table; retire `check:i18n-designer-parity` and its ci.yml step
| — |
## Verification
- 226 test files touched by the move (app-shell designer, i18n, ui
scripts): 226 passed / 2,907 tests after the clone-to-customize preload;
the first run's one red was that zh case, fixed in
`PermissionMatrixEditor.cloneToCustomize.test.tsx`.
- `pnpm --filter @object-ui/i18n type-check`, `pnpm --filter
@object-ui/app-shell type-check`: both exit 0 (after `pnpm --filter
@object-ui/i18n build`).
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 46 derived commands run, all exit 0;
`--ran` reconciliation: 46 derived, 46 run, 0 NOT-MEASURED (derived
zero). Also run: ui `check:eager-closure`,
`check:eager-locale-catalogues`, `check:i18n-dead-keys`,
`check:i18n-designer-parity`, `check:i18n-drift`, `check:i18n-keys`, all
exit 0 on the AFTER console dist. Head `e355404ca8`; 36 files, +1,792 /
−961 (2,753 changed lines, under the 3,000 threshold).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmRuDqEXC3CybUtz5d1)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9dc8fba commit 500bc9d
36 files changed
Lines changed: 1792 additions & 961 deletions
File tree
- .changeset
- ui
- apps/console
- packages
- app-shell/src/views
- metadata-admin
- inspectors
- previews
- studio-design
- i18n
- src
- __tests__
- namespaces/studio
- scripts
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1156 | 1156 | | |
1157 | 1157 | | |
1158 | 1158 | | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
1159 | 1171 | | |
1160 | 1172 | | |
1161 | 1173 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
43 | 48 | | |
44 | 49 | | |
45 | 50 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
161 | 161 | | |
162 | 162 | | |
163 | 163 | | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
164 | 170 | | |
165 | 171 | | |
166 | 172 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
263 | | - | |
| 263 | + | |
264 | 264 | | |
265 | 265 | | |
266 | 266 | | |
| |||
0 commit comments