Skip to content

fix(i18n): Studio edits the Traditional Chinese entry in a Traditional session and never overwrites the other script - #31089

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-7el3c5
Nov 7, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-7el3c5

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #31074

Clause-②: no
Metadata-protocol: none

Clause-② is no: SupportedLocale gains 'zh-Hant', but it lives in @object-ui/app-shell, which is private and exports only its root, which does not carry that type. @object-ui/i18n and @objectstack/cli change behaviour, not their exports. Metadata-protocol is none: what an author can write is unchanged.

维护者速读

  • 改了什么:繁体中文(zh-Hant / zh-TW / zh-HK)会话进入 Studio 时,设计器语言不再一律按简体处理;导航设计器显示并修改的是侧边栏同样显示的繁体条目。只写了简体的标签,在繁体会话里修改时会新增一条繁体,不再覆盖简体原文。os i18n check 也把“繁体条目留空”算作缺翻译。
  • 为什么改:之前繁体管理员在 Studio 看到简体名称,改名会覆盖简体作者写的文字,繁体用户看到的条目却不更新;留空的繁体条目还被报告为“已翻译”。
  • 风险与回滚:只影响中文会话;简体会话、英文及其他语言的设计器行为与改前一致(有测试钉住)。Studio 的界面文字在繁体会话下沿用简体(studio 词库只有一份中文),这是有意的回退。回滚即还原本 PR。
  • 先看:ui/packages/app-shell/src/views/metadata-admin/i18n.ts 的 designerLocaleOf;ui/packages/i18n/src/pickLocalized.ts 的 localeWriteKey(共用写入分支的脚本检查);packages/cli/src/utils/i18n-coverage.ts 的 inlineLocaleText。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/useMetadataLocale.sessionScript-31074.test.tsx packages/i18n/src/__tests__/setLocalized.test.ts,预期全绿。
  • 需要你做的:无,席位落地。
  • 席位意见:ui 席 2 对照 head 410d25d6 的 diff 复核:15 个文件、+304 / −60,无受治理路径,不碰 packages/spec/src,不欠契约复审。Studio 设计器语言改为跟随会话书写:繁体会话(zh-Hant / zh-TW / zh-HK)按 zh-Hant 读写标签条目,界面文字仍用简体目录;写入侧遇到只有另一种书写的标签时新增本书写条目、不覆盖原文;CLI 覆盖率把渲染器实际显示的空条目算作缺口。现场三项读数与三组消融见正文。CI 全绿,同意落地。

What changes

  1. The designer locale follows the session script. useMetadataLocale (metadata-admin/i18n.ts) no longer collapses every zh* to 'zh-CN'. A new pure designerLocaleOf(language) resolves the session language through the console's own resolveBuiltInLanguage. A session the console serves in zh-Hant (zh-Hant, zh-TW, zh-HK, …) designs as 'zh-Hant', every other Chinese session designs as 'zh-CN', and anything else as 'en-US'. SupportedLocale gains 'zh-Hant'.
  2. An edit in a script the map lacks adds its own entry. The shared write limb (localeWriteKey in pickLocalized.ts, behind setLocalized / clearLocalized) stops owning an entry of the other script. A zh-Hant edit over { en, zh } or { en, 'zh-CN' } adds zh-Hant, and a clear there removes nothing. The same holds the other way round. Where the table does not name a script subtag (zh-Hant-US), the limb does not assume it is the editor's own. The read side is unchanged. navItemLabel's renamedLabel / clearedLabel now go through setLocalized / clearLocalized instead of asking resolveI18nLabel for the owned key, so they inherit the rule. Their own "sibling-only map ⇒ add the exact tag" rule (objectui#11196) is kept in ownsAnEntry.
  3. CLI coverage. inlineLocaleText judges an empty entry after the shared resolver picks, not before. A blank entry the renderer shows is a gap: { 'zh-CN': '成员', 'zh-TW': '' } at zh-Hant, and { zh: '成员', 'zh-CN': '' } at zh-CN. Server seat: no objection (pm:mailbox — domain:server#2 · os-tesla #30985, 6702009928).

Studio surfaces that read the designer locale

  • Chrome (falls back to Simplified). Of the 66 non-test files that call useMetadataLocale, and the components that take its value as a prop, almost all use it for chrome only (t() / tFormat() / tOptional() → studioString). The studio namespace ships one Chinese catalogue (zh), and resolveStudioLanguage('zh-Hant') serves it, so a Traditional designer reads Simplified chrome and never English. No Traditional strings were invented. Three chrome sites compared === 'zh-CN' and would have dropped to English, so they now use isZhLocale: PositionHoldersSection.tsx:110, AssignedUsersSection.tsx:132 and ResourceEditPage.tsx:3059.
  • Label entries (follow the session script, no edit). These pass the same locale to pickLocalized, resolveI18nLabel, navItemLabelText or renamedLabel / clearedLabel: StudioNavItemInspector, AppNavInspector, AppNavCanvas, InterfacesNavRail, SourcePageProperties, StudioDesignSurface, ObjectActionsPanel, ObjectListViewInspector / ObjectListViewTitle, ListViewBulkActionsSection, AutomationsFlowRail, metadataError, packageSurfaces, useDatasetCatalog, AppPreview, DashboardPreview, ViewPreview, DashboardDefaultInspector, DashboardWidgetInspector and ReportDefaultInspector.
  • Not the designer locale. translationTable.ts and doc-draft.ts key by the translation or doc language the author picks, so they are untouched.

Re-pinned #31068 controls

These pinned the cross-script overwrite as "unchanged" and now pin the added entry:

The spec twin is untouched. The packages/spec test's verbatim copy of pickLocalized covers splitScriptTagOf, localizedEntryKey and pickLocalized, and none of them changed: only localeWriteKey did. Its provenance pin still names the revision the copy matches.

Tests

Head 410d25d60c.

Live readings (console from this tree, showcase backend)

Setup: examples/app-showcase with two top-level nav labels set locally, nav_capability_map = { en: 'Proj EN', zh: '简体项目', 'zh-TW': '繁體專案' } and nav_start_here = { en: 'Tasks EN', zh: '简体任务' }. Their apps.showcase_app.navigation.<id>.label bundle rows were dropped, because a bundle row wins over the map. These edits were local only and are reverted. Console: ui/apps/console vite with objectui-locale = zh-Hant, logged in as admin, Studio → Interfaces.

  1. The nav designer lists the first item as 繁體專案, not 简体项目, in both the rail and edit mode. On main it listed 简体项目 (measured on PR fix(i18n): a Traditional Chinese session reads and edits the Traditional entry of an inline label map #31068's head, Studio designs a Traditional Chinese session in Simplified, so its label edits land on the Simplified entry #31074 body).
  2. An inline rename of 繁體專案 to 改名繁體 autosaved PUT /api/v1/meta/app/showcase_app?mode=draft&package=com.example.showcase.custom with label: { en: 'Proj EN', zh: '简体项目', 'zh-TW': '改名繁體' }, so zh-TW was updated and zh left untouched.
  3. Renaming 简体任务 to 新增繁體 sent label: { en: 'Tasks EN', zh: '简体任务', 'zh-Hant': '新增繁體' }: zh-Hant was added and the Simplified text kept. The inspector's Label field then read 新增繁體.

Studio chrome in that session is Simplified (界面 / 权限 / 属性), which is the namespace fallback described above.

Suites
  • pnpm --dir ui exec vitest run packages/i18n/src/__tests__ packages/app-shell/src/views/metadata-admin/previews packages/app-shell/src/views/metadata-admin/useMetadataLocale.sessionScript-31074.test.tsx: all pass.
  • pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/BuilderLanding.environmentOverlayCopy-12171.test.ts: 4 pass.
  • packages/cli: vitest run test/i18n-inline-locale-map-coverage.test.ts 17 pass.
  • Type-checks: @object-ui/app-shell type-check, @object-ui/i18n type-check and @objectstack/cli typecheck (with NODE_OPTIONS=--max-old-space-size=6144, because the 4096 run OOMed in check:test-typecheck) all exit 0.
  • eslint on every changed ui file: 0 errors.
Ablations (committed first, mutated, restored)
Derived gates

node scripts/pm/dispatch-gates.mjs --commands on 410d25d60c derives 66 families, and all 66 exit 0. --ran reconciles them as 66 accounted and 0 NOT-MEASURED. One family needed a rerun: pnpm --dir ui exec vitest run scripts/ first exited 1 on a 15 s timeout in dollar-dialect-alias-census.test.ts while the live console was running on the same box. A container restart then cut the sweep short. The resumed sweep reran every family that had not yet recorded exit 0, and that one passed. node scripts/check-sdui-manifest.mjs is in the derived set and exits 0.

Acceptance notes

  • Studio chrome stays Simplified in a Traditional session. The studio namespace has one Chinese catalogue, and inventing Traditional strings is out of scope. Only the label entries an author reads and edits follow the script.
  • Reading is unchanged: a zh-Hant reader of { en, zh } still sees the zh text until a Traditional entry exists, and the first edit adds one.
  • navItemLabel's "sibling-only map ⇒ add the exact tag" rule (objectui#11196) is unchanged. For example, { en, 'zh-TW' } renamed in zh-Hant adds zh-Hant, as before.

…ntry

The shared write limb no longer owns the other script's entry: a zh-Hant
edit over { en, zh } adds zh-Hant and keeps the Simplified text, and a
clear there removes nothing. Reading is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibxSGAjHBvjATmenUGp
useMetadataLocale resolves the session language through the console's own
resolveBuiltInLanguage, so the nav designer reads and edits the entry the
sidebar shows. Chrome falls back to the Simplified catalogue; three strict
zh-CN chrome comparisons move to isZhLocale. navItemLabel's rename and clear
go through the shared write limb.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibxSGAjHBvjATmenUGp
…s as a gap

Empty entries are judged after the shared resolver picks, so a blank zh-TW
beside zh-CN is a gap for a zh-Hant reader rather than covered by zh-CN.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibxSGAjHBvjATmenUGp
@github-actions github-actions Bot added size/m 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 7, 2026
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 63 of 226 client-bound route-ledger rows — the other 163 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 163: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 104 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 719a502b260616c5ffe62db3c3089f3f27e78622 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 07ab3eeda48f4c6fba0fd6da2c456dd100ba38ca — the merge of head 410d25d60cf8e96d2c3c4c5e8808d6447ac9f5f4 into base 719a502b260616c5ffe62db3c3089f3f27e78622, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 07ab3eeda48f4c6fba0fd6da2c456dd100ba38ca && git checkout 07ab3eeda48f4c6fba0fd6da2c456dd100ba38ca
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 719a502b260616c5ffe62db3c3089f3f27e78622 410d25d60cf8e96d2c3c4c5e8808d6447ac9f5f4 && git checkout -B drift-repro 719a502b260616c5ffe62db3c3089f3f27e78622 && git merge --no-ff 410d25d60cf8e96d2c3c4c5e8808d6447ac9f5f4

node scripts/docs-audit/affected-docs.mjs --json 719a502b260616c5ffe62db3c3089f3f27e78622

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf

席位核对 · ACCEPT · 2026-11-07T05:43Z · head 410d25d60cf8e96d2c3c4c5e8808d6447ac9f5f4

Seat domain:ui#2 reviewed the diff of its own dispatch (claim 6701997413 on #31074, amendment 6702028195, card thread session_01L9qibxSGAjHBvjATmenUGp, report 6702687115). The same seat dispatched and reviews; the code is the thread's.

  1. Shape: 15 files, +304 / -60. Fixes #31074.
  2. Premise measured: live, in a zh-Hant console on this tree:
  3. Gates / check-runs: 63 check runs on this head: 55 success, 8 skipped, 0 red. 66 derived gates exit 0, with --ran reconciled; after a container restart mid-sweep, the families still unrecorded were re-run. mergeable_state is clean.
  4. Accepted deviation: the worktree path is objectstack-issue-30910 (the reused thread), recorded in amendment 6702028195. Changesets, read sentence by sentence against the diff: @object-ui/i18n patch (cross-script edit) and @objectstack/cli patch (blank entry is a gap), both Clause-②: no and Metadata-protocol: none. That is right: no accept set changes. setLocalized writes a different key in one case it handled wrongly, and app-shell is private with no type export.
  5. Out of scope / Skills-docs: Studio has no Traditional chrome catalogue (its namespace ships en + zh). That is a known fallback, and nothing is filed. Skills-docs: not owed.
  6. Landing: check-governed-merges --pr 31089 reads not governed, 364 changed lines, so Tier S and no human approval owed. No packages/spec/src file, Clause-②: no, so no contract review owed. This lands on the maintainer's standing words 「按 pm-dispatch 审过的pr你可以自主落地」 (2026-10-30T14:20Z) and 「你应该使用 pm-dispatch 技能审核和落地pr」 (2026-11-07T01:02Z): relay pr_ready, then automerge_enable. On merge the card closes.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 7, 2026 05:45
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 51992df Nov 7, 2026
68 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-7el3c5 branch November 7, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m 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.

Studio designs a Traditional Chinese session in Simplified, so its label edits land on the Simplified entry

1 participant