Found while implementing #7149 (PR #7162), which fixed this exact defect in packages/plugin-detail/src/ActivityTimeline.tsx. Filed rather than swept in: those files are outside that card's declared lane.
Measured on origin/main head 85b5077d7
Two more files in packages/plugin-detail hold a byte-identical copy of the same formatTimestamp helper, still returning hardcoded English:
packages/plugin-detail/src/RecordComments.tsx:28-45
if (diffMins < 1) return 'just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
packages/plugin-detail/src/PointInTimeRestore.tsx:38-48 — the same, minus the days branch (it falls through to toLocaleString() after hours).
Why they are not equivalent to each other
The keys already exist
No new keys are needed for the four relative-time strings — detail.justNow, detail.minutesAgo ('{{count}}m ago'), detail.hoursAgo, detail.daysAgo are present in all ten packs and already translated. Verified by reading the pack objects directly, not by a dotted-key grep (the packs are nested, so a grep for detail.justNow returns a false zero against the en pack that defines it); positive control detail.back / detail.noActivity / detail.edit = 10/10, negative control detail.zzzAbsentControl7149 = 0/10.
Three call sites in this same package already consume them exactly this way — RecordActivityTimeline.tsx:152-157, RecordMetaFooter.tsx:51-57, ThreadedReplies.tsx:37-42 — so the target shape is established and needs no design:
if (diffMins < 1) return t('detail.justNow');
if (diffMins < 60) return t('detail.minutesAgo', { count: diffMins });
RecordComments is therefore a pure lookup swap. PointInTimeRestore additionally needs the hook wired, and whoever takes it should sweep that file for its other literals rather than converting only the timestamps.
Not in scope here, but worth a look together
apps/console/src/pages/system/ApprovalsInboxPage.tsx:647-649 reaches the same copy through tr('justNow', 'just now') — an inline fallback rather than a bare key. Different pattern, possibly deliberate; noted only so a sweep does not miss it.
Evidence bar for whoever takes this
An en-only test is green before the fix too — each key's en value is byte-identical to the literal it replaces — so English cannot discriminate a pack lookup from a hardcoded string. Assert in zh/ja/ar, and assert the provider-less path separately (it must yield the English default from DETAIL_DEFAULT_TRANSLATIONS, never a raw key, and it must interpolate {{count}}).
Never an inline defaultValue (#3517).
Found while implementing #7149 (PR #7162), which fixed this exact defect in
packages/plugin-detail/src/ActivityTimeline.tsx. Filed rather than swept in: those files are outside that card's declared lane.Measured on
origin/mainhead85b5077d7Two more files in
packages/plugin-detailhold a byte-identical copy of the sameformatTimestamphelper, still returning hardcoded English:packages/plugin-detail/src/RecordComments.tsx:28-45packages/plugin-detail/src/PointInTimeRestore.tsx:38-48— the same, minus the days branch (it falls through totoLocaleString()after hours).Why they are not equivalent to each other
RecordComments.tsxalready uses the translation hook — 11useDetailTranslation/t('detail.…')references in the file. So it is precisely theActivityTimelineshapeActivityTimelinehas 18 more hardcoded English literals — the empty state was one of a set, and 12 of them already have pack keys sitting unused #7149 described: a component that is wired to the packs, with one helper that never got converted. A zh session reads translated comment chrome next to an English5m ago.PointInTimeRestore.tsxuses no translation hook at all (0 references) — the wholly-unwired shape, so its scope is larger than a lookup swap.The keys already exist
No new keys are needed for the four relative-time strings —
detail.justNow,detail.minutesAgo('{{count}}m ago'),detail.hoursAgo,detail.daysAgoare present in all ten packs and already translated. Verified by reading the pack objects directly, not by a dotted-key grep (the packs are nested, so a grep fordetail.justNowreturns a false zero against theenpack that defines it); positive controldetail.back/detail.noActivity/detail.edit= 10/10, negative controldetail.zzzAbsentControl7149= 0/10.Three call sites in this same package already consume them exactly this way —
RecordActivityTimeline.tsx:152-157,RecordMetaFooter.tsx:51-57,ThreadedReplies.tsx:37-42— so the target shape is established and needs no design:RecordCommentsis therefore a pure lookup swap.PointInTimeRestoreadditionally needs the hook wired, and whoever takes it should sweep that file for its other literals rather than converting only the timestamps.Not in scope here, but worth a look together
apps/console/src/pages/system/ApprovalsInboxPage.tsx:647-649reaches the same copy throughtr('justNow', 'just now')— an inline fallback rather than a bare key. Different pattern, possibly deliberate; noted only so a sweep does not miss it.Evidence bar for whoever takes this
An
en-only test is green before the fix too — each key'senvalue is byte-identical to the literal it replaces — so English cannot discriminate a pack lookup from a hardcoded string. Assert in zh/ja/ar, and assert the provider-less path separately (it must yield the English default fromDETAIL_DEFAULT_TRANSLATIONS, never a raw key, and it must interpolate{{count}}).Never an inline
defaultValue(#3517).