feat(spec,cli): i18n 标签契约 —— 内联 locale map 授权化 + filter-only tab 的翻译槽 - #6512
Merged
Conversation
…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
…n-label-contract-sweep
…nder ADR-0122 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 117 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
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
qq9340100
marked this pull request as ready for review
August 8, 2026 03:30
qq9340100
enabled auto-merge
August 8, 2026 03:30
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.
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Aug 8, 2026
…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
enabled auto-merge
August 8, 2026 05:10
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
This was referenced Aug 8, 2026
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.
Fixes #5728
Fixes #5377
i18n 契约族 sweep 首棒。三单同分支,#5199 的 Fixes 行已摘除 —— 它的前提被实测推翻,理由写在最后一节,PM 已回执确认按 needs_decision 处理。
#5728 ——
I18nLabelSchemaunion 化(裁决 B)前提复核(origin/main)
packages/spec/src/ui/i18n.zod.ts:70确为纯z.string()。闸门实测 42 条component-props-invalid,与 issue 表格逐项吻合:PageTabsProps.items[].labelRecordRelatedListProps.titleRecordRelatedListProps.add.labelRecordDetailsProps.sections[].labelElementTextPropsSchema.content改动
按裁决 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:此前合法的标签全部仍然合法。裁决原文写的是
z.union([z.string(), z.record(z.string(), z.string())])。裸 record 会把 #5055 退役掉的 key-reference 方言重新放进来 ——{ key: 'views.x.label', defaultValue: 'Task List' }结构上就是 string 到 string 的映射,parse 全绿,然后 objectuipickLocalized的最后兜底是「取对象里第一个 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/cli的i18n-extract同步抽取。声明、消费、抽取同一 PR,参照 PR feat(spec): translatable page component copy — pages.<name>.components.<id> #6466 的pages.*.components先例。为什么挂在
objects.*._tabs而不是pages.*:一个 tab 是某个对象记录上的具名筛选预设 ——「紧急」命名的是任务的一个切片,和一个保存视图的标签一样属于对象词汇。这也是「引用 view 的标签」这一回退能够表达的前提:两级查找都落在同一个对象的命名空间里。形状先例是_sections—— 它们同样写在页面组件上,却按对象寻址。一处实测修正,与派发词不同,请复核:
ViewTabSchema有两个 carrier ——UserFiltersSchema.tabs(page-only 预设栏,ADR-0047)与ListViewSchema.tabs(「multi-tab view interface」)。只有前者有渲染器(objectuipackages/plugin-list/src/UserFilters.tsx的TabFilters);后者在两个仓里零读者。给没人画的 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在两个仓里都不存在;真正画表头的是 objectuipackages/core/src/utils/dataset-format.ts的buildDatasetFieldHelpers:fieldLabel(object, name, fallback)走的正是objects.*.fields.*.label字段翻译约定 —— 即「dimensionname命中真实字段名时回落字段翻译」。该代码 2026-08-01 就在了(objectui14f6999),比本单开单(08-04)早三天。报告方观察到的现象(current_grade渲染中文、account_name保持英文),正是这个已发布回落在按设计工作:一个改了名、不对应任何字段的 dimension,没有字段可回落。剩余的英文在生产端,不在 translator。
buildFieldMeta(objectql-strategy.ts:1175与native-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 testpnpm --filter @objectstack/cli testpnpm --filter @objectstack/lint testpnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/cli typechecknode scripts/check-i18n-coverage.mjspnpm lint/check:nul-bytes/check:i18n/check:doc-authoringcheck:spec-parsed-alias(ADR-0122)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全回退链 +translatePagetab 栏 +_tabsschema)、packages/cli/test/i18n-tab-coverage.test.ts(抽取器 15 项,含「不给零读者 carrier 发槽」与「抽取路径与 schema 互相接受」)。两处顺带记录:
InlineLocaleMapSchema的z.input与z.infer相同(键上的 regex 只收窄哪些键能 parse,不改写任何键),按 ADR 规定的方式 pin 在type-alias-convention.pin.test.ts,而不是造一个永久同义词InlineLocaleMapParsed。该 pin 计数断言随之 754 到 755;测试标题此前停在 751([spec] DataProtocol 新增 validate-only 数据校验操作 —— #4633 裁 D 的契约半边(spec 先行) #6037 把断言推到 754 时漏改),一并订正。api/strictness 计数 396 到 401 与references/api/protocol.mdx的新增条目来自合并进来的 feat(spec,objectql,metadata-protocol): validate-only data operation — DataProtocol.validateData #6474(validate-only data operation),不是本 PR 的改动。Generated by Claude Code