Found by the new factory-default-drift comparison in scripts/check-i18n-call-site-keys.mjs (objectui#7567 / PR objectui#7870), while sizing that rule's abstention bucket. Not fixed there: that card's file surface was the gate script and its test, and this is a runtime-package edit.
The rows
packages/plugin-timeline/src/useTimelineTranslation.ts:26-30, inside TIMELINE_DEFAULT_TRANSLATIONS:
'timeline.relative.today': 'Today',
'timeline.relative.tomorrow': 'Tomorrow',
'timeline.relative.yesterday': 'Yesterday',
'timeline.relative.inDays': 'In {{n}} days',
'timeline.relative.daysAgo': '{{n}} days ago',
Two facts, both checked on origin/main at 6bca0e4e8:
- No
en leaf. packages/i18n/src/locales/en.ts defines no timeline.relative.* key. (It has timeline.bucket.* and timeline.scale.*, both fully defined and fully aligned; this one subtree is not there.)
- No caller, anywhere.
git grep 'timeline\.relative' over the whole tree — sources, tests and JSON, node_modules and dist excluded — returns exactly these five declaration lines and nothing else.
So they render for nobody: fallbackT only reaches a row when some call site asks for its key, and none does.
Why no existing rule can see them
This is the part worth recording, because it is not an oversight in any one gate — it is a gap between three of them:
check-i18n-call-site-keys's missing-key class judges call sites. A table row with no call site has none, so class 1 is structurally unable to reach it.
all-locales-key-parity.test.ts compares packs to each other. Ten packs identically lacking a key is full parity, and full parity is green.
check-i18n-dead-keys sweeps in the other direction — pack keys with no call site — and these keys are in no pack to be swept.
- The new
factory-default-drift class reads the row, but abstains: a key en does not define is class 1's shape by construction, and the two classes are kept disjoint on purpose. What it does do is print the count, which is how these five surfaced at all. They are the entire abstention bucket: 5 of 846 factory-table rows on this tree.
The fix is a product question, not a mechanical one
Two readings, and picking one silently is the thing to avoid:
- Dead — the relative-date labels were superseded (
formatRelativeDate in @object-ui/core does this job now) and these rows are residue. Then the fix is deleting the five lines.
- Never wired up — the timeline was meant to render relative dates through
t() and the call sites were never written. Then the fix is adding the five keys to packages/i18n/src/locales/en.ts (which makes all-locales-key-parity.test.ts demand them in the other nine packs, which is the correct order) and calling them.
⚠️ Note the {{n}} holes in two of the rows: if the second reading is right, {{n}} is what fallbackT resolves and what objectui#3512 holds the copy to, so the spelling is already correct and should be kept as-is rather than renamed to {{count}} without checking what the call site would pass.
⛔ Do not "fix" this by making the new gate compare them anyway — with no en value there is nothing to compare against, and the abstention is correct.
Lands in: packages/plugin-timeline (and packages/i18n if the second reading wins).
Filed by the os-dev seat on objectui#7567, session https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3. Channel: MCP issue_write — repo-scoped REST issue creation was refused by the sandbox classifier on this seat.
Found by the new
factory-default-driftcomparison inscripts/check-i18n-call-site-keys.mjs(objectui#7567 / PR objectui#7870), while sizing that rule's abstention bucket. Not fixed there: that card's file surface was the gate script and its test, and this is a runtime-package edit.The rows
packages/plugin-timeline/src/useTimelineTranslation.ts:26-30, insideTIMELINE_DEFAULT_TRANSLATIONS:Two facts, both checked on
origin/mainat6bca0e4e8:enleaf.packages/i18n/src/locales/en.tsdefines notimeline.relative.*key. (It hastimeline.bucket.*andtimeline.scale.*, both fully defined and fully aligned; this one subtree is not there.)git grep 'timeline\.relative'over the whole tree — sources, tests and JSON,node_modulesanddistexcluded — returns exactly these five declaration lines and nothing else.So they render for nobody:
fallbackTonly reaches a row when some call site asks for its key, and none does.Why no existing rule can see them
This is the part worth recording, because it is not an oversight in any one gate — it is a gap between three of them:
check-i18n-call-site-keys'smissing-keyclass judges call sites. A table row with no call site has none, so class 1 is structurally unable to reach it.all-locales-key-parity.test.tscompares packs to each other. Ten packs identically lacking a key is full parity, and full parity is green.check-i18n-dead-keyssweeps in the other direction — pack keys with no call site — and these keys are in no pack to be swept.factory-default-driftclass reads the row, but abstains: a keyendoes not define is class 1's shape by construction, and the two classes are kept disjoint on purpose. What it does do is print the count, which is how these five surfaced at all. They are the entire abstention bucket: 5 of 846 factory-table rows on this tree.The fix is a product question, not a mechanical one
Two readings, and picking one silently is the thing to avoid:
formatRelativeDatein@object-ui/coredoes this job now) and these rows are residue. Then the fix is deleting the five lines.t()and the call sites were never written. Then the fix is adding the five keys topackages/i18n/src/locales/en.ts(which makesall-locales-key-parity.test.tsdemand them in the other nine packs, which is the correct order) and calling them.{{n}}holes in two of the rows: if the second reading is right,{{n}}is whatfallbackTresolves and what objectui#3512 holds the copy to, so the spelling is already correct and should be kept as-is rather than renamed to{{count}}without checking what the call site would pass.⛔ Do not "fix" this by making the new gate compare them anyway — with no
envalue there is nothing to compare against, and the abstention is correct.Lands in:
packages/plugin-timeline(andpackages/i18nif the second reading wins).Filed by the
os-devseat on objectui#7567, sessionhttps://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3. Channel: MCPissue_write— repo-scoped REST issue creation was refused by the sandbox classifier on this seat.