Repository navigation
i18n-extract scaffolds none of the three key families #14253 adds, so check:i18n-coverage is blind to them #14376
Description
Found while implementing #14253. Filed rather than fixed in that PR because closing it moves nine generated bundles and scripts/i18n-coverage-baseline.json, and that ratchet movement should be readable on its own rather than buried inside a contract change.
What is missing
#14253 gives three authored display surfaces their first bundle keys:
objects.OBJECT._views.VIEW.bulkActions.NAME.{label,confirmText,confirmLabel,params.PARAM.{label,help,placeholder}}objects.OBJECT._validations.RULE.messagedatasets.NAME.{label,description,dimensions.DIM.label,measures.MEASURE.label}
All three are resolved at runtime by that PR. None of them is emitted by the extractor (packages/cli/src/utils/i18n-extract.ts), whose module header enumerates the families it walks — _sections, _views (label / description / emptyState), _actions (incl. params and resultDialog), _tabs, dashboards, pages.components, flows.screens, metadataForms.
Why that matters, beyond convenience
Two consequences, and the second is the one that decays quietly:
os i18n extractwill not scaffold the new keys, so a translator has to know they exist and hand-write them. That is the ordinary cost of an un-extracted family and it is survivable.pnpm check:i18n-coveragecannot see them. That ratchet measures untranslated declared labels against what the extract walk produces, so a family the walk does not visit contributes nothing to coverage — the number stays green while the surface it is supposed to describe grows. Same shape as the gate families this repo keeps finding dormant: not wrong, just silently narrower than it reads.
Precedent, both directions
This is the third time this exact follow-up has been needed, which is itself worth noticing: #11485 added the flow/screen bucket after #11287 landed the keys, and #13109 found the per-component pass stuck at region level after translatePage learned nested children. Both were filed the same way and closed on their own. The recurring shape is resolver first, extractor second, and nothing today makes the second half visible when the first lands.
Worth considering whether the fix is a fourth manual walk or a structural one — the resolvers and the extractor are two hand-maintained enumerations of the same key set, which is the same "second vocabulary" that TRANSLATABLE_METADATA_TYPES was made derived to avoid (#3786).
Measured today, so the baseline movement is predictable
No package that owns an i18n-extract.config.ts (platform-objects, five plugins, three services) currently authors a bulkActionDefs, a validations[].message, or a dataset — so wiring the emitters moves zero platform bundles. scripts/i18n-coverage-baseline.json may still move: check:i18n-coverage reads example apps, and examples/app-showcase, examples/app-crm and examples/app-todo do ship *.dataset.ts files.
Acceptance
- The three families are emitted by the extract walk, gated the way the existing ones are.
pnpm check:i18nandpnpm check:i18n-coveragegreen, with any baseline movement explained in the PR body rather than regenerated silently.- A test that fails without the new emitters, per family.
Generated by Claude Code
Activity
huangyiirene commentedon Sep 2, 2026
Triage:
bug·tooling·priority:p2·pm:blockedon #14253 ·domain:cli· typeBug.Blocked-by: #14253was already in the body at line start — correct channel, nothing to backfill.pm:blockingderived onto #14253.domain:clibecause the fix lands inpackages/cli/src/utils/i18n-extract.ts;scripts/i18n-coverage-baseline.jsonmoves as a consequence of that edit, not as its subject.p2 rather than p3 on the second consequence, not the first. An un-scaffolded family costs a translator some typing — survivable, as the card says. The one that matters is that
check:i18n-coveragemeasures against what the extract walk produces, so a family the walk never visits contributes nothing to the ratchet: the number stays green while the surface it claims to describe grows. That is the same dormant-gate shape as #14386, graded this round, and the same reason: a gate that cannot fail on a population is counted as if it could.The acceptance criteria as written are the right ones and I am not adding to them. In particular: ⛔ do not regenerate
scripts/i18n-coverage-baseline.jsonsilently. The card's own measurement says platform bundles move zero (no package owning ani18n-extract.config.tsauthors abulkActionDefs, avalidations[].messageor a dataset today) while the example apps do ship*.dataset.ts, so any baseline movement is example-app movement and must be explained line by line in the PR body. A baseline diff with no explanation is indistinguishable from the gate being loosened.A test per family that fails without its emitter — not one test over the three. Three families, three ways to half-land this.
The structural question: recorded, deliberately not carded today
The card is right that this is the third instance of the same shape — #11485 after #11287, #13109 after
translatePagelearned nested children, now this after #14253 — and right that the underlying cause is two hand-maintained enumerations of one key set, the exact thingTRANSLATABLE_METADATA_TYPESwas made derived to avoid (#3786). It is also the pattern #12911 is about: a class that recurs because the remedy each time is a rule ("remember the extractor") rather than a construct.⛔ I am not filing the structural card yet, and the reason is the anchoring rule rather than appetite: nobody has measured whether the extractor's family set is derivable from the resolvers at all, and a card that cannot name the file the fix lands in is not triaged. ⇒ Do the fourth manual walk here, because it is owed regardless of what the structural answer turns out to be, and whoever implements it should record what they learn about derivability while inside that file — that reading is what turns this into a fileable card instead of a wish. Carried into the round report as the one structural candidate on the board.
Size/model suggestion: M,
sonnet, once unblocked — three emitters plus three tests, with the baseline movement read rather than accepted.Generated by Claude Code
huangyiirene commentedon Sep 2, 2026
Unlock scan (R+92): released —
pm:blocked→pm:queue. #14253 has landed; the exhaustedBlocked-by: #14253line is deleted from the body in the same stroke.Both halves re-verified on
origin/main@f7d92d3before releasing:translateDatasetis now present in the generated spec surface (packages/spec/api-surface/system.json,export-origins/system.json).git grep -c "_validations\|bulkActions\|datasets" -- packages/cli/src/utils/i18n-extract.ts→ 0. None of the three families reaches the extract walk. ⇒ The coverage ratchet is blind to them as of now, not prospectively.So the second consequence in the card is live today:
check:i18n-coveragemeasures against what the walk produces, and three resolved families contribute nothing to it. Everything in my R+91 grading note still stands — in particular ⛔ do not regeneratescripts/i18n-coverage-baseline.jsonsilently: platform bundles should move zero (no package owning ani18n-extract.config.tsauthors abulkActionDefs, avalidations[].messageor a dataset), so any movement is example-app movement and gets explained line by line in the PR body. And a test per family, not one across the three.pm:blockedwith only the #14253 line discharged.Size/model suggestion: M,
sonnet.Generated by Claude Code