What
validate-translation-references collects the _validations universe with a flat walk of objects[].validations[]:
// packages/lint/src/validate-translation-references.ts:629for(construleofasArray(obj.validations)){construleName=strName(rule.name);if(ruleName)facts.validations.add(ruleName);}A conditional rule's then / otherwise branch is a full rule carrying its own name, and that branch name is the address the resolver uses — checkConditional delegates to the branch and authoredRuleMessage keys on the branch's rule.name. So a bundle entry for a branch is correct and resolves at runtime, but this walk never sees the name and the rule reports it as an orphan.
Measured
Against the built rule (packages/lint/dist/index.js), a stack with one conditional carrying two named branches and bundle entries for all three names:
findings on _validations keys: 2
- translation-target-unknown | translations[0]["zh-CN"].objects.demo_account._validations.churn_reason_present
Translations are keyed to validation rule "churn_reason_present", which object "demo_account" does not
declare in `validations[]`. The rule's authored `message` keeps its source locale in every refusal.
- translation-target-unknown | translations[0]["zh-CN"].objects.demo_account._validations.churn_reason_absent
(same)
The wrapper's own name (churn_reason_consistency) is accepted; the two branches — the only two whose messages a caller ever reads — are flagged.
Two things are wrong at once:
- False positive. The entry is legitimate.
packages/objectql/src/validation/rule-validator.test.ts pins the behaviour directly: it('addresses a nested conditional branch by the BRANCH's own name', ...), with the wrapper's entry present and deliberately NOT rendered. - The advice is inverted. The finding says the authored message "keeps its source locale in every refusal" — the opposite of the truth for these keys. Acting on it (deleting the entry) reintroduces the exact defect the entry fixes, and the message reads as authoritative.
Blast radius today
Severity is warning, and the rule id is translation-target-unknown — not the i18n/ prefix scripts/check-i18n-coverage.mjs counts (countI18nRuleIssues filters rule.startsWith('i18n/')), so no gate goes red. It is noise plus wrong advice, not a build break.
It is live in the tree as of #14518: showcase_account now carries churn_reason_present / churn_reason_absent entries, which is what a nested conditional requires.
Suggested shape
Descend into then / otherwise when building ObjectFacts.validations, the same recursion evaluateRule performs. Same shape as the missing-leg family (#13835_tabs, #11608flows), except the leg exists here and its universe is short.
Worth checking in the same pass whether a conditionalwrapper's own name should stay in the universe. Its message is structurally unreachable (checkConditional either returns nothing, returns unevaluableRuleError — which builds its own sentence — or delegates), so an entry for it is inert. #14518 keeps one deliberately, so the bundle mirrors the declared rule set 1:1 and its pin can ask for every declared name rather than re-deriving objectql's dispatch; if the rule ever grows a "redundant, never rendered" verdict, that is the shape to weigh it against.
Where
packages/lint/src/validate-translation-references.ts:629 — the flat walk.packages/objectql/src/validation/rule-validator.ts — checkConditional / authoredRuleMessage, the addressing this must mirror.
Found while implementing #14518; outside that card's declared file surface, so recorded here rather than widened into its PR.
Generated by Claude Code
What
validate-translation-referencescollects the_validationsuniverse with a flat walk ofobjects[].validations[]:A
conditionalrule'sthen/otherwisebranch is a full rule carrying its ownname, and that branch name is the address the resolver uses —checkConditionaldelegates to the branch andauthoredRuleMessagekeys on the branch'srule.name. So a bundle entry for a branch is correct and resolves at runtime, but this walk never sees the name and the rule reports it as an orphan.Measured
Against the built rule (
packages/lint/dist/index.js), a stack with oneconditionalcarrying two named branches and bundle entries for all three names:The wrapper's own name (
churn_reason_consistency) is accepted; the two branches — the only two whose messages a caller ever reads — are flagged.Two things are wrong at once:
packages/objectql/src/validation/rule-validator.test.tspins the behaviour directly:it('addresses a nested conditional branch by the BRANCH's own name', ...), with the wrapper's entry present and deliberately NOT rendered.Blast radius today
Severity is
warning, and the rule id istranslation-target-unknown— not thei18n/prefixscripts/check-i18n-coverage.mjscounts (countI18nRuleIssuesfiltersrule.startsWith('i18n/')), so no gate goes red. It is noise plus wrong advice, not a build break.It is live in the tree as of #14518:
showcase_accountnow carrieschurn_reason_present/churn_reason_absententries, which is what a nestedconditionalrequires.Suggested shape
Descend into
then/otherwisewhen buildingObjectFacts.validations, the same recursionevaluateRuleperforms. Same shape as the missing-leg family (#13835_tabs, #11608flows), except the leg exists here and its universe is short.Worth checking in the same pass whether a
conditionalwrapper's own name should stay in the universe. Its message is structurally unreachable (checkConditionaleither returns nothing, returnsunevaluableRuleError— which builds its own sentence — or delegates), so an entry for it is inert. #14518 keeps one deliberately, so the bundle mirrors the declared rule set 1:1 and its pin can ask for every declared name rather than re-deriving objectql's dispatch; if the rule ever grows a "redundant, never rendered" verdict, that is the shape to weigh it against.Where
packages/lint/src/validate-translation-references.ts:629— the flat walk.packages/objectql/src/validation/rule-validator.ts—checkConditional/authoredRuleMessage, the addressing this must mirror.Found while implementing #14518; outside that card's declared file surface, so recorded here rather than widened into its PR.
Generated by Claude Code