Skip to content

feat(i18n)!: the Console's language packs follow the platform locale table, keyed by its tags, and Traditional Chinese (zh-Hant) ships - #30902

Draft
objectstack-fleet[bot] wants to merge 19 commits into
mainfrom
claude/project-thread-f2dhrw
Draft

objectstack-fleet[bot] wants to merge 19 commits into
mainfrom
claude/project-thread-f2dhrw

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread
domain:ui#2 · os-tesla · session_01L9qibyWSiXh6B2sMVHfXYC

Part of #30511. Every item the card asks for is in this PR; the seat closes the card when it lands, and phase 4 (the locale menu) follows.
Supersedes #30595 (the same reviewed diff, resumed from claude/project-thread-xplvvb@9b15e9ad45 on this thread's own branch, then merged with main through #30640's lazy marketplace namespace and #30601's shared locale ladder).

Clause-②: yes (narrowing) — widens: a zh-Hant pack (the zhHant export of @object-ui/i18n/locales, the @object-ui/i18n/locales/zh-Hant module), the resolveBuiltInLanguage export of @object-ui/i18n, and every stored or browser code the platform locale table reads as one of its tags (zh-TW, zh-HK, zh-MO → zh-Hant; zh, zh-CN, zh-SG → zh-Hans; pt → pt-BR). Narrows: the Simplified Chinese and Portuguese packs are keyed zh-Hans and pt-BR, no longer zh and pt, in builtInLocales, BUILT_IN_LANGUAGE_CODES, getLoadedBuiltInLocales() and getAvailableLanguages(), and their modules @object-ui/i18n/locales/zh and @object-ui/i18n/locales/pt are @object-ui/i18n/locales/zh-Hans and @object-ui/i18n/locales/pt-BR.
Metadata-protocol: none
Skills-docs: follow-up owed — skills/objectui/guides/i18n.md

维护者速读

  • 改了什么:控制台的语言包改为跟随平台语言表,表里每个语言一个包,以表的标签为键(zh → zh-Hans,pt → pt-BR),新增繁体中文 zh-Hant,由简体包自动转换生成。刚合入的应用市场按需文案包同样按标签拆成 11 份,也有繁体。存值语言(sys_user.locale)挑选改用平台统一的匹配顺序,与服务端一致。
  • 为什么改:裁决 F([Decision] One locale list, one completeness rule — the long-term i18n regime across Console, platform metadata, cloud and mobile #29349)要求控制台的语言集合取自平台语言表;此前控制台自带十个包,表里的繁体中文缺席,测试照样全绿。台湾、香港用户此前看到的是简体。
  • 风险与回滚:只动前端与 i18n 门禁脚本,不动 spec 与服务端。旧存值 zh-CN、zh、pt 照常渲染;zh-TW、zh-HK 用户从简体变为繁体。仓外代码若按 builtInLocales.zh 或 @object-ui/i18n/locales/zh 取包会断,changeset 写了改法。首屏英文包字节不变。回滚即 revert 本 PR。
  • 先看:ui/packages/i18n/src/locales/registry.ts(按标签加载与 resolveBuiltInLanguage)、ui/packages/i18n/src/namespaces/marketplace/index.ts(应用市场文案按标签加载)、ui/packages/i18n/scripts/zh-hant.mjs(繁体转换规则)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/i18n/,预期全部通过;再把 ui/packages/i18n/src/locales/index.ts 里 'zh-Hant': zhHant, 一行删掉重跑 all-locales-key-parity,预期转红并点名 zh-Hant。
  • 需要你做的:请以 os-zhuang(或 hotlong)批准本 PR。本 PR 改动 8,497 行,超过 3,000 行人批线;契约复审 CONTRACT_REVIEW_TIER PASS 在案(评论 6683373168,Head-sha c1f166cc40e17e5a1c069137a6ffa32897da33fb;前一 head 的 FAIL 6682790803 已由 143ef31 修复)。批准后席位翻 ready、挂自动合并。
  • 席位意见:ui 席 2 对照 head c1f166cc 的 diff 复核:152 个文件,语言包键随平台语言表改为标签(zh → zh-Hans、pt → pt-BR),新增由简体自动生成的 zh-Hant,应用市场按需文案包同样按标签拆分;旧存值 zh、zh-CN、pt 经表的读别名照常解析,首屏英文包字节不变、上限未动。属收窄(BREAKING,patch,changeset 带 FROM → TO 与 ADR-0087 标记),独立复审员按 CONTRACT_REVIEW_TIER 先判 FAIL(lint 入口守卫),修复后 delta 复审 PASS;CI 57 项全绿。复审提出的后续已立卡:A Traditional Chinese console reads and writes the Simplified entry of an inline label map #30910(繁体会话读写内联标签取到简体键)、skills/objectui i18n guide still teaches the zh and pt pack keys that #30511 retires #30911(skills i18n 指南仍教 zh/pt);服务端与 studio 命名空间缺繁体归 Platform locale table (ruling F of #29349): one list of 11 languages in spec, complete on every surface #29428。同意落地,待人批。

What changes

The pack set comes from the platform locale table. ui/packages/i18n no longer keeps its own list of ten languages. It reads PLATFORM_LOCALES from @objectstack/spec/system (phase 1, PR #30007).

  • Pack files. src/locales/zh.ts is now zh-Hans.ts, pt.ts is now pt-BR.ts, and zh-Hant.ts is new. builtInLocales is keyed by table tag, in table order, and satisfies Record<PlatformLocaleTag, unknown>. A tag the table gains without a pack, or a pack the table lacks, is a compile error.
  • registry.ts. LAZY_LOCALE_LOADERS is typed by the table's tags, and BUILT_IN_LANGUAGE_CODES is PLATFORM_LOCALE_TAGS.
    • The new export resolveBuiltInLanguage(code) names the pack that a stored or browser code reads as. It tries the table's tag or read alias first (platformLocaleTagOf, case-insensitive), then each shorter prefix (zh-Hant-TW → zh-Hant, en-GB → en).
    • loadBuiltInLocale and isBuiltInLanguage go through it, so every legacy code still loads its pack.
  • i18n.ts. fallbackLng is a map built from the table. Every tag falls back to the fallback language. Every read alias falls back to its tag, then to the fallback language. So an instance whose language is a legacy zh or zh-CN renders the zh-Hans resource.
  • provider.tsx. The boot effect and changeLanguage now write a built-in catalogue under its table tag, the bundle createI18n keyed it by.
    • Before, an instance in an alias (zh, zh-TW, pt) got the resident catalogue written a second time under the alias. That fired an added event, which re-rendered every reader and refetched every chart keyed on the resolver.
    • ObjectChart.refreshIndicatorLocale-10580 found it: its zh chart never left the skeleton.

zh-Hant is generated. scripts/zh-hant.mjs is the converter. It uses:

  • OpenCC s2twp, with opencc-js pinned at 1.3.1 as a devDependency;
  • the phrase overrides of the www.objectos.ai converter;
  • the locative 里 repairs.

scripts/gen-zh-hant.mjs writes src/locales/zh-Hant.ts from zh-Hans.ts (pnpm --filter @object-ui/i18n gen:zh-hant). It refuses to write when a pinned conversion case fails. There are two additions over the site's rules, both pinned as conversion cases:

  • STRADDLE_OVERRIDES: 并发布 → 並發布, 头发送 → 頭發送, 是只读 → 是只讀, 只影响 → 只影響. s2twp segments by its Simplified phrase dictionary before it converts. A phrase that straddles a word boundary (审阅「并发」布) therefore cannot be repaired at the Traditional phrase stage. The fix puts the Simplified phrase into the segmentation dictionary and at the head of the first group.
  • TWP_PHRASE_OVERRIDES gains 操作運行時 → 操作執行時.

Other details of the generated pack:

  • Gate-only keys. The six gate-only console.ai.* outbound keys are left out of zh-Hant, as they are from every non-gate pack. Comments replace them so line numbers stay aligned with zh-Hans.
  • Freshness test. src/__tests__/zh-hant-generated.test.ts reds when the committed pack is not byte-for-byte what the generator writes, and names the command that repairs it. It is a package test, not a check:* gate (ruling F adds none).
  • Merge-driver ledger. It records zh-Hant.ts as NOT_DRIVER_MANAGED (gen gen:zh-hant). The pack is generated whole, but a REGEN_ARTIFACTS row needs a check: script, and this phase adds none.
  • Type-check coverage. The converter's zh-hant.d.mts twin joins the package's tsconfig.test.json, so check:type-check-coverage reads it.
  • Lint-rule coverage. ui/scripts/check-lint-rule-coverage.mjs declares the same twin in UNREACHED_GROUPS, beside the two script twins it already declares. Its tests now accept an objectstack# card on a ledger row as well as an objectui# one.

The lazy marketplace namespace follows (#30640). src/namespaces/marketplace/zh.ts is now zh-Hans.ts, pt.ts is now pt-BR.ts, and zh-Hant.ts is new, generated by the same command.

  • MARKETPLACE_LOADERS is typed Record<PlatformLocaleTag, …>, eleven literal imports, so a table tag without a catalogue is a compile error.
  • resolveMarketplaceLanguage reads a stored code through resolveBuiltInLanguage, so zh-TW loads the Traditional catalogue and pt the pt-BR one. The loader stores the catalogue under the table tag.
  • The all-packs door (locales/index.ts) deep-merges each tag's catalogue into its pack, as main's complete() already did, and still throws on a leaf defined twice.
  • The converter gains generateZhHantNamespaceCatalogue and a frozen ZH_HANT_TARGETS list (the locales pack and the marketplace catalogue); gen:zh-hant writes both, and the freshness test walks the list.
  • apps/console/vite.config.ts names one i18n-ns-marketplace-<tag> chunk per table tag.
  • feat(console): a marketplace app's Install confirms what it adds before it installs #30840's eleven install.preview keys came in through the renames and are in zh-Hans, pt-BR and the regenerated zh-Hant.

Readers keyed by the old codes.

  • app-shell readers. LoadingScreen (the boot line), outboundAgentText and flowResponse read the loaded-pack snapshot by table tag, through resolveBuiltInLanguage.
  • app-shell pickRenderableLocale (the sys_user.locale → UI language pick) is now resolveLocaleAmong from @objectstack/spec/system (fix(spec,core): resolve locales through the platform table's read aliases, so a zh-TW / zh-HK reader gets zh-Hant #30601), the ladder the server's resolveLocale uses, instead of a copy of it. A stored zh-TW meets a list offering both scripts and renders zh-Hant; before, it took the first zh variant, zh-Hans. A variant the table reads as the language itself (es-ES for es) is taken ahead of the first offered variant, as the server takes it.
  • components page tabs. The Traditional rows of the exact-locale label dictionary now serve a zh-Hant session, and zh-HK / zh-MO sessions too. Before, every non-exact zh fell to the Simplified rows.
  • apps/console/vite.config.ts. One locale chunk per table tag.

Gates read the table.

  • check-i18n-en-drift.mjs takes its locales from PLATFORM_LOCALE_TAGS and merges each namespace's partial pack in (main's PACK_NAMESPACE_DIRS), throwing on a key defined twice. A follower pack that the base commit does not have (this PR's renames and zh-Hant) reads as empty at the base and is named in the summary. en missing at the base, or any pack missing at HEAD, still throws.
  • check-i18n-dead-keys.mjs strips map-chain tags by the table.
  • check-eager-locale-catalogues.mjs keeps its deliberate explicit list, now the eleven tags. Its test pins that list to the table and to the registry's loader table.

Paths. One doc link (ui/packages/plugin-gantt/docs/verification/README.md, which check-doc-links reads) and one test comment named zh.ts. Both now name zh-Hans.ts.

Tests

  • Suites that read the table. The parity, region-fallback, entry-shape and en-drift suites read the table.

    • all-locales-key-parity walks PLATFORM_LOCALE_TAGS, so a table language with no pack reds it by name.
    • region-locale-fallback-11326 covers both scripts: zh-CN and zh-SG read Simplified; zh-TW, zh-HK and zh-MO read Traditional.
  • zh-hant-generated.test.ts (new) covers:

    • byte-for-byte freshness and the generated header;
    • every pinned conversion case;
    • 保存 → 儲存 as the converter's answer;
    • the gate-only keys dropped, and nothing else;
    • the converter's declaration twin.
  • provider-load-rerender-10382.test.tsx. An instance booted in zh, zh-CN, zh-TW or pt reads its tag's resident catalogue, renders once, and keeps no bundle under the alias. A catalogue that lands after mount reaches a zh-TW reader.

  • i18n.test.ts. Main's CJK-Latin title spacing (Console and CLI polish from the real-user walkthrough #30126) is pinned for both zh-Hans and zh-Hant booted by tag.

  • check-i18n-en-drift.test.ts. Fixtures write a const named pack, because a tag is not an identifier. A new describe pins the base-absent follower rule, its CLI line, and the two cases that still throw.

  • git-paths-nested-layout.test.ts. It pinned that any pack missing at the base throws. Under the follower rule only en does, so the case now pins both halves: en missing at the base throws, and a follower the base lacks reads as empty and is named.

  • userLocale-10059.test.tsx. A new describe pins the table step:

    • zh-TW, zh-HK, zh-MO and zh-Hant-TW → zh-Hant;
    • zh, zh-CN and zh-SG → zh-Hans;
    • pt → pt-BR;
    • a deployment's own spelling;
    • the control, where no Traditional spelling is offered.

    The pin "first offered variant" moved from zh to es (es over en, es-MX, es-AR → es-MX), because the table reads a bare zh as Simplified. A new case pins the shared ladder's table step: es over en, es-MX, es-ES → es-ES.

  • Marketplace namespace suites (marketplace-namespace, marketplace-namespace-lazy, MarketplacePage.namespaceGate-30588) are keyed by tag, and pin zh-TW / zh-HK → zh-Hant and pt → pt-BR resolution plus a zh-TW reader's lazy load. zh-hant-generated runs each case over ZH_HANT_TARGETS, with a counter-probe on the list itself.

  • page-tabs-builtin-label-i18n-4645.test.tsx. zh-Hant, zh-HK and zh-MO sessions read 詳情 / 備註 / 概覽, and two of those are dict-only tokens. The zh-Hans / zh-SG control reads Simplified.

  • LocaleSwitcher suites (appLocales, persistence, unsavedEditsAsk-13254). They pick and expect the menu's names for the table's tags through localeLabel, and the fallback-menu cases expect one entry per tag. The component itself is not edited.

  • About 100 other suites hard-coded ten packs, a zh / pt key or a 'zh' lookup. They now read the table or the tag. For example, toHaveLength(10) became PLATFORM_LOCALE_TAGS.length, and where a table names every pack, a converted zh-Hant twin sits beside each retagged zh-Hans row.

Readings (head f7e9df2d, base 98f640d5)

Ablations, suites, type-check and gates

The heavy runs were taken on e94c13c8. f7e9df2d adds one merge of main (11 commits) and the regenerated zh-Hant; the i18n package, the en-drift test, the 32 test files those 11 commits added or changed, the i18n checks and the ADR-0087 and no-major checks were rerun on it.

  • Green on main, red by name here. On main before this branch, the table already named zh-Hans, zh-Hant and pt-BR and the Console had no pack keyed by any of them, yet all-locales-key-parity and region-locale-fallback-11326 were green (2 files, 58 tests; taken for feat(i18n)!: the Console's language packs follow the platform locale table, keyed by its tags, and Traditional Chinese (zh-Hant) ships #30595).
  • Ablations. Each was restored afterwards, with git diff HEAD empty.
  • Suites.
    • Full ui suite (pnpm --dir ui run test) on d0632482: 6631 files passed, 7 skipped; 21 files red. 19 of them read built dist / .d.ts output that the skip-dts build had not written; the other 2 named the old builtInLocales.zh key and were fixed in d0632482. After the full workspace build, the 21 files rerun: 21 / 21, 513 tests.
    • packages/i18n/, scripts/, packages/app-shell/src/hooks/ and the 36 test files main had added since the first merge, on e94c13c8: 401 files passed, 2 skipped; 7830 tests.
    • On f7e9df2d: packages/i18n/, the en-drift test and the 32 test files of the last merge: 131 files, 2014 tests, green.
    • Dogfood on e94c13c8, after the full workspace build: pnpm --filter @objectstack/dogfood test: 424 files passed, 3 skipped; 3538 tests.
  • Build and type-check.
    • The full workspace build with type declarations (turbo run build --filter='!@objectstack/docs'): 118 / 118. It caught one gap that the skip-dts runs had not: loadMarketplaceCatalogue indexed the tag-typed loader table with a string. e94c13c8 makes hasMarketplaceCatalogue a type guard.
    • turbo run type-check --filter='...[origin/main]': 101 / 101. ui type-check:coverage, :e2e, :scripts, :vitest-setup and :vitest-config: exit 0.
  • Gates.
    • The 76 derived dispatch gates exit 0 on e94c13c8. dispatch-gates --ran reads 76 derived, 76 run, 0 NOT-MEASURED, 0 UNRUN.
    • ui check:eager-closure, check:eager-locale-catalogues, check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:spec-symbols, check:phantom-deps, check:unreferenced-sources, check:sdui-registration-pins and node scripts/check-sdui-manifest.mjs: exit 0.
    • After the Console's build, the i18n-locale-en chunk holds the single module en.ts, whose source is identical to main's; 57,625 gz bytes. No size ceiling is raised. zh-Hant is 62,336 gz bytes, and the marketplace zh-Hant catalogue 4,560, both loaded on demand.
    • check-adr-0087-registration --base origin/main and check-changeset-no-major --base origin/main: exit 0 on f7e9df2d, the latter also with this body as --event (level axis yes (narrowing) + none, every entry at patch).
    • eslint on the 144 changed ui sources: 0 errors.

Acceptance notes

  • Native read of zh-Hant. The conversion follows the site's rules, but a Traditional reader should still skim the pack. Renderings worth a look:

    • 儀表盤 (Taiwan usage is often 儀表板);
    • 只讀 (often 唯讀);
    • 釋出 and 發布 both appearing for "publish";
    • 後臺 / 啟動臺 (臺 vs 台);
    • 會話 for "session" (Taiwan usage is often 工作階段), and 退出登入 (often 登出);
    • the curly quotes “…” kept from Simplified (Taiwan usage is often 「…」).

    A rule change goes in scripts/zh-hant.mjs and in the site's converter, never in a hand edit of the pack.

  • Studio. The Studio namespace still ships en and zh. A zh-Hant or zh-TW reader sees Studio's Simplified catalogue (ruling app-shell: a published library that is 66% Console product surface — retire the 67 zero-reference exports now, decide the split, and fold the second i18n table into @object-ui/i18n #25922's namespace, untouched here).

  • The language menu (phase 4). LocaleSwitcher is not edited. Its hand-kept names are keyed zh and pt, so Intl.DisplayNames names the three tags they no longer match: 简体中文, 繁體中文 and Português (Brasil). The menu gains 繁體中文 as an eleventh entry.

  • Inline locale maps. pickLocalized is unchanged. Take a metadata label written { 'zh-CN': …, 'zh-TW': … }: a zh-Hant reader takes the first zh- key. A zh-TW-browser reader did the same before this PR, because that browser's language then resolved to zh. Teaching pickLocalized the table is a follow-up candidate, not done here.

  • One ladder. The Console's sys_user.locale pick and the server's resolveLocale now share resolveLocaleAmong, so the stored-locale ladder has one copy. The server's catalogues are its own and are not touched here.

  • ADR-0087. The changeset holds not-required (no-migration-prescription), and its marker names why.

    • The FROM → TO lines are source rewrites for consumers of the ./locales subpath, which is the class runtime-export-retired serves.
    • But that category reads only a package's root entry and refuses a subpath operand, and the root entry retires nothing here.
    • The seat may prefer to raise the gap.
  • Studio keys. fix(app-shell): word the flow closure refusal on the Problems list in the Console's language #30721's studio keys came in with the merge unchanged; Studio stays en + zh, so nothing in it is retagged.

  • Unchanged on purpose. The root ci.yml step is still named "followed by the nine translations", because the workflow is not edited. The eager-locale gate keeps its explicit list, now pinned to the table by its test.

Verification

Two minutes, from ui/:

  1. pnpm exec vitest run packages/i18n/: everything passes.
  2. Delete the 'zh-Hant': zhHant, line from packages/i18n/src/locales/index.ts and run pnpm exec vitest run packages/i18n/src/__tests__/all-locales-key-parity.test.ts: red, naming zh-Hant.
  3. pnpm --filter @object-ui/i18n gen:zh-hant: writes the same two zh-Hant.ts files (the pack and the marketplace catalogue) that are committed, so git diff stays empty.

A sample of the generated pack, with Taiwan vocabulary from s2twp:

zh-Hans.ts zh-Hant.ts
stop: '结束模拟登录' stop: '結束模擬登入'
manage_users: '管理用户' manage_users: '管理使用者'
manage_metadata: '管理元数据' manage_metadata: '管理中繼資料'
manage_platform_settings: '管理平台设置' manage_platform_settings: '管理平台設定'
manage_connector_connections: '管理连接器连接' manage_connector_connections: '管理聯結器連線'
save: '保存' save: '儲存'

claude added 17 commits November 5, 2026 14:35
…pack, key packs by table tag

The Console's language packs are now the platform locale table
(PLATFORM_LOCALES in @objectstack/spec/system): one pack per tag, keyed by
the tag (zh-Hans, zh-Hant, pt-BR). zh-Hant is generated from zh-Hans by
scripts/gen-zh-hant.mjs (OpenCC s2twp, pinned at 1.3.1, with the
www.objectos.ai overrides plus five straddle repairs found reading the pack).

Stored and browser codes keep resolving through the table's read aliases:
resolveBuiltInLanguage (new export) reads zh/zh-CN as zh-Hans, zh-TW/zh-HK
as zh-Hant, pt as pt-BR, then each shorter prefix; createI18n's fallbackLng
maps every alias to its tag. The app-shell splash, outbound agent text and
flow permission line read the snapshot by tag.

The parity, outbound, eager-catalogue and en-drift checks read the table;
en-drift reads a follower pack absent at the base as new.

Claude-Session: https://claude.ai/code/session_01L6bhgThgWPADcpU1xXCBsw
Co-authored-by: Claude <noreply@anthropic.com>
…pack, key packs by table tag

The Console's language packs are now the platform locale table
(PLATFORM_LOCALES in @objectstack/spec/system): one pack per tag, keyed by
the tag (zh-Hans, zh-Hant, pt-BR). zh-Hant is generated from zh-Hans by
scripts/gen-zh-hant.mjs (OpenCC s2twp, pinned at 1.3.1, with the
www.objectos.ai overrides, four straddle repairs and one phrase override
found reading the pack).

Stored and browser codes keep resolving through the table's read aliases:
resolveBuiltInLanguage (new export) reads zh/zh-CN as zh-Hans, zh-TW/zh-HK
as zh-Hant, pt as pt-BR, then each shorter prefix; createI18n's fallbackLng
maps every alias to its tag. The app-shell splash, outbound agent text and
flow permission line read the snapshot by tag.

The parity, outbound, eager-catalogue and en-drift checks read the table;
en-drift reads a follower pack absent at the base as new.

Claude-Session: https://claude.ai/code/session_01L6bhgThgWPADcpU1xXCBsw
Co-authored-by: Claude <noreply@anthropic.com>
…package

The suites outside ui/packages/i18n that hard-coded ten packs, a zh / pt
key, a 'zh' pack lookup or the menu's 中文 entry now read the platform
locale table or the tag.

Three readers keyed by the old codes are fixed with them. The provider
wrote a booted alias's catalogue (zh, zh-TW, pt) a second time under the
alias, an added event that re-rendered every reader and refetched every
chart for a catalogue already resident; it now writes under the tag. A
stored zh-TW locale took the first offered zh variant (zh-Hans);
pickRenderableLocale now takes the first offered code the table reads as
the same language first. The page-tab label dictionary served its
Simplified rows to every non-exact zh session; a zh-Hant, zh-HK or zh-MO
session reads its Traditional rows.

The en-drift fixtures write a const that is not named by the tag, and a
new describe pins the base-absent follower rule. The generated zh-Hant
pack is recorded as not driver-managed in the merge-driver ledger, and
the converter's declaration twin joins the package's test program. The
changeset declares the change.

Claude-Session: https://claude.ai/code/session_01L6bhgThgWPADcpU1xXCBsw
Co-authored-by: Claude <noreply@anthropic.com>
Main's pack edits to zh.ts and pt.ts land in zh-Hans.ts and pt-BR.ts;
zh-Hant.ts is regenerated from the merged zh-Hans.ts. The two conflicted
tests take main's CJK-Latin spacing cases under the zh-Hans tag, and a
case pins the spacing for both Chinese packs booted by their tags.

Claude-Session: https://claude.ai/code/session_01L6bhgThgWPADcpU1xXCBsw
Co-authored-by: Claude <noreply@anthropic.com>
The suite types its language by the keys of builtInLocales, which are now
the platform locale table's tags, so its zh cases name the zh-Hans pack.

Claude-Session: https://claude.ai/code/session_01L6bhgThgWPADcpU1xXCBsw
Co-authored-by: Claude <noreply@anthropic.com>
…er's twin

A doc link and a test comment named the old zh.ts path; they name
zh-Hans.ts. The nested-layout drift case pinned that a pack missing at
the base throws; under the follower rule only en does, and a follower the
base lacks reads as empty and is named, so the case pins both.

The lint-rule-coverage ledger declares the converter's declaration twin,
packages/i18n/scripts/zh-hant.d.mts, beside the two script twins it
already declares, and its tests accept an objectstack card on a row.

Claude-Session: https://claude.ai/code/session_01L6bhgThgWPADcpU1xXCBsw
Co-authored-by: Claude <noreply@anthropic.com>
34c9ccb is the first commit as pushed before its message was reworded
(c857810 has the same tree). Merged with the ours strategy so the branch
fast-forwards from what is already on the remote; no file changes.

Claude-Session: https://claude.ai/code/session_01L6bhgThgWPADcpU1xXCBsw
Co-authored-by: Claude <noreply@anthropic.com>
Merges main through the lazy marketplace namespace. Its catalogues move
to platform locale table tags with the packs (zh -> zh-Hans, pt -> pt-BR),
a zh-Hant catalogue is generated from zh-Hans by gen:zh-hant (now one list
of targets: the pack and each pack-shaped namespace), the namespace's
loaders are typed on the table, and the all-packs door merges each tag's
pack with its catalogue. The en-drift reader keeps both the follower-pack
excuse and the namespace partial packs.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyWSiXh6B2sMVHfXYC
The marketplace shape, lazy-load and console gate suites name the
zh-Hans / zh-Hant / pt-BR catalogues and packs; a stored zh-TW reader is
pinned to the Traditional catalogue; the freshness suite holds every
zh-Hant target the generator writes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyWSiXh6B2sMVHfXYC
pickRenderableLocale now calls resolveLocaleAmong from
@objectstack/spec/system, the ladder the server's resolveLocale runs,
instead of keeping a copy of it with its own table step. The table's read
aliases already name the script-subtag spellings, so a zh-Hant-TW row
still renders Traditional; a variant the table reads as the language
itself (es-ES) is now taken ahead of the first offered variant, as the
server takes it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyWSiXh6B2sMVHfXYC
The docs chrome and membership filter chip cases that main added read a
pack as builtInLocales.zh; they read zh-Hans (and the docs chrome also
zh-Hant) now. The chip cases keep booting the page in the stored zh
code, so they also pin that the alias still renders Simplified.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyWSiXh6B2sMVHfXYC
…indexing its loader

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyWSiXh6B2sMVHfXYC
@github-actions github-actions Bot added size/xl dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
… declaration, so importing the gate runs nothing

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyWSiXh6B2sMVHfXYC
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f7e9df2dc7d58acf9fe82a92da1028ae691fc904
Local-runs: none
Scope: every path of the diff outside the prose faces (the changeset .changeset/30511-console-packs-platform-locale-tags.md is read for ②, not reviewed; no content/docs/** or apps/docs/** path is in the diff) — pnpm-lock.yaml, scripts/regen-artifacts.mjs, ui/apps/console/vite.config.ts, ui/packages/i18n/** (package.json, README.md, tsconfig.test.json, scripts/{gen-zh-hant.mjs,zh-hant.mjs,zh-hant.d.mts}, src/{i18n.ts,index.ts,provider.tsx}, src/locales/{index.ts,registry.ts,zh-Hans.ts,zh-Hant.ts,pt-BR.ts}, src/namespaces/marketplace/{index.ts,zh-Hans.ts,zh-Hant.ts,pt-BR.ts}, 70 src/__tests__/*), ui/packages/app-shell/src/{chrome/LoadingScreen.tsx,console/ai/outboundAgentText.ts,utils/flowResponse.ts,hooks/useUserLocale.ts}, ui/packages/components/src/renderers/layout/containers.tsx, ui/scripts/{check-eager-locale-catalogues,check-i18n-dead-keys,check-i18n-en-drift,check-lint-rule-coverage}.mjs and their __tests__, ui/packages/plugin-gantt/docs/verification/README.md, and the ~60 test files under ui/apps/console, ui/packages/{app-shell,components,fields,plugin-*} + cited: packages/spec/src/system/platform-locales.ts (the published table, PLATFORM_LOCALE_READ_ALIASES, platformLocaleTagOf), packages/spec/src/system/i18n-resolver.ts (resolveLocaleAmong), packages/platform-objects/src/identity/sys-user.object.ts (SYS_USER_LOCALE_TAG_PATTERN), ui/packages/app-shell/src/layout/LocaleSwitcher.tsx, ui/apps/console/src/pages/system/ProfilePage.tsx, ui/packages/i18n/src/namespaces/studio/index.ts, ui/packages/i18n/src/pickLocalized.ts, ui/packages/components/src/lib/date-fns-locale.ts, skills/objectui/guides/i18n.md. The lockfile and the two generated zh-Hant.ts catalogues are read for shape (header, line alignment, export default zhHant), not reviewed line by line.

① Derived judgments

Public surface, @object-ui/i18n (exports map unchanged: ., ./locales, ./locales/*, ./namespaces/studio, ./namespaces/marketplace):

  • . entry gains resolveBuiltInLanguage(code): string | null — RIGHT (widening; the one resolver every reader keyed by the old codes now goes through).
  • ./locales entry: builtInLocales keys are the table's tags in table order (zh → zh-Hans, pt → pt-BR, zh-Hant added), satisfies a Record keyed by PlatformLocaleTag; named exports zh/pt KEPT as identifiers (now the zh-Hans/pt-BR catalogues), zhHant added — RIGHT (narrowing of the key set, declared in the changeset with FROM → TO; the identifier exports are deliberately not renamed, which the header says).
  • ./locales/*: @object-ui/i18n/locales/zh and /pt no longer resolve (files renamed); /zh-Hans, /zh-Hant, /pt-BR new — RIGHT, declared, migration lines present.
  • BUILT_IN_LANGUAGE_CODES is [...PLATFORM_LOCALE_TAGS] (11 entries, zh/pt gone); type readonly string[] unchanged — RIGHT, declared.
  • getLoadedBuiltInLocales() keyed by tag; loadBuiltInLocale / isBuiltInLocaleLoaded / isBuiltInLanguage accept every table tag, every read alias, and any longer tag by prefix walk (zh-Hant-TW, fr-CA, pt-PT → pt-BR) — RIGHT: the accept set is a superset of main's (exact-then-base), and pt-PT→pt-BR preserves main's base-language behaviour; the spec's "pt-PT is not pt-BR" is a statement about platformLocaleTagOf, whose doc itself separates "means" from "serves", and the server's resolveLocaleAmong steps 4–5 serve pt-PT with pt-BR too, so Console and server agree.
  • matchLanguageTag unchanged; now documented as the generic matcher for non-table sets (provider seed, studio) — RIGHT.
  • pickInitialLanguage (exported) answers a TAG for a built-in match (zh-TW → zh-Hant, pt-PT → pt-BR) instead of the base language; an app resources key by base language still wins — RIGHT, and the only way a Taiwan browser reaches Traditional; it is a behavioural change on a public function and the changeset's "stored codes keep working" paragraph covers it only implicitly (see ② note).
  • createI18n fallbackLng becomes an object: each tag → [fallback], each read alias → [tag, fallback], plus default — RIGHT; i18next's object lookup (code, script part, formatted code, language part, default) reaches zh-Hant-TW → zh-Hant and pt-PT → pt → pt-BR as the docblock says; an instance in a stored alias renders its tag's bundle without a second copy.
  • I18nProvider boot effect and changeLanguage write a built-in catalogue under its TAG, never the alias (the double-write/added re-render fix, pinned in provider-load-rerender-10382) — RIGHT; the instance language, the document lang attribute and the stored value stay what the user had (zh-CN, zh-TW).
  • ./namespaces/marketplace: MARKETPLACE_LOADERS typed as a Record keyed by PlatformLocaleTag, MARKETPLACE_LANGUAGE_CODES are tags, resolveMarketplaceLanguage goes through resolveBuiltInLanguage, hasMarketplaceCatalogue is a type guard, catalogue stored under the tag — RIGHT, consistent with the packs.
  • ./namespaces/studio untouched: en + zh, matchLanguageTag base match; a zh-Hans/zh-Hant/zh-TW instance reaches zh through i18next's language-part step — RIGHT per ruling app-shell: a published library that is 66% Console product surface — retire the 67 zero-reference exports now, decide the split, and fold the second i18n table into @object-ui/i18n #25922 (Traditional readers get Simplified Studio strings; declared in the changeset).

@object-ui/app-shell: LoadingScreen, outboundAgentText (PACK_TAG zh → zh-Hans), flowResponse read the snapshot by resolveBuiltInLanguage — RIGHT. pickRenderableLocale = resolveLocaleAmong (spec), so the Console's sys_user.locale pick has the server's table step (zh-TW offered zh-Hans+zh-Hant → zh-Hant; es offered es-MX,es-ES → es-ES) — RIGHT, and it closes spec#3's note 6665575988. Exported surface of app-shell (hasSignInLanguagePick, saveSignInLanguagePick, LocaleSwitcher) unchanged. LocaleSwitcher itself is not edited: it enumerates offerableLanguages, so the menu now offers zh-Hans, zh-Hant, pt-BR, labelled by Intl.DisplayNames because the hand-kept zh/pt label rows no longer match — RIGHT for phase 2 (phase 4 owns the menu), see ④ S2.

@object-ui/components: translateLabel picks the Traditional dictionary rows for any locale resolveBuiltInLanguage reads as zh-Hant (zh-Hant, zh-HK, zh-MO), Simplified otherwise — RIGHT. date-fns-locale.ts (unchanged, read) resolves zh-Hans → zh-CN, zh-Hant → zh-TW, pt-BR exact through Intl.Locale.maximize() — no regression.

@object-ui/console: vite.config.ts one i18n-locale-TAG / i18n-ns-marketplace-TAG group per table tag — RIGHT (the eager gate reads these names).

Gates (edits to existing checks, no new check:*, per ruling F):

  • check-i18n-en-drift.mjs: LOCALES = [...PLATFORM_LOCALE_TAGS]; a FOLLOWER pack absent at the base reads as empty and is named; en absent at base, or any pack absent at HEAD, still throws — RIGHT (the renamed/new packs' keys are a key-set fact the parity test owns; the excuse is before-side only and pinned both ways in check-i18n-en-drift.test.ts and git-paths-nested-layout.test.ts). The en-first assertion at lines 194–196 is WRONG as spelled: a top-level if (LOCALES[0] !== 'en') { throw … } in a scripts/** module that exports bindings runs on import, which is exactly what ui/scripts/check-entry-guard.mjs refuses ("export bindings AND run on import"); the head's Lint UI job reds on it (④ M1). The assertion belongs inside a declaration (a function the LOCALES initialiser calls), not at module top.
  • check-i18n-dead-keys.mjs: map-chain tag stripping reads PLATFORM_LOCALE_TAGS (PACK_MAP_TAGS), PACK_EXPORT_NAMES gains zhHant — RIGHT (previously it stripped export identifiers as if they were tags; the two sets are now distinct).
  • check-eager-locale-catalogues.mjs: explicit 11-tag list kept on purpose; its test pins the list to the table AND to the registry's loader table, and pins that each loader imports the file named for its tag — RIGHT.
  • check-lint-rule-coverage.mjs: UNREACHED_GROUPS row for packages/i18n/scripts/zh-hant.d.mts, card objectstack#30511; tests accept an objectstack# card — RIGHT.
  • scripts/regen-artifacts.mjs: zh-Hant.ts as NOT_DRIVER_MANAGED with gen:zh-hant — RIGHT (no check: currency proof exists, so a REGEN_ARTIFACTS row would be wrong; the package test is the proof). Note the marketplace zh-Hant.ts catalogue is NOT listed there — minor, see ④ S5.

Tests that make the card's ask real: all-locales-key-parity pins Object.keys(builtInLocales) equal to PLATFORM_LOCALE_TAGS (a table tag with no pack reds by name); region-locale-fallback-11326 covers both scripts with a probe key spelled differently in each pack (保存/儲存) and fresh module graphs; zh-hant-generated is byte-for-byte over ZH_HANT_TARGETS with a counter-probe on the list; userLocale-10059 pins the table step. the satisfies and Record-keyed-by-PlatformLocaleTag typing makes a table drift a compile error in two places. RIGHT.

Generator: zh-hant.mjs ports the site's s2twp + TWP_PHRASE_OVERRIDES + LOCATIVE_LI, adds STRADDLE_OVERRIDES (segmentation + group 0) and one phrase override, refuses to write when a pinned CONVERSION_CASES entry fails, keeps the six GATE_ONLY_KEYS out of the pack as comments so line numbers stay aligned (5034 lines both files). opencc-js pinned 1.3.1 as a devDependency; the converter never ships. RIGHT.

Head check-runs on f7e9df2d (the gate verdicts, all completed): Lint UI — failure, step "Verify every ui scripts entry guard goes through one predicate": check:entry-guard names scripts/check-i18n-en-drift.mjs:194 (if () {} at module top in a file that exports bindings). Test UI (15/16) — cancelled ("a higher priority waiting request for ci-CI-pull_request-30902 exists": the PR received a newer push, 143ef31d, at 10:25Z and the concurrency group pre-empted this shard), so the aggregate Test UI reads failure and the CI workflow run reads cancelled; shard 15 is UNMEASURED on this head, not red. Everything else is green: Test UI shards 1–14 and 16, Test Core (1/8, 2/8, aggregate), TypeScript Type Check, Type Check UI, Type Check · workspace / consumer gates / source gates / debt ledger, Build Core, Build UI Deps, Build & E2E UI, Live E2E UI, Build Docs, Lint & Repo Gates, Lint · ESLint, every scoped Lint, Lint · PM dispatch-gates, Lint · docs & code guards, Check Changeset, Governed Surface Queue Guard, Check PR Size, PR-body guards, Validate Package Dependencies, Dogfood Regression Gate; the opt-in/scheduled jobs are skipped. One derived gate is red on this head, so the gate verdict is red.

② Semver level

Clause-②: yes (narrowing) on both the PR body and the changeset body, line-initial, with the widening and the narrowing each spelled out; Metadata-protocol: none — correct: no spec key, metadata type or authorable value changes. Level: patch on @object-ui/i18n, @object-ui/app-shell, @object-ui/components, @object-ui/console — the four released packages whose shipped code the diff changes (fields and the plugin-* packages carry test edits only). Per AGENTS.md "Add a changeset": kind none ⇒ patch, and a non-protocol breaking change ships patch with its BREAKING banner, FROM → TO migration (five lines: the two builtInLocales members, the two ./locales/* modules, and the code-comparison rewrite) and exactly one ADR-0087 marker — all present; marker not-required (no-migration-prescription) is a category the gate knows (check-adr-0087-registration.mjs) and the reason given (the rename is a source rewrite on a subpath that runtime-export-retired cannot take as operand; root entry retires nothing) is true of the tree. Check Changeset is green on the head. Should-note, not a defect: the changeset's "Stored codes keep working" paragraph does not name pickInitialLanguage's changed answer (zh → zh-Hans for a Chinese browser with no stored choice); it is implied by "a browser code … read through the table's aliases".

Clause-②: yes (narrowing) — the record agrees with the declaration; patch is the right level. The FAIL below is a gate finding (④ M1), not a semver one.

③ Boundary flags

os-dev-report 6682318861 carries no open_questions key and no dev flag; what it does raise:

Nothing escalated.

④ Design judgment

The narrowing against the published spec on main (packages/spec/src/system/platform-locales.ts): the table declares (a) one pack per tag on every surface, (b) stored values written before the table keep resolving through PLATFORM_LOCALE_READ_ALIASES, (c) new writes use the table's tags, (d) a closed alias list with no guessed region/script fallback in platformLocaleTagOf itself, and (e) a label per row for language menus. This diff makes (a) real for the Console (the parity suite and two Record-keyed-by-PlatformLocaleTag types turn a table/pack mismatch red), honours (b) at every reader found (registry, i18next fallbackLng, provider boot and switch, marketplace namespace, LoadingScreen, outboundAgentText, flowResponse, translateLabel, pickRenderableLocale), and does (c) at the two write sites (LocaleSwitcher → useLanguageSelection → sys_user.locale, and the device slot), where the server's locale_bcp47_shape rule (SYS_USER_LOCALE_TAG_PATTERN) accepts zh-Hans/zh-Hant/pt-BR and the server's resolveLocaleAmong table step serves a stored zh-Hans from its zh-CN catalogue. Nothing a consumer could do on main is taken away: builtInLocales.zh / .pt and the two module paths are the only removals, each with a one-line rewrite, and every code that used to load a pack still loads one (a strict superset). The Console's prefix walk beyond the closed alias list (pt-PT → pt-BR, fr-CA → fr) does not contradict (d): the spec separates "which tag a code MEANS" from "which key SERVES it", and the serving ladder it publishes (resolveLocaleAmong steps 4–5) answers pt-PT with pt-BR as well. Attempts to break it: a stored zh/zh-CN/pt row (alias → tag; pinned); a cached device choice zh (isStaticallyKnownLanguage → isBuiltInLanguage('zh') true, so it is kept and renders zh-Hans through fallbackLng); a zh-TW browser with no choice (pickInitialLanguage → zh-Hant, previously zh); a zh-Hant-HK browser (alias); a deployment offering zh-CN+zh-TW for a stored zh-HK (zh-TW, via the table step); an instance in zh-TW whose zh-Hant catalogue lands after mount (reaches it, pinned); the marketplace lazy loader for a zh-TW session (tag-typed, zh-Hant); a zh-Hans date picker (date-fns zh-CN via maximize()); Accept-Language: zh-Hant to a server with zh-CN only (variant step → Simplified, the declared Studio/server state). The narrowing itself holds; the one must-fix is a gate the head reds, not the design.

Must-fix (a FAIL on this head):

  • M1 ui/scripts/check-i18n-en-drift.mjs:194–196 — the en-first assertion is a top-level if … throw in a scripts/** module that exports LOCALES, FOLLOWERS, readPacks, analyze and more, so importing the gate for its exports (its own test does; check-entry-guard's rule is that a top-level process.exit/throw inside an importer is read as the importer's result) runs code on import. ui/scripts/check-entry-guard.mjs refuses it and the head's Lint UI check-run is red on exactly this line. Fix: move the assertion into a declaration — e.g. export const LOCALES = Object.freeze(enFirstTableTags(PLATFORM_LOCALE_TAGS)) with the throw inside enFirstTableTags — so the module's top level only declares. (The PR has since received 143ef31d, a one-file commit that does this; it is not the head this record names, and the seat re-renders the record there, with Test UI (15/16) measured.)

Should-fix (none blocks a corrected head):

  • S1 pickLocalized / localeWriteKey (ui/packages/i18n/src/pickLocalized.ts, unchanged): a zh-Hant session reading an inline map { 'zh-CN': …, 'zh-TW': … } takes the first zh-* key (Simplified) and an edit in that session writes back to that same zh-CN entry. Not a regression (a zh-TW browser resolved to zh on main and did the same; a stored zh-TW still exact-matches), but after this PR the chrome is Traditional while authored labels are Simplified. The dev named it "follow-up candidate, not filed": file the card (teach both limbs the table's reading) before phase 4 lands.
  • S2 LocaleSwitcher.tsx BUILT_IN_LABELS rows zh and pt are dead after this PR; 简体中文, 繁體中文, Português (Brasil) now come from Intl.DisplayNames, the ICU-dependent source the file's own header says it avoids. Phase 4 owns the menu; it should read PLATFORM_LOCALES[].label (the table carries exactly these names) and drop the hand map. ProfilePage.tsx already derives names and is unaffected.
  • S3 skills/objectui/guides/i18n.md teaches ten packs with zh and pt — after this lands it teaches a retired key and module path. Governed Tier H, so a separate card/PR; file it before landing so the follow-up is a tracked card, not a line in a changeset.
  • S4 Server catalogues (packages/core memory-i18n) and the studio namespace carry no zh-Hant; a Traditional Console shows Simplified server-translated labels and Studio strings. Both are declared (changeset, PR body, ruling app-shell: a published library that is 66% Console product surface — retire the 67 zero-reference exports now, decide the split, and fold the second i18n table into @object-ui/i18n #25922) and out of this card's lane; the tracking card Platform locale table (ruling F of #29349): one list of 11 languages in spec, complete on every surface #29428 should carry the server backfill as its own phase.
  • S5 scripts/regen-artifacts.mjs lists only ui/packages/i18n/src/locales/zh-Hant.ts as NOT_DRIVER_MANAGED; ui/packages/i18n/src/namespaces/marketplace/zh-Hant.ts is generated by the same command and belongs in the same ledger entry (or a second row), so the merge driver treats both alike.

Implemented-by: claude/project-thread-f2dhrw
Reviewed-by: session_01L9qibw7DekEe3wx5f3zphf

VERDICT: FAIL

…talogue beside the pack's

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyWSiXh6B2sMVHfXYC
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c1f166cc40e17e5a1c069137a6ffa32897da33fb
Local-runs: none
Scope: the two files the delta since f7e9df2d touches — ui/scripts/check-i18n-en-drift.mjs, scripts/regen-artifacts.mjs — plus, carried forward unchanged from record 6682790803 on f7e9df2d, every path of the diff outside the prose faces (152 files, identical file list, same merge base 98f640d5; net diff vs main is now +7673/−824, i.e. the reviewed diff plus the +20/−3 below) + cited: ui/scripts/check-entry-guard.mjs (the gate M1 answered to), packages/spec/src/system/platform-locales.ts, ui/packages/i18n/scripts/zh-hant.mjs (ZH_HANT_TARGETS), ui/packages/i18n/src/__tests__/zh-hant-generated.test.ts; nothing else re-read, nothing else needed.

① Derived judgments

Delta only; every judgment in 6682790803 ① stands for the 150 untouched files and is carried forward by reference.

  • ui/scripts/check-i18n-en-drift.mjs (commit 143ef31d, +8/−3): the top-level if (LOCALES[0] !== 'en') { throw … } is gone; LOCALES = Object.freeze(enFirstTableTags(PLATFORM_LOCALE_TAGS)) with the en-first refusal inside the hoisted function enFirstTableTags. The module's top level now only declares, which is the shape check-entry-guard.mjs demands of an exporting scripts/** file; the assertion still fires at import on a table that does not open with en, so the gate's own invariant is kept — RIGHT. No export added or removed (LOCALES, FOLLOWERS, readPacks, analyze and the CLI unchanged); the follower-absent-at-base rule and its tests are untouched.
  • scripts/regen-artifacts.mjs (commit c1f166cc, +12/−0): a second NOT_DRIVER_MANAGED row for ui/packages/i18n/src/namespaces/marketplace/zh-Hant.ts, same gen: 'gen:zh-hant', same owner, same currency-proof reasoning (the package test walks ZH_HANT_TARGETS, which names exactly these two targets) — RIGHT; closes S5 of 6682790803.
  • No public surface, accept set, gate population or test moved beyond these two hunks (git diff f7e9df2d c1f166cc is these two files only).

Head check-runs on c1f166cc (the gate verdicts): all 57 completed; every one is success or skipped (the skipped ones are the opt-in/scheduled jobs: Temporal Conformance, UI Published Artifacts, Dogfood Verify CLI, the dogfood shard matrix, Schedule gate, Packed-tarball smoke, 18.x upgrade harness). Green includes the two that were not green on f7e9df2d: Lint UI (so check:entry-guard accepts the new shape — M1 closed by the gate that raised it) and Test UI (15/16) (measured this time, with shards 1–14 and 16 and the Test UI aggregate), plus Test Core (1/8, 2/8, aggregate), TypeScript Type Check, Type Check UI, Type Check · workspace / consumer gates / source gates / debt ledger, Build Core, Build UI Deps, Build & E2E UI, Live E2E UI, Build Docs, Lint & Repo Gates, Lint · ESLint, every scoped Lint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · CI/release/engine gates, Check Changeset, Governed Surface Queue Guard, Check PR Size, PR-body guards, Validate Package Dependencies, Dogfood Regression Gate, Auto Label, filter. The gate verdict on this head is green.

② Semver level

Unchanged from 6682790803 ②: Clause-②: yes (narrowing) and Metadata-protocol: none on PR body and changeset; patch on @object-ui/i18n, @object-ui/app-shell, @object-ui/components, @object-ui/console; BREAKING banner, five FROM → TO lines, one ADR-0087 marker not-required (no-migration-prescription). The delta touches a ui gate script and a root tooling ledger, neither published, so the changeset needs no edit and got none. Check Changeset green on this head.

Clause-②: yes (narrowing) — carried forward; patch remains the right level.

③ Boundary flags

No new os-dev-report on the card since 6682318861; the two delta commits answer M1 and S5 of 6682790803 and raise no flag of their own. Everything else in 6682790803 ③ stands: S3 (skills/objectui/guides/i18n.md follow-up card to be FILED before landing), S1 (pickLocalized card), Tier H approval via os-zhuang, close #30595 at landing. Nothing escalated.

④ Design judgment

The narrowing itself is unchanged from f7e9df2d and the judgment in 6682790803 ④ carries forward: no declared capability of the published locale table is taken away at the consumer; stored and browser codes resolve through the table's read aliases; new writes are table tags the server accepts and serves. On the delta: M1 is closed in the tree (the only top-level statement that ran on import is now inside a declaration) and the head's Lint UI conclusion is the proof of record; S5 is closed. Remaining should-fix, none blocking, renumbered from 6682790803: S1 pickLocalized/localeWriteKey zh-Hant inline maps (file the card before phase 4); S2 LocaleSwitcher dead zh/pt label rows (phase 4 reads PLATFORM_LOCALES[].label); S3 skills/objectui/guides/i18n.md (Tier H, separate card, filed before landing); S4 server catalogues and studio namespace without zh-Hant (declared; tracking card #29428). No must-fix on this head.

Implemented-by: claude/project-thread-f2dhrw
Reviewed-by: session_01L9qibw7DekEe3wx5f3zphf

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf

席位核对 · ACCEPT · 2026-11-06T11:07Z · head c1f166cc40e17e5a1c069137a6ffa32897da33fb

Seat domain:ui#2 reviewed the diff of its own dispatch (claim 6677798360, amended 6678228167, on #30511; card thread session_01L9qibyWSiXh6B2sMVHfXYC, report 6682318861; the code is the thread's, resumed from #30595's branch).

  1. Shape: 152 files, +7,673 / -824. @object-ui/i18n builds its packs from the platform locale table: pack keys and ./locales/* modules move zh → zh-Hans, pt → pt-BR; zh-Hant is generated from Simplified (scripts/zh-hant.mjs, gen:zh-hant); resolveBuiltInLanguage (new) is the one resolver; the lazy marketplace namespace splits by tag too. Part of #30511, Supersedes #30595 (closed 6682341949).
  2. Premise measured: ruling F ([Decision] One locale list, one completeness rule — the long-term i18n regime across Console, platform metadata, cloud and mobile #29349) puts the console's language set on the platform table; before this, Taiwan / Hong Kong users got Simplified. Stored zh, zh-CN, pt keep resolving through the table's read aliases (contract review ④).
  3. Gates / check-runs: 57 check runs on this head, all success or skipped (Lint UI and Test UI 15/16 included). Report: 76 derived gates exit 0; ui suite 6,631 files; dogfood 424; type-check 101/101; eager-closure unchanged, i18n-locale-en the same module, no ceiling raise.
  4. Accepted deviation: the dev read feat(i18n)!: the Console's language packs follow the platform locale table, keyed by its tags, and Traditional Chinese (zh-Hant) ships #30595's body once through gh api rather than the MCP read tool (read-only).
  5. Out of scope / Skills-docs: Skills-docs: owed → skills/objectui i18n guide still teaches the zh and pt pack keys that #30511 retires #30911 (skills/objectui/guides/i18n.md, filed). A Traditional Chinese console reads and writes the Simplified entry of an inline label map #30910 (pickLocalized zh-Hant inline maps) filed. Server catalogues and the studio namespace without zh-Hant → Platform locale table (ruling F of #29349): one list of 11 languages in spec, complete on every surface #29428. LocaleSwitcher dead zh / pt rows → phase 4.
  6. Landing: 8,497 changed lines > 3,000 → Tier H by size: human approval owed. Clause-②: yes (narrowing): CONTRACT_REVIEW_TIER review by an isolated reviewer, FAIL on f7e9df2d (6682790803, M1 fixed by 143ef31d), delta PASS on this head (6683373168). Changeset: patch ×4, BREAKING banner, five FROM → TO lines, one ADR-0087 marker. Hand-off this stroke: pm:awaiting-merge, review requested from os-zhuang and hotlong (GOVERNED_APPROVERS). On approval the seat runs the pre-landing check, pr_ready, automerge_enable. Standing words: 「按 pm-dispatch 审过的pr你可以自主落地」 (2026-10-30T14:20Z), 「ui 车道跟进一下 3小时内没更新的pr和已派发的issue,接手处理。」 (2026-11-06T05:20Z).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue size/xl tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant