Uh oh!
There was an error while loading. Please reload this page.
refactor(i18n): retire 25 confirmed-dead locale keys across all ten packs - #6662
Conversation
Each key cleared the objectui#4658 evidence standard individually: zero t() call sites, zero textual footprint outside the packs, and a read of its plausible consumer. Five namespaces held nothing but retired leaves and went with them: map, cellRender, rowAction, recordDetail, home.stats. Adds a negative retirement pin, following the five-instance convention in packages/i18n/src/__tests__/. Every i18n gate here runs call site to key, so a dead key returning to the packs is invisible to all of them. Seven console.* bootstrap strings the same sweep reported CONFIRMED-dead are LIVE and were kept: LoadingScreen.tsx reads them as object properties off the imported pack rather than through t(), which both legs of the sweep miss. The pin asserts they stay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-sales
commented
Aug 28, 2026
✅ ACCEPT — flipped ready, auto-merge onReviewer: Gate reading: all 29 check runs read, and the returned count was compared against What I re-measured myself, and where MY probe was the thing that was wrong
Two of my three first-pass probes were faulty, and both faults are worth recording because they are the same class this PR is about:
The corrected reading used Why the headline finding is worth more than the deletionFive keys this sweep reported CONFIRMED-dead at its strongest tier are live, and the dev pulled them back out. The fourth leg added to close this carried its own positive and negative control before being trusted: the 5 keys proved live by reading source were detected; Two other things I am accepting deliberately
Also accepted: the batch was capped by evidence rather than by count —
Follow-up Generated by Claude Code |
Refs #4730
Fixes#5783
Retires 25 individually hand-confirmed dead locale keys from all ten packs — 250 translated strings with no reader anywhere in the repo. The umbrella #4730 stays open: it still carries the held key-level trims (
search,table,report,form,appDesigner), which is why this isRefsand not a closing keyword.Measured at
09a1f9b3, merge-base813bf8323.The most important finding: the evidence standard has a fourth blind spot
Five keys this sweep reported CONFIRMED-dead in this very round are LIVE. They were pulled back out of the batch after reading their consumer, and are now pinned as live.
packages/app-shell/src/chrome/LoadingScreen.tsxis bootstrap-critical UI: it must render before i18n loads, which is exactly when the server is unreachable. So it deliberately does not calluseObjectTranslation— its own comment says so. Instead it imports the packs directly and reads them as plain object properties:That consumer is invisible to both legs of the #4658 standard:
t()/tt()calls, and there is no call;strings.loadingSteps.connecting, neverconsole.loadingSteps.connecting.So the sweep reports the whole family at its strongest tier with no hint anything was missed. Deleting it ships a blank splash screen in ten locales on exactly the server-down boot the screen exists to explain.
Kept and pinned:
console.loadingSteps.connecting/.loadingConfig/.preparingWorkspace,console.error.connectionFailed/.checkServer, plusconsole.initializing,console.loadingHint,console.actions.retry/.retrying.A fourth leg was added to close this for the batch — a property-chain probe on the tail (
.loadingSteps.connecting) rather than the full dotted key. It was validated with a positive and negative control before being trusted: the 5 keys proved live by reading source → detected;console.error.serverUnreachable/.timeout, which genuinely have no pack reader → 0.Step 1 — re-derived counts, and why a matching number was NOT confirmation
062943f86)813bf8323)Candidates and namespaces match the refresh exactly. That is a collision, not a match, and the population moved underneath it — measured with the instrument's own
collectEnKeys:2848→2856(9 added, allpackagedAutomation.*; 1 removed,appDesigner.fieldDesigner.formula, the finding(i18n):appDesigner.fieldDesigner.formulais now a dead key — 10 locale packs plus the designer defaults map, zero call sites #6310 retirement)en.tsnet +22 linesSo 389 = 389 sits on top of a 10-key population change with 4 keys migrating CONFIRMED → NEEDS-REVIEW. Reading the equal total as "nothing changed" would have been the wrong reading of a right number.
Step 2 — the batch, and how each key was confirmed
25 keys, deliberately bounded. Each cleared all four legs individually; none was taken from the tool's output.
Superseded twin vocabularies — a
grid.*vocabulary won, and the twin lost its reader:rowAction.openMenu/.edit/.deletegrid.openMenu/grid.edit/grid.deleteRowActionMenu.tsx:638,652,663cellRender.empty/.yes/.no/.systemFieldsgrid.empty/grid.yes/grid.no/grid.systemFieldsObjectGrid.tsx:3670,1725,1726,3726RowActionMenu.tsxis fully i18n-wired and demonstrably reads the other namespace — the strongest leg-3 evidence available, since the consumer exists and can be shown to ask for something else.Labels that outlived their control:
calendar.agenda— labelled a view mode finding(types/plugin-calendar):view: 'agenda'is declared byCalendarViewModeand the zod enum but thecalendar-viewrenderer silently falls back to'month'#5740 retired;CalendarViewModeis now'month' | 'week' | 'day'and the zod enum matches. The switcher renders exactlyday/week/month(CalendarView.tsx:347-349) andDEFAULT_TRANSLATIONS(lines 77-85) carries no entry. This is what finding(i18n):calendar.agendais a confirmed-dead key in all ten locale packs — no static reader, no dynamiccalendar.head, and the enum value it labeled is retired #5783 recorded.calendar.noEvents— no empty-state renders it.home.quickActions.createApp/.createAppDesc—QuickActions.tsxis wired and readsmanageObjects,systemSettings,title; not these.layout.systemNav.createApp—AppSidebar.tsx/UnifiedSidebar.tsxread ~12 siblingsystemNavkeys; not this one.actionDialog.defaultActionTitle/.ok—ActionParamDialog.tsxreadstitle,description,cancel,confirm; the retired pair are superseded spellings of two of those.grid.bulk.selectPlaceholder—BulkActionDialog.tsxreads a dozengrid.bulk.*keys; not this one.chart.noData— the livechart.nullCategorysurvives;dashboard.noDataAvailableis a different, live key.Surfaces that left the product:
map.*(5 keys) — the strongest form.@object-ui/plugin-mapdeclares no@object-ui/i18ndependency and contains not()call at all, so it cannot consume a locale string. The messages have no hardcoded twin either.home.stats.*(3) andrecordDetail.viewersTooltip— name surfaces nothing renders.Five namespaces held nothing but retired leaves and went with them:
map,cellRender,rowAction,recordDetail,home.stats.Counter-probe — per key, not per total
Identical
grep -rFn -Ipipeline, locale packs excluded, for candidates and controls.calendar.today3,.day2,.week2,.month2,.newEvent3,.moreEvents4,.allDay2,grid.empty2,grid.yes10,grid.no10,grid.systemFields2,grid.openMenu2,grid.edit3,grid.delete3,home.quickActions.manageObjects2,.title1,.systemSettings2,actionDialog.title5,.description2,.cancel2,.confirm2,layout.systemNav.systemSettings2,.applications2,.objectManager2,chart.nullCategory13,grid.bulk.confirmDefault1,.affectedRecords1,.retry1.Reported per key precisely because a total of 76 would be satisfied by one control hitting 76 times while the rest were as invisible as the retired keys.
Separately, the one fully-variable dynamic head in the repo (
useObjectLabel.ts:604,t(`${ns}.${suffix}`)) was read rather than assumed:nscomes fromgetAppNamespaces()(app metadata namespaces only) and everysuffixcontainsglobalActions.orobjects.…_actions.. It cannot reach any candidate.Pack parity, proved before the first line came out
The deletion tool resolves every target key in every pack through the TypeScript AST and refuses to write anything unless all resolve:
Landing sites are AST node ranges, not regex anchors — uniqueness is by construction (a path resolves to exactly one property or the run aborts), and every site was printed as
file:linebefore any write. Result:10 files changed, 350 deletions(-), 35 per pack, zero insertions in any pack. Uniformity is a property of the method, not something the parity assertion had to catch afterwards.Reverse verification — both legs, direction predicted first
Restore under
trap … EXIT INT TERM;git diff HEADempty after each.Leg A — restore one deleted key (
calendar.agenda,enonly). Anchor uniqueness asserted before writing:^ calendar: {occurs exactly 1 time. Landing site printed (en.ts:610) and confirmed namespace-aware via the AST —calendar.agendaresolves YES,grid.import.agendaresolves no. Injection proved on disk (0 → 1).calendar.agenda, with CONFIRMED unmoved. The reason is structural — the new pin lists every retired key as a literal string, so a returning key now has a textual footprint and lands in NEEDS-REVIEW rather than CONFIRMED. Same mechanism, measured: committing the pin moved the 5 rescued bootstrap keys from CONFIRMED to NEEDS-REVIEW (121 → 116), whileconsole.error.serverUnreachable/.timeout— the two with genuinely no pack reader — correctly stayed CONFIRMED. The pin does not merely assert; it demotes these in the instrument's own output so a future round will not see them at the top tier.The rest of Leg A fired as predicted: the pin went red on exactly one assertion, naming
en :: calendar.agenda, with the other five green;all-locales-key-paritywent red namingcalendar.agendamissing from the other nine packs.Leg B — delete one LIVE key (
calendar.today, all ten packs). This is the discriminating leg.check:i18n-keysEXIT=1,2585/2586 literal keys resolve(was 2586/2586), namingpackages/plugin-calendar/src/CalendarView.tsx:291:17 [missing-key] calendar.today.the deletion swept around the live siblingsRED (expected [ 'en :: calendar.today', …(9) ]), whileno pack defines any retired key,no pack defines a namespace root …,keeps the grid.* vocabulary …andkeeps the bootstrap strings …all stayed GREEN.A 1-failed/5-passed split is the proof the classifier separates live from dead; a uniform red would have proved nothing.
Leg B's landing evidence is the tool's per-pack presence assertion plus
git diff --numstat(10 packs, 0 insertions, 10 deletions) and the AST check thatcalendar.todayis absent whilecalendar.daysurvives.Why a negative pin was added
packages/i18n/src/__tests__/dead-key-batch-retired-4730.test.ts, following the five-instance convention already in that directory. It is load-bearing: every i18n gate in this repo runs call site → key, so a dead key coming back into the packs is invisible to all of them.check-i18n-dead-keys.mjsis the only reverse-direction reader and is report-only by design. The pin is the sole guard on that direction, and it also pins the live-but-reported-dead bootstrap family so the next round cannot repeat the near-miss.Gates (exit codes captured before any pipe, verdicts quoted)
All at
09a1f9b3, clean tree.pnpm exec vitest run packages/i18n/EXIT=0 —Test Files 57 passed (57),Tests 923 passed (923)pnpm --filter @object-ui/i18n type-checkEXIT=0 — script echoed (tsc --noEmit && tsc -p tsconfig.test.json), so not a zero-match no-op. Coverage of the new pin proved with--listFiles: 1 hit undertsconfig.test.json, 0 under the base config (tests are excluded there) — so "type-check is clean" genuinely covers the new file.pnpm --filter @object-ui/i18n lintEXIT=0 — 0 errors, 34 pre-existing warnings; the new file alone: 1 file, 0 errors, 0 warnings. (Plaineslint ., this repo's actual spelling —--no-inline-configmanufactures phantom errors and was not used.)pnpm check:i18n-keysEXIT=0 — "Every in-scope call-site key resolves against the en pack (2831 keys)"pnpm check:i18n-driftEXIT=0 — "0 en value(s) changed (0 key(s) added, 25 removed)"pnpm check:control-bytesEXIT=0 — "scanned 5500 tracked text file(s)"pnpm check:published-distEXIT=0 — "Inspected 39 published package(s)"pnpm check:phantom-deps/check:entry-guard/check:esm-specifiers/check:self-import— all EXIT=0node scripts/check-changeset-presence.mjsEXIT=0 — "10 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"pnpm exec vitest run packages/app-shell/src/__tests__/defaults-maps-mirror-en-pack.test.tsx packages/plugin-calendar/ packages/plugin-map/ packages/plugin-dashboard/EXIT=0 —Test Files 114 passed (114),Tests 984 passed (984)pnpm exec vitest run packages/plugin-grid/EXIT=0 —Test Files 96 passed (96),Tests 884 passed (884)node scripts/check-i18n-dead-keys.mjsEXIT=0 — before2856pack keys /146CONFIRMED; after2831/116, of which 25 are this deletion and 5 are the pin demoting the rescued bootstrap keys. NEEDS-REVIEW is untouched at 243→248 (+5 from that same demotion), which is the scope fence showing up in the instrument's own output.--strictexits 1 both before and after this change. That1is the standing backlog, not a regression from this PR.Scope fence
NEEDS-REVIEWtier untouched in every namespace.search(partially live, 7t()sites) andappDesigner(heavy indirect-reference tier) not touched.scripts/check-i18n-dead-keys.mjsuntouched and still report-only.Generated by Claude Code