Skip to content

feat(spec,cli): i18n 标签契约 —— 内联 locale map 授权化 + filter-only tab 的翻译槽 - #6512

Merged
qq9340100 merged 9 commits into
mainfrom
claude/issue-5728-i18n-label-contract-sweep
Aug 8, 2026
Merged

feat(spec,cli): i18n 标签契约 —— 内联 locale map 授权化 + filter-only tab 的翻译槽#6512
qq9340100 merged 9 commits into
mainfrom
claude/issue-5728-i18n-label-contract-sweep

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #5728
Fixes #5377

i18n 契约族 sweep 首棒。三单同分支,#5199 的 Fixes 行已摘除 —— 它的前提被实测推翻,理由写在最后一节,PM 已回执确认按 needs_decision 处理。


#5728 —— I18nLabelSchema union 化(裁决 B)

前提复核(origin/main)

packages/spec/src/ui/i18n.zod.ts:70 确为纯 z.string()。闸门实测 42 条 component-props-invalid,与 issue 表格逐项吻合:

闸门报出
PageTabsProps.items[].label 14
RecordRelatedListProps.title 13
RecordRelatedListProps.add.label 5
RecordDetailsProps.sections[].label 2
ElementTextPropsSchema.content 8
合计 42

改动

按裁决 B 原文:I18nLabelSchema 改为「默认语言字符串 内联 locale map」的 union;ElementTextPropsSchema.content 由裸 z.string() 改用同一 schema(裁决显式点名,union 覆盖不到它)。文档注释同步改写,并删掉「i18n keys are auto-generated by the framework」这句谎称(见 #5377 ②)。

验收:闸门 42 → 0,参考文档再生后如实描述两种形状(content/docs/references/ui/i18n.mdx 现在把 union 的两个 option 分别列出)。零 breaking:此前合法的标签全部仍然合法。

⚠️ 一处对裁决字面的收紧,请复核(PM 已预先回执照准)

裁决原文写的是 z.union([z.string(), z.record(z.string(), z.string())])裸 record 会把 #5055 退役掉的 key-reference 方言重新放进来 —— { key: 'views.x.label', defaultValue: 'Task List' } 结构上就是 string 到 string 的映射,parse 全绿,然后 objectui pickLocalized 的最后兜底是「取对象里第一个 string 值」,于是 i18n key 本身被当作可见标签渲染出来,且在每一种语言下都如此。那正是本文件头部记录的 silent-strip 失败类型,从 #5055 刚刚移除的那一个形状进来。

所以 record 的约束为 BCP-47 形状(外加 default —— pickLocalized 把它读作无标签兜底项)。这样裁决的两个条款同时成立:被 resolver 真实兑现的内联 map 获得授权,而零 resolver 的死方言保持拒绝 —— 这正是裁决自己写的「declared = enforced 同一原则的两个方向,与 #4667 不矛盾」。

代价实测为零:仓内 31 处内联 map 全部使用 en / zh-CN / ja-JP / es-ES

拒绝的是那个形状而不是一张违禁词表:key-reference 形式必带 defaultValue(旧 I18nObjectSchema 上是必填),而 defaultValue 不可能是语言标签。一个只写了三字母 key 的假想 map 仍会通过 —— 没有 ISO-639 注册表就无法把它和语言子标签区分开,而手工维护一张「长得像标签的英文单词」黑名单,是这个 schema 没有资格做出的语言学断言。这一点写在 schema 注释里,没有藏起来。

关于 B 方案的覆盖面(如实记录)

B 是一处改动覆盖全部键,因此 I18nLabelSchema 的约 40 个使用点全部获得了内联 map 能力,而 pickLocalized 在 renderer 侧的覆盖并不均匀 —— 例如 AriaPropsSchema.ariaLabel 就没有 resolver 读它的 map。裁决在否掉 C(只放宽被兑现的键,判为「最差:同一文件内 label 契约分叉」)时已经接受了这个取舍。translation-bundle 通道仍是长期方向,不在本单。


#5377 —— filter-only tab 的翻译槽 + describe 更正

范围按报告方 2026-08-05 自我更正与分诊收窄,只做两点,头条场景不做宽面。

_tabs 翻译槽 + resolver

前提复核通过:ObjectTranslationDataSchema_tabs(strictObject,作者自加会被拒绝而非忽略),i18n-resolver.ts 无 tab-label resolver。

  • ObjectTranslationDataSchema_tabs,按 ViewTabSchema.name 寻址。
  • resolveTabLabel 回退链,严格按派发词:显式 _tabs 翻译 > 引用 view 的 _views.*.label(既有路径原样保留 —— 一个翻译了 view 却没写过 _tabs 的应用,渲染结果和改动前完全一致) > 作者字面值 > tab name。
  • 接入 translatePage;packages/clii18n-extract 同步抽取。声明、消费、抽取同一 PR,参照 PR feat(spec): translatable page component copy — pages.<name>.components.<id> #6466pages.*.components 先例。

为什么挂在 objects.*._tabs 而不是 pages.*:一个 tab 是某个对象记录上的具名筛选预设 ——「紧急」命名的是任务的一个切片,和一个保存视图的标签一样属于对象词汇。这也是「引用 view 的标签」这一回退能够表达的前提:两级查找都落在同一个对象的命名空间里。形状先例是 _sections —— 它们同样写在页面组件上,却按对象寻址。

一处实测修正,与派发词不同,请复核:ViewTabSchema两个 carrier —— UserFiltersSchema.tabs(page-only 预设栏,ADR-0047)与 ListViewSchema.tabs(「multi-tab view interface」)。只有前者有渲染器(objectui packages/plugin-list/src/UserFilters.tsxTabFilters);后者在两个仓里零读者。给没人画的 carrier 发翻译槽,等于声明一个用户永远看不到的能力,并且随后 coverage 闸门还会为这些键索要翻译。因此 resolver 与 extractor 都只覆盖活的那个,并把理由写在代码注释里。

showcase 的 ui/pages/task-triage.page.ts 正是本单场景:4 个带 filter、不带 view 的 tab(In Progress / Urgent / In Review / Done)。

i18n-coverage 棘轮:如派发词预警,新键使 showcase 从 451 涨到 455。已在 examples/app-showcase/src/system/translations/index.ts 给这 4 个键出生即翻译(zh-CN,用词沿用同一 bundle 里对应 status/priority option 的既有译法),基线未抬。gate 回绿:OK (12 config(s), 660 baselined untranslated string(s), none new)

② describe 更正

I18nLabelSchema 的 describe 与 JSDoc 不再宣称 keys 是框架自动生成的 —— 没有任何 key 被生成。AriaPropsSchema.ariaLabel 一并更正:它其实没有逐字带那句话(它覆盖了共享 describe),但它继承了那个假设,而 TranslationDataSchema 里根本没有寻址 ariaLabel 的槽 —— 现在 describe 直接说明这一点,而不是留给作者自己踩。两条都有 pin 测试。


#5199 —— 前提已被实测推翻,摘除 Fixes 行

派发词要求「先测 translator 管线实际结构,实测推翻方向 1 时按 needs_decision 返回、从 PR 里摘掉它的 Fixes 行、不硬做」。实测结果:

方向 1 描述的修法已经实现了,而且比本单开单更早。 resolveDatasetLabel 在两个仓里都不存在;真正画表头的是 objectui packages/core/src/utils/dataset-format.tsbuildDatasetFieldHelpers:

const headerLabel = (name: string) => {
  const fallback = measureField(name)?.label ?? name;
  return object && fieldLabel ? fieldLabel(object, name, fallback) : fallback;
};

fieldLabel(object, name, fallback) 走的正是 objects.*.fields.*.label 字段翻译约定 —— 即「dimension name 命中真实字段名时回落字段翻译」。该代码 2026-08-01 就在了(objectui 14f6999),比本单开单(08-04)早三天。报告方观察到的现象(current_grade 渲染中文、account_name 保持英文),正是这个已发布回落在按设计工作:一个改了名、不对应任何字段的 dimension,没有字段可回落。

剩余的英文在生产端,不在 translator。 buildFieldMeta(objectql-strategy.ts:1175native-sql-strategy.ts:789)两个策略都只发 { name, type },从不发 label。所以 dataset 声明的 dimensions[].label / measures[].label 根本到达不了渲染端 —— 这是 service-analytics 的生产端缺口,不是 packages/spec 的 translator 缺口,需要一次新裁决(「dataset 声明的 label 该不该随查询结果下发」),不在本棒授权范围内。已按预案在报告的 open_questions 里给出三轴分析;PM 已回执,再分诊由 PM 在验收后办理。

方向 2(_datasets / _reports 新翻译面)按裁定不在本棒,挂 v18。


验证

全部前台阻塞执行,构建/测试持容器级 flock + 堆帽 + --filter 限定。

结果
pnpm --filter @objectstack/spec test 339 files / 8722 passed
pnpm --filter @objectstack/cli test 92 files / 943 passed
pnpm --filter @objectstack/lint test 62 files / 1541 passed
pnpm --filter @objectstack/spec typecheck 通过(含 scripts + test-typecheck)
pnpm --filter @objectstack/cli typecheck 通过
#5068 闸门(三个平台页) 42 → 0
node scripts/check-i18n-coverage.mjs OK,12 configs,基线未变
pnpm lint / check:nul-bytes / check:i18n / check:doc-authoring 通过
check:spec-parsed-alias(ADR-0122) 通过(见下)
spec 生成物四件套 check:docs / check:api-surface / check:authorable-surface / check:generated 通过

新增测试:packages/spec/src/ui/i18n.test.ts(union 两种形状 + 退役方言仍拒 + describe 不再宣称自动生成)、packages/spec/src/system/i18n-resolver.test.ts(resolveTabLabel 全回退链 + translatePage tab 栏 + _tabs schema)、packages/cli/test/i18n-tab-coverage.test.ts(抽取器 15 项,含「不给零读者 carrier 发槽」与「抽取路径与 schema 互相接受」)。

两处顺带记录:


Generated by Claude Code

claude added 4 commits August 8, 2026 02:01
…tions

Fixes #5728
Fixes #5377

`I18nLabelSchema` becomes a union of the plain default-language string and an
inline BCP-47 locale map, and `ObjectTranslationDataSchema` gains `_tabs` with a
resolver that reads it. See the PR body for the per-issue rationale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
…nder ADR-0122

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 5:02am

Request Review

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/spec.

117 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli, @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:system tests protocol:ui tooling labels Aug 8, 2026
The union widening changes what the generated skill artifacts say about every
`I18nLabelSchema` prop (`string` -> `string | object`), and `i18n.zod.ts` gains
an explicit MODULE doc so its skill-index headline is a decision rather than a
side effect of which declaration sorts first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
claude added 2 commits August 8, 2026 04:14
Conflicts: type-alias-convention.pin.test.ts — both sides took Iso758
(main's #5933 SpecifierValueDomain vs this branch's InlineLocaleMapSchema);
renumbered ours to Iso759, receipts stacked in merge order, count 756.
Generated files taken from origin/main verbatim; regenerated wholesale in
the follow-up commit per the four-step.
os-regen four-step after merging origin/main: docs references, api-surface,
authorable-surface (base + system). check:generated 10/10, check:spec-parsed-alias
756 OK, check:i18n-coverage none new.
@qq9340100
qq9340100 added this pull request to the merge queue Aug 8, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 8, 2026
claude added 2 commits August 8, 2026 04:57
…8-i18n-label-contract-sweep

Second conflict round on the pin count: the sweep's -7 (755->748) merged in
under this branch's +1 (InlineLocaleMapSchema); receipts stacked in merge
order, count recomputed from the file = 749. Generated files taken from
origin/main verbatim; regenerated wholesale in the follow-up commit.
… tree

Second regen round after #6526 merged: docs references, api-surface,
authorable-surface, strictness-ledger counts. check:generated 10/10,
check:spec-parsed-alias 749 OK, pin test 3/3.
@qq9340100
qq9340100 enabled auto-merge August 8, 2026 05:10
@qq9340100
qq9340100 added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit a36db28 Aug 8, 2026
26 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5728-i18n-label-contract-sweep branch August 8, 2026 05:30
os-project-manager pushed a commit that referenced this pull request Aug 8, 2026
`origin/main` 上有三个提交改了 schema 并各自重生成了
`content/docs/references/**`(#6512 i18n 标签契约、#6540 capability 注册、
#6526 ADR-0049 退役 sweep),与本分支的重生成在 13 个文件上相交。
`.gitattributes` 的 `merge=os-regen` 驱动按设计**没有做文本合并**,而是把这
13 个文件标记为「必须在合并后的树上重生成」—— 否则会落地 #6224 那种「零冲突
却陈旧」的组合。

本提交就是那次重生成:`gen:schema && gen:docs` 跑在合并后的树上,12 个文件
被修正,`check-regen-pending` 标记已清除。无一处手改 `.mdx`;
`packages/spec/scripts/lib/format-type.ts` 与其测试**逐字未动**(main 上没有
任何提交碰过这两个文件)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants