Full i18n stack for en/pt/es/fr/de, library-free, with catalog validation and two expansion passes

manueljpconde built a complete localization system for five languages without pulling in an i18n library. The implementation spans schema, backend allowlist, frontend runtime, catalog validation tooling, and two successive catalog expansion passes covering auth through main app surfaces.

i18n

The infrastructure PR adds a locale column on user_profiles with a named CHECK constraint (user_profiles_locale_check), a backend allowlist in locales.ts, and a matching frontend SupportedLocale type. PATCH /user/profile validates locale writes against the allowlist. All three layers must stay in sync - the commit notes this explicitly as a hazard to manage on any future locale additions.

Server-side resolution in the root layout walks mike_locale cookie → Accept-Languageen. Client-side switching goes through I18nContext without a full reload. ProfileLocaleSync is a render-only component that one-way pushes the stored profile locale to the cookie after authentication; the signup form was updated to persist the active locale so a pre-signup language switch isn't lost when the profile first loads.

A follow-up commit addressed four reviewer findings. The npm test scripts were passing quoted globs that tsx received literally on bash 3.2 - both backend and frontend now use a Node-based file discovery script (scripts/run-tests.mjs). findDuplicateTopLevelKeys.ts (95 lines, 95 tests) was added to the catalog validator after the review noted that JSON.parse silently deduplicates keys, which could let translation errors go undetected. The signup-flow locale persistence and document.documentElement.lang drift on client switches were also fixed.

A second PR expanded the catalog from the initial auth/sidebar coverage to the main app surfaces: account page, chat view, tabular reviews, assistant components, project modals, workflow list. That added roughly 104 keys per locale - 520 new translations in total. The translations were generated via Codex; the branch name makes that clear and native speaker review is recommended before customer-facing deployment.

Test counts after both patches: frontend 55/55, backend 34/34.

So what Worth pulling if you want multi-locale support targeting EU legal markets. The implementation is small enough to audit and doesn't carry a third-party i18n dependency. Watch for two things: the named CHECK constraint ties locale expansion to a migration, and the non-English translations in the second expansion PR are machine-generated and should be reviewed before going in front of paying users.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

5 commits from manueljpconde/mikeEU, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
346b8197 feat: add localization support for en/pt/es/fr/de (#4) Manuel Conde 2026-05-10 ↗ GitHub
commit body
Adds i18n infrastructure (thin custom + native Intl, no library) covering
auth pages, sidebar nav, language switcher, and common labels across the
five supported locales. English is canonical fallback at every layer.

- Schema: user_profiles.locale text column with named CHECK constraint
- Backend: PATCH /user/profile accepts/persists locale with allowlist validation
- Frontend: server-side locale resolution (cookie -> Accept-Language -> en)
  in root layout; client I18nProvider for runtime switching; ProfileLocaleSync
  applies authenticated profile.locale one-way
- Cookie 'mike_locale' (non-httpOnly, Path=/, Max-Age=1y, SameSite=Lax)
- Catalog validator with structural + placeholder + plural-shape checks
- 51 automated tests (46 frontend + 5 backend) on pure logic; React surface
  covered manually
- Coupled allowlist triple flagged: schema CHECK / backend SUPPORTED_LOCALES
  / frontend SupportedLocale must move together

Spec:  https://github.com/manueljpconde/mikeEU/issues/4#issuecomment-4414976968
Plan:  https://github.com/manueljpconde/mikeEU/issues/4#issuecomment-4414987693
GO:    https://github.com/manueljpconde/mikeEU/issues/4#issuecomment-4415157201

Closes #4
cb6b9a2f fix(i18n): four review findings on PR #10 Manuel Conde 2026-05-10 ↗ GitHub
commit body
1. npm test scripts: replace quoted-glob args (which tsx received literally
   and didn't expand on bash 3.2) with a Node-based test discovery script
   per repo. Both `npm test --prefix frontend` and `npm test --prefix
   backend` now run the suites correctly.

2. Validator now rejects duplicate top-level JSON keys via a state-machine
   raw-text scan (findDuplicateTopLevelKeys), restoring the contract
   promised in spec rule 4. JSON.parse silently dedupes; this catches
   editor mistakes before runtime sees the deduped catalog.

3. Signup persists the active i18n locale alongside name/organisation
   so a guest who selected pt/es/fr/de doesn't get snapped back to en
   when ProfileLocaleSync applies the (default 'en') profile after
   signup.

4. Client setLocale now syncs document.documentElement.lang via useEffect
   on locale change. SSR sets it once; the effect keeps it in sync after
   user-driven switches.

Tests: frontend 55/55 (added 9 in findDuplicateTopLevelKeys.test.ts),
backend 34/34. Both `npm test` and `npm run lint:catalogs` runnable
without manual glob expansion.
46c379ca Merge pull request #10 from manueljpconde/feature/4-implement-localization-for-supported-app-loca Manuel Conde 2026-05-10 ↗ GitHub
feat: add localization support for en/pt/es/fr/de
93b5b2bd Expand i18n coverage across main surfaces Manuel Conde 2026-05-10 ↗ GitHub
b6a1762d Merge pull request #14 from manueljpconde/codex/i18n-main-surfaces Manuel Conde 2026-05-10 ↗ GitHub
[codex] Expand i18n coverage across main surfaces

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-219.md from inside the repo you want the changes in.

⬇ Download capture-thread-219.md