Split out of #7142, which fixed one literal in packages/plugin-detail/src/ActivityTimeline.tsx (the empty-state title). Sweeping the file while implementing that fix found the empty state was not a lone oversight: the component uses no translation hook at all on origin/main — it imported nothing from @object-ui/i18n and made no t() call anywhere. Filed rather than fixed, because widening #7142's scope was the PM's call, not the implementing agent's.
Measured
Rendered ActivityTimeline with activities={[]} under a zh I18nProvider, and its sibling RecordActivityTimeline the same way, reading textContent off both containers:
[zh] ActivityTimeline => "Activity(0)No activity recorded"
[zh] RecordActivityTimeline => "活动(0)全部动态暂无活动记录"
[no-provider, filterable] ActivityTimeline => "Activity(0)AllField ChangesCreatesDeletesCommentsStatus ChangesNo activity recorded"
The sibling renders the same surface entirely in Chinese. After #7142 the zh card reads "Activity(0)暂无活动记录" — correct for the empty state, still English for the card title beside it.
Inventory
Line numbers are on the post-#7142 file. "pack key" means a key whose value in the en pack is already the exact literal, read out of the pack objects directly (not grepped — the packs are nested, so a dotted-key grep returns zero for keys that exist).
Relative timestamps, formatTimestamp:
| line | literal | existing key |
|---|
| 49 | 'just now' | detail.justNow |
| 50 | `${diffMins}m ago` | detail.minutesAgo = '{{count}}m ago' |
| 52 | `${diffHours}h ago` | detail.hoursAgo |
| 54 | `${diffDays}d ago` | detail.daysAgo |
Filter chips, FILTER_LABELS:
| line | literal | existing key |
|---|
| 82 | 'All' | none |
| 83 | 'Field Changes' | detail.fieldChangesFilter |
| 84 | 'Creates' | none |
| 85 | 'Deletes' | none |
| 86 | 'Comments' | detail.comments |
| 87 | 'Status Changes' | none |
Card chrome:
| line | literal | existing key |
|---|
| 109 | Activity (the CardTitle text node) | detail.activity |
| 118 | aria-label="Activity type filter" | none (detail.filterActivity = 'Filter activity' is close but not identical) |
Activity descriptions, formatFieldChange — sentences assembled in code, so these need new keys with interpolation holes rather than a lookup:
| line | literal | existing key |
|---|
| 66, 67, 74 | '(empty)' | none |
| 68 | `Changed ${fieldLabel} from "${oldVal}" to "${newVal}"` | none |
| 71 | 'Created this record' | none |
| 72 | 'Deleted this record' | none |
| 75 | `Changed status to "${newVal}"` | none |
| 78 | 'Updated record' | none |
So 12 of the 18 are a lookup swap against keys that already exist and are already translated in all ten packs; the remaining 6 (plus the two chip labels and the aria label) need new keys across en.ts and its nine siblings, with all-locales-key-parity green. Never an inline defaultValue (#3517).
Reachability caveat, worth checking before sizing this
DetailView gates the timeline on hasActivity = !!schema.activities && schema.activities.length > 0 (DetailView.tsx:1408) and never passes filterable, so from that host the filter chips never render and the empty state is unreachable. The chips and the filtered-empty path are reachable through the published export (packages/plugin-detail/src/index.tsx:118) by a consumer passing filterable. That may change how this is prioritized, but it does not change the timestamps or the card title, which render on every activity tab.
Unrelated observation in the same file
Filter is imported from lucide-react at line 11 and never used — eslint reports it as a warning today (@typescript-eslint/no-unused-vars, warning not error, so nothing is red).
Split out of #7142, which fixed one literal in
packages/plugin-detail/src/ActivityTimeline.tsx(the empty-state title). Sweeping the file while implementing that fix found the empty state was not a lone oversight: the component uses no translation hook at all onorigin/main— it imported nothing from@object-ui/i18nand made not()call anywhere. Filed rather than fixed, because widening #7142's scope was the PM's call, not the implementing agent's.Measured
Rendered
ActivityTimelinewithactivities={[]}under a zhI18nProvider, and its siblingRecordActivityTimelinethe same way, readingtextContentoff both containers:The sibling renders the same surface entirely in Chinese. After #7142 the zh card reads
"Activity(0)暂无活动记录"— correct for the empty state, still English for the card title beside it.Inventory
Line numbers are on the post-#7142 file. "pack key" means a key whose value in the
enpack is already the exact literal, read out of the pack objects directly (not grepped — the packs are nested, so a dotted-key grep returns zero for keys that exist).Relative timestamps,
formatTimestamp:'just now'detail.justNow`${diffMins}m ago`detail.minutesAgo='{{count}}m ago'`${diffHours}h ago`detail.hoursAgo`${diffDays}d ago`detail.daysAgoFilter chips,
FILTER_LABELS:'All''Field Changes'detail.fieldChangesFilter'Creates''Deletes''Comments'detail.comments'Status Changes'Card chrome:
Activity(the CardTitle text node)detail.activityaria-label="Activity type filter"detail.filterActivity='Filter activity'is close but not identical)Activity descriptions,
formatFieldChange— sentences assembled in code, so these need new keys with interpolation holes rather than a lookup:'(empty)'`Changed ${fieldLabel} from "${oldVal}" to "${newVal}"`'Created this record''Deleted this record'`Changed status to "${newVal}"`'Updated record'So 12 of the 18 are a lookup swap against keys that already exist and are already translated in all ten packs; the remaining 6 (plus the two chip labels and the aria label) need new keys across
en.tsand its nine siblings, withall-locales-key-paritygreen. Never an inlinedefaultValue(#3517).Reachability caveat, worth checking before sizing this
DetailViewgates the timeline onhasActivity = !!schema.activities && schema.activities.length > 0(DetailView.tsx:1408) and never passesfilterable, so from that host the filter chips never render and the empty state is unreachable. The chips and the filtered-empty path are reachable through the published export (packages/plugin-detail/src/index.tsx:118) by a consumer passingfilterable. That may change how this is prioritized, but it does not change the timestamps or the card title, which render on every activity tab.Unrelated observation in the same file
Filteris imported fromlucide-reactat line 11 and never used — eslint reports it as a warning today (@typescript-eslint/no-unused-vars, warning not error, so nothing is red).