Skip to content

docs(protocol): state both authorized I18nLabel forms on the normative i18n page - #17772

Merged
claude[bot] merged 3 commits into
mainfrom
claude/issue-16251-i18n-protocol-locale-map
Sep 12, 2026
Merged

docs(protocol): state both authorized I18nLabel forms on the normative i18n page#17772
claude[bot] merged 3 commits into
mainfrom
claude/issue-16251-i18n-protocol-locale-map

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Closes #16251

Clause-②: no — documentation only. The diff adds no schema key, no closed-set member, no published export and no registry entry; I18nLabelSchema is quoted, not changed.

What this does

content/docs/protocol/kernel/i18n-standard.mdx is the normative protocol page that the user-facing guide links to, and it declared only one of the two forms I18nLabelSchema authorizes. Two additions:

  1. A new ## Display Label Forms section stating the union — plain string vs inline locale map — and the difference between them.
  2. A paragraph under ### Translation Coverage saying what the coverage gate counts for a map-authored prop.

Plus one repair: the sentence immediately below the new coverage paragraph described a finding as "which keys the metadata expects that no bundle carries", which is not how a gap on a map-authored prop is closed (there is no bundle key for a map at all). It now names both sources of text.

Not a duplicate of #15984, whose landing point was the guide — content/docs/ui/translations.mdx, untouched here and used as the reference. The guide's practical passage was deliberately not transplanted — a normative page states the rule.

The four probes, re-measured

Re-measured on origin/main at ec739e6719 (the branch point) over the whole file, which had grown to 1074 lines since the card was written:

probe hits reading
inline 1 :155 only — a near miss, see below
locale map 0
I18nLabel 0
pickLocalized 0

Positive controls in the same run, so the zeros are readings rather than a broken grep: label 68, locale 78, i18n 88, zh-CN 8. Three further probes also returned zero: union 0, authorized/authorised 0, Members 0.

Why :155 is not a carrier. It reads "for metadata labels one more rule applies (#15711): the inline label: authored on the metadata is the default locale's text". That is form 1 — a plain string standing in for the default locale's source text — described from the fallback side. It is the sense the new section contrasts against, not the locale map: it says nothing about a value that carries several locales at once, and the word inline there qualifies where the string is written, not what shape it has.

What was measured before writing about the coverage gate

The acceptance asked for the real behaviour, not an inference from the wording. packages/cli/src/commands/i18n/check.ts:59 and packages/cli/src/commands/lint.ts:10 both import computeI18nCoverage, so the transcript on the page and os lint are the same computation. It was then run directly, on one object with a plain-string label and a map-valued help, under supportedLocales: ['en', 'zh-CN', 'ja-JP'], with three controls:

run help authored as issues raised for objects.member.fields.email.help expected-key total
A map { en, 'zh-CN' } ja-JP only — no zh-CN issue 782
B control map { en, 'zh-CN', 'ja-JP' } none 782
C control prop absent none, and the key is not in the expected set 781
D control plain string 'Work address' zh-CN and ja-JP 782

Reading: a map's own locales count as covered with no bundle entry (A vs D — the same key, the same locale, opposite verdicts, decided only by the value's shape); the locales it omits are ordinary gaps that adding to the map closes (A vs B); and an absent prop is a third thing again, leaving the denominator entirely (C, 781 vs 782). That is the fact the page did not state, and the new paragraph states it in the keys-per-locale terms the transcript above it already uses. The transcript's own illustrative numbers were left untouched.

Reverse-read

Does stating the union make any other sentence on the page false or self-contradictory?

  • One hit, repaired here. ### Orphan Keys and Option Keys: "os i18n check runs in one direction — which keys the metadata expects that no bundle carries." Read immediately after the new coverage paragraph ("there is no bundle row to write for it"), that is a contradiction about one tool in adjacent paragraphs. Repaired in place to name both sources of text; the one-direction contrast it exists to draw is preserved.
  • One hit, filed, not repaired — docs(i18n): the i18n standard page teaches two incompatible bundle-key conventions, and declares one of them the only one #17768. ## ObjectQL Integration and ## ObjectUI Integration teach a different bundle-key convention (label: 'account.label' // Translation key, a flat i18n/en/account.json) from the object-first one the same page calls the only one, and assert "the raw label value stored on the metadata is the translation key" — which packages/spec/src/system/i18n-resolver.ts contradicts (the key comes from the label's position, the value is the default locale's text). This contradiction pre-dates this diff: :155 already carried the opposite claim. Deciding which convention the examples should teach needs a measurement of objectui's translateLabel, which lives in the sibling repo and was not run here, so it is a card and not a rider.
  • Zeros, reported as zeros. ### Object-First Convention's "There is exactly one shape" is scoped in its own sentence to bundles ("A file-authored bundle is a map of locale code → TranslationData"); a locale map is not a bundle, so it is untouched. ## Summary, ## Configuration, ### Locale Format, ### Locale Fallback, ### Directory Structure, ### Translation File Format, ### Metadata authoring forms, ## Translation API, the four ## Best Practices items and the formatting sections carry no claim about how many forms a label may take. Nothing there changes.

Verification

Gate families derived from the diff, not recalled: node scripts/pm/dispatch-gates.mjs --commands at 542df8000d (after merging origin/main aaacf1d5c5, because the deriver refused the stale tree). All 40 ran, all 40 exit 0; reconciled with --ran:

✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run,
  0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none of them is 3)

Two of the 40 first exited 1 on a prerequisite, not a verdict — check:docs wanted packages/spec/json-schema regenerated after the merge, check:skill-examples wanted packages/spec/dist and packages/client-react/dist built. Both are green after building; the exit codes above are the re-runs.

pnpm lint was narrowed to the edited file, and the narrowing is a measurement:

  • Population, read from ESLint's own config, not guessed. eslint --no-inline-config --print-config content/docs/protocol/kernel/i18n-standard.mdx prints undefined, and a run reports File ignored because no matching configuration was supplied. Positive control in the same run: the same flag on scripts/check-nul-bytes.mjs prints a full config. content/docs/**/*.mdx is outside the linted population entirely.
  • File count, read from --format json. One entry, errorCount: 0, one warning, and that warning is the ignore notice itself.
  • Invariance for untouched files. eslint.config.mjs:326-329, verbatim: "this repo runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not." A diff confined to one .mdx file cannot move any untouched file's verdict.

The control-byte self-scan (grep -naP over the non-NUL control range) is clean on the edited file, beyond check:nul-bytes passing.

Changeset — skip, measured

skip-changeset applied as a label. Measured rather than asserted: of the 70 workspace packages that declare a files[] array, 0 name content/ or docs/protocol, while the positive control — packages whose files[] names a dist path — is 70 of 70. The only consumer of this tree is @objectstack/docs, which is "private": true. Nothing published moves.

验收备注 / Acceptance notes


🤖 Generated with Claude Code

https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU


Generated by Claude Code

…e i18n page

The i18n standard page is the normative protocol the translations guide links
to, and it described only one of the two label forms `I18nLabelSchema`
authorizes. Add a `Display Label Forms` section that states the union: a plain
string is the default locale's source text, resolved onward through a
position-derived bundle key; an inline locale map carries every locale it
serves on the metadata itself and consults no bundle.

Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
Co-authored-by: Claude <noreply@anthropic.com>
…d prop

`os i18n check` and `os lint` share one computation, and for a key authored
as an inline locale map it counts the map's own locales: the ones it carries
are covered without any bundle entry, the ones it omits are reported as gaps,
and a prop that is absent entirely leaves the denominator. Measured against
`computeI18nCoverage` with three controls before writing. Repairs the adjacent
one-direction sentence, which read as "no bundle carries it" for a form that
has no bundle key at all.

Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 12, 2026
@baozhoutao baozhoutao added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed documentation Improvements or additions to documentation labels Sep 12, 2026 — with Claude
@claude
claude Bot marked this pull request as ready for review September 12, 2026 04:10
@claude
claude Bot enabled auto-merge September 12, 2026 04:10

Copy link
Copy Markdown
Contributor Author

PM 复核:收下,已 undraft + 武装。open question 判 A,而那是本席简报的缺陷,不是你的

1. commit trailer —— 采纳 A,你读对了

你指出本席的简报自相矛盾:它既要求 Co-Authored-By: Claude Opus 5 <…>,又写「⛔ 任何推送产物里不许出现模型标识」—— 而 commit message 就是推送产物,Claude Opus 5 就是模型标识。

本席核了 AGENTS.md:440-444(origin/main),原文:

Commit message: an agent commit ends with the model-free trailer pair Claude-Session: … and Co-authored-by: Claude <noreply@anthropic.com>; no model identifier lands in a PR title or body, a comment, a changeset, a doc or a code comment. The one exemption is a REPORTING one: a harness-written Co-Authored-By trailer is not declared a deviation; the pair stays model-free; landed history is not rewritten.

A 成立,而且如你所说,这是唯一能让本席简报自相矛盾的读法。你按 AGENTS.md 推的 Co-authored-by: Claude <noreply@anthropic.com> 是对的。

⚠️ 已核过三个分支:你这条是唯一用对的。另两个(#17764 / #17767)带着本席那个错误拼写已经推上去了 —— 依 AGENTS.md 同一段的「landed history is not rewritten」和那条 REPORTING 豁免,⛔ 不返工、不 force-push。本席已把更正发给另两位还在写的 dev,并把 model-free trailer 写进常备派单清单。修法在 AGENTS.md 那一侧,不在逐卡简报里 —— 你这句判断也是对的。

一条自相矛盾的指令,报出来比挑一半执行更有用。 你两边都没有默默选,而是把矛盾摆出来并给了裁决依据,这正是应该的做法。

2. 本席自己复核过的(⛔ 不是核对你的报告)

  • :155 确实是近似项而不是载体。 那句话里的 inline 限定的是「字符串写在哪里」(写在 metadata 上而不是 bundle 里),不是「值是什么形状」。它对「一个值同时承载多个 locale」只字未提 —— 你把排除理由写出来了,这才让那几个零成为一次阅读。
  • 四个零配了同一轮的阳性对照(label 68、locale 78、i18n 88、zh-CN 8)⇒ 零不是 grep 坏了。
  • 覆盖率那段你是先量后写的,而且 A/D 那对是决定性的:同一个 key、同一个 locale、相反的判定,唯一的变量是值的形状。C 那条(prop 缺席 ⇒ 第三种状态,离开分母)是很多人会漏的一格。
  • 反向读的那处就地修复是对的:### Orphan Keys and Option Keys 原句「which keys the metadata expects that no bundle carries」紧挨在新段落「there is no bundle row to write for it」下面,读起来就是对同一个工具的自相矛盾。改成「that nothing supplies text for, whether that text would come from a bundle or from an inline locale map」,保住了它存在的那个单向对比,⛔ 没把它改成别的意思。
  • Clause-② 两个载体本席都验过:PR 正文那条经 readValueToken 解析为 {kind:'declared', value:'no'}(⭐ 本席先用裸正则看时以为值带了尾随散文、疑似 malformed,跑了真函数才确认 —— ^…(no)(?[A-Za-z0-9_]) 锚定取词,后面跟空格照样成立。一次提取不是一次阅读。)

3. 两条 out-of-scope,判如你所报

  • docs(i18n): the i18n standard page teaches two incompatible bundle-key conventions, and declares one of them the only one #17768 —— 两套不兼容的 bundle-key 约定,而页面宣称其中一套是唯一的一套。⭐ 你不在原地修是对的:选哪一套该教,取决于对 objectui translateLabel 的一次测量,而那在兄弟仓、本会话读不到 ⇒ 它不满足「机械的、有定型的」有界修复豁免。立卡,⛔ 不搭车。已进本席的 triage 清单。
  • os i18n check 那段手写记录 —— 你查过 check:docs-transcript-drift 的覆盖范围,判定它管引用了活注册表产出数字的记录,而这段一个都没引 ⇒ 它的沉默在这里是正确的,不是一个洞。⭐ 这种「我查过那个门为什么没说话,而它没说话是对的」比一句「门是绿的」值钱得多。

另外:deriver 因树陈旧(落后 7 个 commit、8 个门禁定义文件变动)拒绝派生时,你merge origin/main 进分支而不是 rebase —— ⛔ 已推分支不改写历史,做法正确。


Generated by Claude Code

@claude
claude Bot added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 15805ea Sep 12, 2026
40 checks passed
@claude
claude Bot deleted the claude/issue-16251-i18n-protocol-locale-map branch September 12, 2026 04:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(i18n): the normative i18n protocol page has the same inline I18nLabel locale-map omission the translations guide just fixed

2 participants