Repository navigation
feat(i18n)!: the Console's language packs follow the platform locale table, keyed by its tags, and Traditional Chinese (zh-Hant) ships - #30595
Closed
objectstack-fleet[bot] wants to merge 7 commits into
Closed
objectstack-fleet[bot] wants to merge 7 commits into
objectstack-fleet[bot] wants to merge 7 commits into
Conversation
…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>
This was referenced Nov 5, 2026
Closed
Contributor
Author
|
domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf Superseded by PR #30902 ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Sales · project thread
domain:ui#5 · os-sales · session_01L6bhgThgWPADcpU1xXCBsw
Part of #30511. Every item the card asks for is in this PR. The card stays open for the seat to close when this lands, and phase 4 (the locale menu) follows it.
Clause-②: yes (narrowing) — widens: a
zh-Hantpack (thezhHantexport of@object-ui/i18n/locales, the@object-ui/i18n/locales/zh-Hantmodule), theresolveBuiltInLanguageexport 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 keyedzh-Hansandpt-BR, no longerzhandpt, inbuiltInLocales,BUILT_IN_LANGUAGE_CODES,getLoadedBuiltInLocales()andgetAvailableLanguages(), and their modules@object-ui/i18n/locales/zhand@object-ui/i18n/locales/ptare@object-ui/i18n/locales/zh-Hansand@object-ui/i18n/locales/pt-BR.Metadata-protocol: none
Skills-docs: follow-up owed —
skills/objectui/guides/i18n.md(its "Built-in locales" list names ten packs, withzhandpt)维护者速读
zh→zh-Hans,pt→pt-BR)。新增繁体中文zh-Hant,由简体包经 OpenCC 分词转换器生成,规则照搬官网转换器,不手写。zh-Hant缺席,测试照样全绿。ui/,外加根目录scripts/regen-artifacts.mjs的一条台账,不动 spec 和服务端。ui/里涉及 i18n 包及其 provider、app-shell 三处按标签读包、页签标签字典、存值语言挑选、控制台分包配置、三个 i18n 门禁脚本,以及相应测试。zh-CN、zh、pt经表的别名照常渲染;zh-TW、zh-HK存值从简体变为繁体。builtInLocales.zh或@object-ui/i18n/locales/zh取包会断,changeset 写了 FROM → TO。ui/packages/i18n/src/locales/registry.ts:resolveBuiltInLanguage与按标签的加载表。ui/packages/i18n/src/provider.tsx:语言包按标签写入,别名语言不再重复写一份。ui/packages/i18n/scripts/zh-hant.mjs:转换规则与跨词边界修正。ui/packages/app-shell/src/hooks/useUserLocale.ts:pickRenderableLocale新增一步,按表读同一语言。pnpm --dir ui exec vitest run packages/i18n/,预期全部通过。builtInLocales里zh-Hant那一行再跑一次,all-locales-key-parity会转红并点名zh-Hant。What changes
The pack set comes from the platform locale table.
ui/packages/i18nno longer keeps its own list of ten languages. It readsPLATFORM_LOCALESfrom@objectstack/spec/system(phase 1, PR #30007).src/locales/zh.tsis nowzh-Hans.ts,pt.tsis nowpt-BR.ts, andzh-Hant.tsis new.builtInLocalesis keyed by table tag, in table order, andsatisfies 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_LOADERSis typed by the table's tags, andBUILT_IN_LANGUAGE_CODESisPLATFORM_LOCALE_TAGS.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).loadBuiltInLocaleandisBuiltInLanguagego through it, so every legacy code still loads its pack.i18n.ts.fallbackLngis 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 legacyzhorzh-CNrenders thezh-Hansresource.provider.tsx. The boot effect andchangeLanguagenow write a built-in catalogue under its table tag, the bundlecreateI18nkeyed it by.zh,zh-TW,pt) got the resident catalogue written a second time under the alias. That fired anaddedevent, which re-rendered every reader and refetched every chart keyed on the resolver.ObjectChart.refreshIndicatorLocale-10580found it: its zh chart never left the skeleton.zh-Hant is generated.
scripts/zh-hant.mjsis the converter. It uses:s2twp, withopencc-jspinned at 1.3.1 as a devDependency;scripts/gen-zh-hant.mjswritessrc/locales/zh-Hant.tsfromzh-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: 并发布 → 並發布, 头发送 → 頭發送, 是只读 → 是只讀, 只影响 → 只影響.s2twpsegments 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_OVERRIDESgains 操作運行時 → 操作執行時.Other details of the generated pack:
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.src/__tests__/zh-hant-generated.test.tsreds 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 acheck:*gate (ruling F adds none).zh-Hant.tsasNOT_DRIVER_MANAGED(gengen:zh-hant). The pack is generated whole, but aREGEN_ARTIFACTSrow needs acheck:script, and this phase adds none.zh-hant.d.mtstwin joins the package'stsconfig.test.json, socheck:type-check-coveragereads it.ui/scripts/check-lint-rule-coverage.mjsdeclares the same twin inUNREACHED_GROUPS, beside the two script twins it already declares. Its tests now accept anobjectstack#card on a ledger row as well as anobjectui#one.Readers keyed by the old codes.
app-shellreaders.LoadingScreen(the boot line),outboundAgentTextandflowResponseread the loaded-pack snapshot by table tag, throughresolveBuiltInLanguage.app-shellpickRenderableLocale(thesys_user.locale→ UI language pick). A new step before the base-language match takes the first offered code that the table reads as the same language. Without it, a storedzh-TWmet the built-in list and took its firstzhvariant,zh-Hans.componentspage tabs. The Traditional rows of the exact-locale label dictionary now serve azh-Hantsession, andzh-HK/zh-MOsessions too. Before, every non-exactzhfell to the Simplified rows.apps/console/vite.config.ts. One locale chunk per table tag.Gates read the table.
check-i18n-en-drift.mjstakes its locales fromPLATFORM_LOCALE_TAGS. A follower pack that the base commit does not have (this PR's renames andzh-Hant) reads as empty at the base and is named in the summary.enmissing at the base, or any pack missing at HEAD, still throws.check-i18n-dead-keys.mjsstrips map-chain tags by the table.check-eager-locale-catalogues.mjskeeps 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, whichcheck-doc-linksreads) and one test comment namedzh.ts. Both now namezh-Hans.ts.Tests
Suites that read the table. The parity, region-fallback, entry-shape and en-drift suites read the table.
all-locales-key-paritywalksPLATFORM_LOCALE_TAGS, so a table language with no pack reds it by name.region-locale-fallback-11326covers both scripts:zh-CNandzh-SGread Simplified;zh-TW,zh-HKandzh-MOread Traditional.zh-hant-generated.test.ts(new) covers:provider-load-rerender-10382.test.tsx. An instance booted inzh,zh-CN,zh-TWorptreads its tag's resident catalogue, renders once, and keeps no bundle under the alias. A catalogue that lands after mount reaches azh-TWreader.i18n.test.ts. Main's CJK-Latin title spacing (Console and CLI polish from the real-user walkthrough #30126) is pinned for bothzh-Hansandzh-Hantbooted by tag.check-i18n-en-drift.test.ts. Fixtures write a const namedpack, 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 onlyendoes, so the case now pins both halves:enmissing 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-MOandzh-Hant-TW→zh-Hant;zh,zh-CNandzh-SG→zh-Hans;pt→pt-BR;The pin "first offered variant" moved from
zhtoes, because the table reads a barezhas Simplified.page-tabs-builtin-label-i18n-4645.test.tsx.zh-Hant,zh-HKandzh-MOsessions read 詳情 / 備註 / 概覽, and two of those are dict-only tokens. Thezh-Hans/zh-SGcontrol reads Simplified.LocaleSwitcher suites (
appLocales,persistence,unsavedEditsAsk-13254). They pick and expect the menu's names for the table's tags throughlocaleLabel, 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/ptkey or a'zh'lookup. They now read the table or the tag. For example,toHaveLength(10)becamePLATFORM_LOCALE_TAGS.length, and where a table names every pack, a convertedzh-Hanttwin sits beside each retaggedzh-Hansrow.Readings (head
9b15e9ad, base75bbebb2)The runs below were taken on
9571f5e2.9b15e9adis a no-change merge (theoursstrategy) that records the earlier push of the first commit, so the tree is the same.zh-Hans,zh-Hantandpt-BRand the Console had no pack keyed by any of them, yetall-locales-key-parityandregion-locale-fallback-11326were green (2 files, 58 tests).git diff HEADempty.zh-Hantentry removed frombuiltInLocales→all-locales-key-parity3 failed / 42 passed, and the diff nameszh-Hant. Restored: 49 / 49.zh-Hansvalue edited without regenerating (保存→保存草稿) →zh-hant-generated1 failed / 5 passed: "zh-Hant.ts is stale: runpnpm --filter @object-ui/i18n gen:zh-hantand commit src/locales/zh-Hant.ts".provider-load-rerender-10382and the zh case ofObjectChart.refreshIndicatorLocale-10580. Restored: 14 / 14.packages/i18n/plus the 33 test files main added or changed since the branch point, run after the merge: 128 files, 1834 tests, green.zh,zh-CN,zh-TW,zh-HK,zh-SG,pt,pt-PT), on the final head: 775 files (774 passed, 1 skipped), 8827 tests.pnpm --dir ui exec vitest run scripts/on the final head: green.pnpm --filter @objectstack/dogfood test: 420 files passed, 3 skipped; 3496 tests.turbo run type-check --filter=...[75bbebb2]over the ui packages: 102 / 102. It caughtbooleanValueLabel.locale-11689, which9070a29ffixes.type-check:coverage,type-check:scripts,type-check:vitest-configandtype-check:e2e: exit 0.dispatch-gates --ranreads 75 run, 0 NOT-MEASURED, 0 UNRUN.check:i18n-keys,check:i18n-drift,check:spec-symbols,check:unreferenced-sourcesandcheck:phantom-deps: exit 0.check:eager-locale-cataloguesandcheck:eager-closureexit 0.i18n-locale-enis 59,579 gz bytes: the single moduleen.ts, byte-identical to the base.zh-Hantis 62.9 KB gz, loaded on demand.check:dts-closure: exit 0 after the Console'sbuild:plugin.check-changeset-no-major.mjswith this body as the event: exit 0. The level axis readsyes (narrowing)andnone, with every entry atpatch.check:nul-bytes: exit 0. eslint on the 136 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:
A rule change goes in
scripts/zh-hant.mjsand in the site's converter, never in a hand edit of the pack.Studio. The Studio namespace still ships
enandzh. Azh-Hantorzh-TWreader 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).
LocaleSwitcheris not edited. Its hand-kept names are keyedzhandpt, soIntl.DisplayNamesnames the three tags they no longer match: 简体中文, 繁體中文 and Português (Brasil). The menu gains 繁體中文 as an eleventh entry.Inline locale maps.
pickLocalizedis unchanged. Take a metadata label written{ 'zh-CN': …, 'zh-TW': … }: azh-Hantreader takes the firstzh-key. Azh-TW-browser reader did the same before this PR, because that browser's language then resolved tozh. TeachingpickLocalizedthe table is a follow-up candidate, not done here.Server parity.
pickRenderableLocaledocumented the server'sresolveLocaleorder (packages/corememory-i18n). Its new step is console-only, and the server's fallback does not read the table's aliases yet.ADR-0087. The changeset holds
not-required (no-migration-prescription), and its marker names why../localessubpath, which is the classruntime-export-retiredserves.Landing order. Console first load: the English language pack has 63 bytes left under its size ceiling, so any card that adds English console text fails CI #30588 moves the
marketplace.*subtree intoui/packages/i18n/src/namespaces/marketplace/and lands first. After it merges, this branch merges main, gives the new namespace itszh-Hans,zh-Hantandpt-BRfiles, and rerunsgen:zh-hant.Unchanged on purpose. The root
ci.ymlstep 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/:pnpm exec vitest run packages/i18n/: everything passes.'zh-Hant': zhHant,line frompackages/i18n/src/locales/index.tsand runpnpm exec vitest run packages/i18n/src/__tests__/all-locales-key-parity.test.ts: red, namingzh-Hant.pnpm --filter @object-ui/i18n gen:zh-hant: writes the samezh-Hant.tsthat is committed, sogit diffstays empty.A sample of the generated pack, with Taiwan vocabulary from
s2twp:zh-Hans.tszh-Hant.tsstop: '结束模拟登录'stop: '結束模擬登入'manage_users: '管理用户'manage_users: '管理使用者'manage_metadata: '管理元数据'manage_metadata: '管理中繼資料'manage_platform_settings: '管理平台设置'manage_platform_settings: '管理平台設定'manage_connector_connections: '管理连接器连接'manage_connector_connections: '管理聯結器連線'save: '保存'save: '儲存'🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgThgWPADcpU1xXCBsw