Repository navigation
docs(i18n): the translations guide never mentions inline I18nLabel locale maps — the one route it does not describe is the one the coverage gate now reports on #15984
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationpriority:p2Medium: important, M3Medium: important, M3
on Sep 6, 2026 分诊 ·
domain:devx/documentation/priority:p2/pm:queue分诊席位。⛔ 不认领、不派发、不写代码、不合并。
origin/main@932acc3d,2026-09-06T04:56Z。复现,含一处需要更正的计数
content/docs/ui/translations.mdx存在。「inline」一词在页面上:行 内容 :86单独的 inline(表格单元/片段):159「inline label:— it never walks the fallback chain」:254「your inline label:is already that locale's text」:264「action labels declared inline on an object」 ⚠️ 卡说「The word "inline" appears on the page twice, both times about something else」。实测是 4 行——但卡描述的那两处(:254、:264)逐字命中,另两处(:86、:159)同样不是在讲 locale map。⇒ 结论不变,计数需更正为 4。另一条我加的读数:
'zh-CN':在该页只出现 1 次,在:38且后面跟着{(即'zh-CN': {)⇒ 那是bundle 文件的结构,不是内联I18nLabelmap(label: { en: '…', 'zh-CN': '…' })。⇒ ⭐ 页面确实从未展示第二种授权形式。定级 p2
⭐ 卡把「为什么是现在而不是以前」讲得最好,这就是 p2 的理由:
Until #14749 the omission cost an author nothing they could observe … That is no longer true. After PR #15980 the gate reads a map's own locales … So an author who writes
{ en, 'zh-CN' }on a stack declaringsupportedLocales: ['en', 'zh-CN', 'ja-JP']now gets a realmissing translation for locale "ja-JP"finding about a prop this page gives them no vocabulary for。⇒ 一个作者被闸门指着一个这一页没教过的东西报错,而它引出的三个问题(这是什么形式?为什么
os i18n extract不为它生成脚手架?这个缺口我该怎么办?)答案全在代码里,没有一处在用户会读的地方。⇒ 不是「文档少写了一节」,是闸门开始对一个未文档化的表面报错。p2。
不给 p1:无运行时缺陷、无数据风险;作者收到的是一条真实且正确的 finding,只是无从下手。
车道
domain:devx/ 定型documentation落点
content/docs/ui/translations.mdx⇒ devx。⭐ 卡给的形状我认为可直接用,但有一条边界必须保住
建议的四句(「both forms are authorized;the map is rendered but never extracted,所以从 bundle 工作的译者找不到这些字符串;coverage 计入 map 携带的 locales;没有 bundle key,也不会从节点路径合成一个」)——
⚠️ 最后半句是裁决结果(#14749 Q3 = C3),⛔ 不是文档作者的判断。⇒ 写的时候要按裁决原样转述,⛔ 不要改写成「暂时没有」之类留有余地的说法——那会让下一个人以为还能争取。⭐ 而「rendered but never extracted」这一句是全篇最需要说清楚的:它解释了为什么闸门报缺口、而
extract不生成脚手架——两个工具对同一个键的态度不同,且都是对的。⭐ 卡的读法自陈值得留住
Per-file, not by a tree-wide grep. … Positive control on the same command and scope: the phrase "inline locale map" fires on 12 files under
content/docs/, and 0 of those 12 are outsidecontent/docs/references/— the auto-generated tree, where every occurrence is a schema.describe()about rendering, none about coverage or extraction. So the null on the hand-written pages is a measurement, not a silent empty。⇒ ⭐ 它不但给了正控制,还说明了那 12 个命中为什么不算数(全在自动生成的 references 树里、讲的是渲染不是覆盖)。这比单纯给一个非零数强得多。
相邻
#15435(同类:那张表漏了
bulkActions与datasets两组,已被接受并修复)—— 卡引作先例,成立。
#14816 / #14817 —— 同一 i18n 家族。⚠️ 本轮我也路由了 #16121(os i18n extract打印的键数错两个数量级)⇒ ⭐ 同一批作者面向的 i18n 工具链上有三处「工具说的和实际不符」,⛔ 不合并,但值得一个人一次读完。
Generated by Claude Code
Claim: PM loop round 1
Session:session_01Vbw3RPgdtqesx4azk9SbW8
Branch:claude/issue-15984-translations-inline-locale-maps
Worktree:objectstack-issue-15984
Domain:domain:devx
File surface:content/docs/ui/translations.mdx(a row in "What you can translate" and a short subsection or "Current boundaries" bullet) (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus—dispatch-gates.mjs --tier: no path-derived mandate; default tier because the passage transcribes a maintainer ruling (#14749 Q3 = C3) verbatim and must not soften it
Clause-②: no
Serial constraints cleared: no open PR touchescontent/docs/ui/translations.mdxorcontent/docs/protocol/kernel/i18n-standard.mdx(all open PRs' file lists read at 07:0xZ). Premise re-read onorigin/main@cbca47d0: the page shows the bundle route only;'zh-CN': {appears once at:38(bundle structure, not an inline map); the fourinlinementions (:86,:159,:254,:264) are all about something else — triage's count correction (4, not 2) stands.I18nLabelSchemaatpackages/spec/src/ui/i18n.zod.ts:272; the normative prose lives inpackages/cli/src/utils/i18n-extract.ts:27-52("Inline locale maps are NOT extracted — and are not 'unauthored' either", #14749 Q3 = C3).
Generated by Claude Code
os-dev-report
{ "issue": 15984, "status": "done", "branch": "claude/issue-15984-translations-inline-locale-maps", "pr": "https://github.com/objectstack-ai/objectstack/pull/16250", "premise_still_valid": true, "summary": "Added the missing localisation route to content/docs/ui/translations.mdx: one row in \"What you can translate\" for a label written as an inline locale map (its bundle-location cell is the point of the row -- nowhere), and one bullet at the head of \"Current boundaries\" carrying the four required statements in order: both forms of a label are authorized (plain string translated in a bundle; inline locale map resolved at render time by pickLocalized / resolveI18nLabel); the map is rendered but NEVER extracted, so a translator working from the bundle will not find those strings; coverage counts the locales the map carries and reports the ones it omits against supportedLocales, producing a real `missing translation for locale \"ja-JP\"` finding; and no bundle key exists for a map, nor is one synthesised from a node's position in the component tree -- transcribed as the settled refusal it is (maintainer ruling 2026-09-03, #14749), not as a \"not yet\" or \"for now\". The page was not restructured and the existing plain-string \"inline `label:`\" mentions at :159/:254/:264 are untouched. The assignee was already set by the PM dispatch and I posted no second Claim.", "premise_verification": { "A1": "CONFIRMED on the fresh tree at BASE 1ebde4953: `'zh-CN':` occurs exactly once in content/docs/ui/translations.mdx, at :38, followed by `{` -- bundle-file structure, not an inline map. `locale map` occurred 0 times on the page before this change. The page truly showed the second authorized form nowhere.", "A2": "CONFIRMED. All three author questions are answered only in code: what the form is -> packages/spec/src/ui/i18n.zod.ts I18nLabelSchema (union; \"Both are real; neither is deprecated by this schema\"); why `os i18n extract` scaffolds nothing -> packages/cli/src/utils/i18n-extract.ts:27-52 (\"Inline locale maps are NOT extracted -- and are not 'unauthored' either\", #14749 Q3 = C3); what to do about the gap -> packages/cli/src/utils/i18n-coverage.ts lookup, where the map's entry FOR THIS LOCALE is tag-matched and never the renderer's fallback limbs (Q2 = B1). The new passage answers all three in user language and links no source file paths; only the two resolver identifiers (pickLocalized, resolveI18nLabel) are named, and pickLocalized already appears in this repo's generated reference prose.", "A3": "PARTLY FALSIFIED AND CORRECTED. Building @objectstack/spec + @objectstack/lint + @objectstack/client was NOT sufficient for check:skill-examples: it refused with a PREREQUISITE-NOT-MET message naming packages/client-react/dist (\"holds no .d.ts declarations -- the package is not built\"), exit 1, recorded as NOT MEASURED rather than red. A second locked build of '@objectstack/client-react...' cleared it and the re-run is green. The closure the docs gate family needs is spec + lint + client + client-react." }, "gates": { "head_sha": "a99929c44604eb77bf2b0e905ac1aac31c78ca79", "derivation": "node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack -> exit 0; stderr names the repo and commit: \"gate list derived from the tree of 'objectstack-ai/objectstack' at commit a99929c44\", \"--repo ... checked against this checkout's 'origin' remote -- it holds\"; change set \"1 path(s) vs merge base 1ebde4953 of 'origin/main' and HEAD (committed 1, working tree 0, untracked 0; three-dot semantics)\". 38 commands derived (26 pnpm, 12 direct node). The PM LEAD listed ~31; the 7 additional are the whole-tree-declared families (published-readme-links, react-page-adapter-contract, refd-timer-probe, role-word, skill-identifier-liveness, vendor-version-stamps, watch-hint-literal) -- all run.", "reconciliation": "node scripts/pm/dispatch-gates.mjs --changed --repo objectstack-ai/objectstack --ran RANFILE (the derived list) -> exit 0; own verdict line: \"Run reconciliation -- 38 derived, 38 run, 0 NOT-MEASURED, 0 UNRUN.\" and \"dispatch-gates --ran: 38 derived famil(ies) accounted for -- 38 run, 0 NOT-MEASURED.\"", "exit_code_capture": "every gate run as `cmd > file 2>&1; E=$?` -- redirect before capture, no pipe between the gate and $?.", "all_green": "38/38 exit 0", "sampled_verdict_lines": [ "pnpm check:nul-bytes -> exit 0 -> \"check-nul-bytes: OK (scanned 7953 text file(s) -- 7953 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).\"", "pnpm check:doc-anchors -> exit 0 -> \"check-doc-anchors: 313 internal #fragment link(s) across 408 source file(s) all resolve to a real heading\"", "pnpm check:docs-single-h1 -> exit 0 -> \"check-docs-single-h1: 403 page(s) under content/docs/ carry no body-level `# ` heading\"", "node scripts/check-doc-frontmatter.mjs -> exit 0 -> \"check-doc-frontmatter: 2 content root(s) verified, each against its own floor -- content/docs 403, content/blog 3.\"", "pnpm check:docs-audit-scope -> exit 0 -> \"docs-accuracy-audit scope is in sync with content/docs/: 190 hand-written doc(s)\"", "pnpm --filter @objectstack/spec run check:skill-examples -> exit 0 (re-run) -> \"257 prose examples type-check across 3 surface(s) -- every marked block parsed, so tsc ran the SEMANTIC pass on all of them\"", "pnpm check:corpus-claim-drift -> exit 0", "pnpm check:docs-transcript-drift -> exit 0", "pnpm check:doc-authoring -> exit 0" ], "not_measured_then_cleared": [ "pnpm --filter @objectstack/spec run check:skill-examples -> FIRST RUN exit 1, but its own text is a refusal to measure: \"packages/client-react/dist holds no .d.ts declarations -- the package is not built\" ... \"A verdict now would be computed against a build that no longer matches src ... a FALSE GREEN\". Recorded as NOT MEASURED per the PREREQUISITE-NOT-MET rule, never as a red. Cleared by a locked build of '@objectstack/client-react...' and re-run green at the same HEAD a99929c44." ], "lock_usage": [ "OS_VERIFY_LOCK_SLOT=issue-15984 ... -c \"pnpm --filter '@objectstack/spec...' --filter '@objectstack/lint...' --filter '@objectstack/client...' build --concurrency=2\" -> ATTEMPT 1: \"VERDICT queue-timeout (exit 99) - never acquired - waited 540s (9m00s) - holder pid 9192, held 661s\". Exit 99 = NOT MEASURED; slot kept; the interval was spent on the 20 lock-free gate scripts, then the same slot was re-used.", "ATTEMPT 2 (same slot): \"VERDICT command-exit 0 - held the lock 495s (8m15s) - waited 538s (8m58s)\".", "client-react closure build (same slot): \"VERDICT command-exit 0 - held the lock 396s (6m36s) - waited 270s (4m30s)\".", "Both waits were foreground, in-turn blocking (tail -f on the wrapper log until its own VERDICT line); no turn ended on a monitor." ] }, "tests": "No test files changed -- this is a docs-only diff (one .mdx page), so the deliverable is the gate family, not a vitest run. Affected-package check: `TURBO_SCM_BASE=1ebde4953 pnpm exec turbo ls --affected` is not the governing list for a content/docs path; the governing derivation is dispatch-gates over the changeset, run above: 38 derived, 38 run, 0 UNRUN, all exit 0, at HEAD a99929c44604eb77bf2b0e905ac1aac31c78ca79. Prerequisite builds went through scripts/pm/os-verify-lock.sh (verdict lines quoted under gates.lock_usage). eslint narrowing, all three pieces of evidence at the same HEAD: (1) POPULATION read from eslint's own config, not guessed -- every `files:` key in eslint.config.mjs is a TS/JS extension glob (`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` and narrower path-scoped variants); no config object names an `.mdx` extension. (2) FILE COUNT from `--format json`: `npx eslint --no-inline-config --format json content/docs/ui/translations.mdx` returns exit 0 and a 1-element array whose only message is `File ignored because no matching configuration was supplied.` -- 1 file offered, 0 files actually linted, 0 errors. (3) INVARIANCE: because no config object matches the `.mdx` extension at all, and type-aware linting is therefore never seeded from this file, this diff cannot move any eslint verdict on any untouched file. So the whole-repo `pnpm lint` run that CI owns would evaluate ZERO of this PR's changed files -- the narrowing here is a measurement, not a skip. No ablation performed and none is meaningful: the change is prose, it adds no guard and removes none, so there is nothing whose failure mode could be demonstrated by mutation.", "files_changed": ["content/docs/ui/translations.mdx"], "line_budget": "n/a", "deviations": [ "CHANNEL SWITCH, declared: the dedup search for the out-of-scope finding could not use REST -- `GET /search/issues` returned HTTP 403 from this seat (repo-scoped REST reads and writes both work; only the global search endpoint is denied). Per the dispatch rule this became exactly one targeted MCP `search_issues` call. Its read is not a silent empty: #15984 itself came back as a control hit in the same result set (10 results, all i18n-family). No open card covers the neighbour page, so #16251 is not a duplicate.", "NOT MEASURED, cleared: check:skill-examples on its first run -- see gates.not_measured_then_cleared. Re-run green at the same HEAD.", "NOT MEASURED, standing: three families dispatch-gates prints as taking a value from the CI workflow env and therefore not runnable here -- check-cross-package-test-inputs.mjs --union-into \"$RUNNER_TEMP/...\", check-shard-attestation.mjs, check-test-completeness.mjs. The runnable `pnpm check:cross-package-test-inputs` form WAS run and is green; the CI-argv form is CI's.", "NOT MEASURED, standing: the 40 artifact-roster families and the 10 too-wide-to-place families that dispatch-gates scores `silent` for every card. Their silence is a fact about a list, not a clearance, and is not read as green.", "NO OFF-SURFACE TOUCH: the diff is exactly the one declared file. content/docs/protocol/kernel/i18n-standard.mdx was read but NOT edited, per the declared surface; the finding about it is filed as #16251.", "TITLE REPAIR on the filed finding #16251: the first POST sent the title through a shell double-quoted `node -e`, and bash command-substituted the backticks, dropping `I18nLabel` from it. Caught by reading the created issue back; repaired with a PATCH from a quoted heredoc and read back verbatim. The body was written from a quoted heredoc and was never affected." ], "mcp_calls": "1 - a single targeted `search_issues` for the out-of-scope dedup, forced by the 403 above. Everything else (issue read, comments read, PR create, label write, all read-backs, issue file + title patch) went through repo-scoped REST.", "open_questions": [], "out_of_scope_findings": [ "filed as #16251: content/docs/protocol/kernel/i18n-standard.mdx -- the page the guide links as *the normative protocol* -- has the SAME omission #15984 just fixed. Measured over the whole 1020-line file at BASE 1ebde4953: `inline` appears exactly once (:155, about a plain-string `label:` as source text, #15711), and `locale map`, `I18nLabel` and `pickLocalized` appear zero times; its `### Translation Coverage` section (:820) shows an `os i18n check` transcript in keys-per-locale terms with nothing about a map's own locales. PM asked for my judgment: YES, I believe it needs the passage too -- a page that declares itself the normative label contract while omitting one of the two forms that contract authorizes is exactly the failure i18n.zod.ts's own header names as the reason the schema was widened. But whether the normative page should state the union or defer to the guide is a scope call, so it is a card, not a rider. NOT edited by PR #16250." ] }
Generated by Claude Code
ACCEPT — PM seat
domain:devx @ objectstack, sessionsession_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T08:0xZ. Reviewed against GitHub (PR #16250 diff and files, check-runs on heada99929c), not the report's narrative. Report:5557861670.Checklist conclusions
- PR shape: draft, base
main, first lineFixes #15984, no other closing keyword; AGENTS.md footer; 1 commit. - Scope (
get_files): exactlycontent/docs/ui/translations.mdx(+26/−0);skip-changeseton the PR. Governed test: NOT governed ⇒ ordinary queue. - Diff read in full: one table row in "What you can translate" whose bundle-location cell is "Nowhere", and one bullet heading "Current boundaries" carrying, in order, the four required statements — both forms authorized; the map is rendered but never extracted (with why: the extractor refuses to invent a key a sibling reorder would reassign); coverage counts the map's own locales and reports the omitted ones against
supportedLocales(the realmissing translation for locale "ja-JP"finding named); no bundle key exists and none is synthesised from a node's position — written as a settled refusal (maintainer ruling 2026-09-03, InlineI18nLabelmaps are invisible toos i18n extractandcheck:i18n-coverage— should the extractor and the coverage gate see them? (objectstack#14412 ruling A follow-up) #14749), not as "not yet" (triage's boundary held). The plain-string "inlinelabel:" mentions at:159/:254/:264are untouched; no source paths linked. - Evidence: 38 derived families re-derived on the branch, 38/38 run, all exit 0 at
a99929c(dependency closures built under the lock;check:skill-examplesNOT MEASURED on first run for a missing client-reactdist, cleared by a further locked build and re-run green — the closure the docs family needs is spec + lint + client + client-react, a reading worth keeping); eslint narrowing measured (no config matches.mdx). - A1 / A2 confirmed on the fresh tree; A3 partly falsified and corrected as above.
- Out of scope: docs(i18n): the normative i18n protocol page has the same inline
I18nLabellocale-map omission the translations guide just fixed #16251 filed bare (the normativei18n-standard.mdxhas the same omission) — triage's to grade; correctly not ridden in.
Landing: non-governed; 6 checks in progress at ACCEPT ⇒ flip point ~08:1xZ; on MERGED,
pm:dispatchedstripped and assignee cleared in the landing stroke.
Generated by Claude Code
- PR shape: draft, base
LANDED — PM seat
domain:devx @ objectstack, sessionsession_01Vbw3RPgdtqesx4azk9SbW8, 2026-09-06T08:55Z.PR #16250 (
Fixes #15984) MERGED 2026-09-06T08:49:53Z as8adb9e84aonmainvia the merge queue (queued 08:15:37Z). Content probe on re-fetchedorigin/main6a1e38244:inline locale mapincontent/docs/ui/translations.mdx= 3 (resolveI18nLabel= 1); control: the same string in the pre-merge parent tree = 0. Card closed by the merge;pm:dispatchedstripped and assignee cleared in this stroke.
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026
Filed unassigned by the
domain:cliseat while implementing #14749 (PR #15980), as an out-of-scope finding. Deliberately unlabelled — triage ownsdomain:*and the grading.The gap
content/docs/ui/translations.mdxis the user-facing guide for localising an app. Read end to end, it describes exactly one localisation route: a plain-string label on the metadata, translated in a bundle addressed by convention. It never mentions the second authorized form of anI18nLabel— the inline locale map,label: { en: 'Members', 'zh-CN': '成员' }— whichI18nLabelSchemahas authorized since #5728, whichpickLocalized/resolveI18nLabelrender, and which #5728 / #10926 / #14412 have three times ruled is the localisation route for props that have no bundle key at all.The word "inline" appears on the page twice, both times about something else: "your inline
label:" (a plain string, as that locale's source text) and "action labels declared inline on an object" (declared inline, not a locale map).Why it is worth a card now rather than before
Until #14749 the omission cost an author nothing they could observe: a map was silently outside the coverage report, so a reader of this page and a reader of
os lintsaw the same nothing.That is no longer true. After PR #15980 the gate reads a map's own locales — locales it carries count as covered, locales it omits are reported as gaps. So an author who writes
{ en, 'zh-CN' }on a stack declaringsupportedLocales: ['en', 'zh-CN', 'ja-JP']now gets a realmissing translation for locale "ja-JP"finding about a prop this page gives them no vocabulary for. The three questions that finding raises — what is this form, why doesos i18n extractscaffold nothing for it, and what do I do about the gap — are each answered in the code (packages/spec/src/ui/i18n.zod.tsandpackages/cli/src/utils/i18n-extract.ts's header, where the #14749 C3 ruling placed the normative prose) and nowhere a user reads.Suggested shape, not a prescription
Most likely a row in "What you can translate" plus a short subsection or a "Current boundaries" bullet stating: both forms are authorized; the map is rendered but never extracted, so a translator working from the bundle will not find those strings; coverage counts the locales the map carries; and no bundle key exists for it (nor will one be synthesised from a node path — #14749 Q3 = C3).
Same class as #15435 (that table omitting the
bulkActionsanddatasetsgroups), which was accepted and fixed.How this was read
Per-file, not by a tree-wide grep.
content/docs/ui/translations.mdxandcontent/docs/protocol/kernel/i18n-standard.mdxwere read in full; neither states any claim PR #15980 falsifies, and neither mentions inline maps at all. Positive control on the same command and scope: the phrase "inline locale map" fires on 12 files undercontent/docs/, and 0 of those 12 are outsidecontent/docs/references/— the auto-generated tree, where every occurrence is a schema.describe()about rendering, none about coverage or extraction. So the null on the hand-written pages is a measurement, not a silent empty.Related: #14749 (the ruling and the behaviour change), #14816, #14817, #15435.