Skip to content

fix(homepage): restore missing common.* i18n keys and add a key-completeness test (closes #238) - #263

Open
dchaudhari7177 wants to merge 1 commit into
schutera:mainfrom
dchaudhari7177:fix/restore-missing-common-i18n-keys
Open

fix(homepage): restore missing common.* i18n keys and add a key-completeness test (closes #238)#263
dchaudhari7177 wants to merge 1 commit into
schutera:mainfrom
dchaudhari7177:fix/restore-missing-common-i18n-keys

Conversation

@dchaudhari7177

Copy link
Copy Markdown

Addresses #238 (auto-close keyword kept in the title only, per CLAUDE.md's rule about bodies).

The restore

common.unknown and common.heartbeatDataIncomplete, in both en and de.

The wording is recovered from 8c1be9c, not rewritten — I pulled the exact bytes out of the deleting commit so the strings are byte-identical to what shipped in ff1ada1, including the em dashes and the literal UTF-8 umlauts (the file mixes ü escapes and literal characters; these particular strings were literal). They also go back in their original position, between offline and loading, so the diff is a clean revert of the accidental hunk rather than an append.

Confirmed the four call sites the issue names: ModulePanel.tsx:208, DashboardPage.tsx:379, DashboardPage.tsx:537 for common.unknown, and DashboardPage.tsx:164 for the degraded banner.

The guard

New homepage/src/__tests__/i18nKeyCompleteness.test.ts: walks homepage/src/**/*.tsx? (skipping __tests__, which may reference deliberately-absent keys), extracts every single-quoted t('...') literal, and asserts each resolves in every locale.

Three things it does that a naive version would not, all found while writing it:

  1. It accepts plural forms. isRenderable() mirrors what LanguageContext.t() can actually return — a string, or an object with a string other branch that t() selects via Intl.PluralRules. Without that, the test reports dashboard.modulesListed as missing, which is wrong: it is an intentional { one, other } object, and the comment above it in translations.ts explains exactly why German needs it. Arrays like setup.stepLabels stay excluded, since those are read with useTranslationRaw(), not t().

  2. It asserts it found something. expect(callSites.length).toBeGreaterThan(20) guards the scan itself. This earned its place immediately: my first version used __dirname, which is undefined under vitest's ESM loader, so the walk returned zero files — and every other assertion passed vacuously. Without that guard I would have shipped a test that could never fail.

  3. It checks locale parity. A flattened key list per locale must be identical, so a key added to en only is caught before a user sees the English string in a German UI.

Verifying the guard actually catches this bug

Deleting the restored en common.unknown again makes it fail with the key and its call sites:

+   "common.unknown  (components\ModulePanel.tsx)",
+   "common.unknown  (pages\DashboardPage.tsx)",

and the parity test fires too. Restoring returns it to green.

Docs

docs/11-risks-and-technical-debt/README.md gains a "Lessons learned" entry in the required What happened / Why / How to avoid format, per CLAUDE.md's mandatory docs gate. The lesson worth keeping is the general one: a graceful fallback needs a test that notices when it fires. t() degrading to the key path instead of throwing is what made this invisible to the type checker, the 191-test suite and the build simultaneously.

make check-citations7 OK, 0 problems.

Verification

cd homepage && npx vitest run33 files, 198 tests, 0 failures, including the 5 new ones.

One deviation to flag

CLAUDE.md's end-of-implementation gate asks for the senior-reviewer subagent. I was not able to run it in my environment, so this PR has not been through that gate — please treat it as un-reviewed by that standard. Everything else in the gate (tests, make check-citations, docs update, branch/commit conventions) has been done.

common.unknown and common.heartbeatDataIncomplete were added in ff1ada1
and removed again in 8c1be9c, in a hunk unrelated to that PR's subject.
Four call sites still reference them, and LanguageContext.t() returns the
key path when the leaf is not renderable, so the dashboard rendered the
literal text common.heartbeatDataIncomplete in both locales -- exactly
when the backend is degraded and a readable message matters most.

Restore both keys in both locales with their original wording, recovered
from the deleting commit rather than rewritten.

Add homepage/src/__tests__/i18nKeyCompleteness.test.ts, which scans every
t('...') literal in the homepage source and asserts it resolves to
renderable text in every locale. Renderable means a string or a plural
form carrying a string 'other' branch, mirroring what t() can actually
return -- without that, the intentional dashboard.modulesListed plural
object reports as missing. The suite also asserts it found call sites at
all, so a scan that stops matching fails loudly instead of passing
vacuously, and that the locales stay structurally in step.

Lesson recorded in docs/11-risks-and-technical-debt per CLAUDE.md.

Refs schutera#238
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant