Skip to content

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

Queued
objectstack-fleet[bot] wants to merge 4 commits into
mainfrom
claude/project-thread-7el3c5
Queued

objectstack-fleet[bot] wants to merge 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
Any commits made after this event will not be merged.

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

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