Frontend i18n: enforce en/sv key parity with a test #60

Closed
opened 2026-06-06 18:53:08 +00:00 by logaritmisk · 1 comment
Owner

Severity: Low. From a frontend UX audit (positive finding + a gap).

Context

i18n discipline is good: no hardcoded user-facing English in JSX (all strings go through t()), and en.json/sv.json are currently at exact parity. But there's no automated parity checki18n.test.tsx has no parity assertion, keys are untyped strings, and nothing fails CI if sv drifts from en. The moment someone adds an en key without sv, the Swedish UI silently falls back to English for that key.

Suggested fix

Add a test asserting the flattened key sets of en.json and sv.json are identical (and ideally that no value is empty). Optionally generate a typed key union so a missing translation is a type error.

Source: frontend UX/design audit, 2026-06-06.

**Severity: Low.** _From a frontend UX audit (positive finding + a gap)._ ## Context i18n discipline is good: no hardcoded user-facing English in JSX (all strings go through `t()`), and `en.json`/`sv.json` are currently at exact parity. **But there's no automated parity check** — `i18n.test.tsx` has no parity assertion, keys are untyped strings, and nothing fails CI if `sv` drifts from `en`. The moment someone adds an `en` key without `sv`, the Swedish UI silently falls back to English for that key. ## Suggested fix Add a test asserting the flattened key sets of `en.json` and `sv.json` are identical (and ideally that no value is empty). Optionally generate a typed key union so a missing translation is a type error. _Source: frontend UX/design audit, 2026-06-06._
Author
Owner

Done — merged to main (4c24f03).

Added web/src/i18n/parity.test.ts (pure, no React) that runs in the normal Vitest suite, so en/sv drift fails locally and in CI:

  • Key-set parity — flattens both en.json and sv.json to dotted leaf keys (e.g. objects.columns.number) and asserts the sets are identical. On failure it lists exactly which keys are missing in sv and missing in en, so a drift is immediately actionable.
  • No empty values — every leaf in both locales must be a non-empty (trimmed) string (also catches a path that's an object in one file but a string in the other).

Verified it's a true positive: injecting an en-only key + an empty value made both tests fail with the offending key names; the in-parity files pass (223 tests green; typecheck/lint clean; no codename).

Scope note: did not add a typed key union — the app uses template-literal keys (t(\fields.types.${type}`), t(`authorities.${k}`), t(`visibility.${v}`)`, …) that strict key-typing would reject, so the runtime parity test is the friction-free guard the issue asked for. A typed-key approach (with template-key escape hatch) remains an optional follow-up.

Done — merged to `main` (`4c24f03`). Added **`web/src/i18n/parity.test.ts`** (pure, no React) that runs in the normal Vitest suite, so en/sv drift fails locally and in CI: - **Key-set parity** — flattens both `en.json` and `sv.json` to dotted leaf keys (e.g. `objects.columns.number`) and asserts the sets are identical. On failure it lists exactly which keys are *missing in sv* and *missing in en*, so a drift is immediately actionable. - **No empty values** — every leaf in both locales must be a non-empty (trimmed) string (also catches a path that's an object in one file but a string in the other). Verified it's a true positive: injecting an en-only key + an empty value made both tests fail with the offending key names; the in-parity files pass (223 tests green; typecheck/lint clean; no codename). Scope note: did **not** add a typed key union — the app uses template-literal keys (`t(\`fields.types.${type}\`)`, `t(\`authorities.${k}\`)`, `t(\`visibility.${v}\`)`, …) that strict key-typing would reject, so the runtime parity test is the friction-free guard the issue asked for. A typed-key approach (with template-key escape hatch) remains an optional follow-up.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#60