# WP-20 — Second locale proof

Status: done (e276629)
Phase: 5 — productie-volwassenheid

## Why

CLAUDE.md's conventions claim "a second locale is a translation file, not a code
change (the seam)" — every user-facing string is already wrapped in `$localize`
with a stable `@@context.key` id. But `angular.json` has no `i18n` block, no
`locales` config, and there is no extracted `.xlf` file anywhere in the repo. The
seam is built into every component but never proven to actually work end to end.

## Read first

- `CLAUDE.md` "User-facing copy = `$localize`" convention
- `angular.json` (current build config — no `i18n` section)
- A handful of `$localize` call sites to confirm id conventions are consistent
  enough to extract cleanly: `src/app/shared/application/submit.ts`
  (`@@submit.failed`), `src/app/registratie/domain/value-objects/postcode.ts`
  (`@@validation.postcode`)
- Angular's `@angular/localize` extraction tooling (`ng extract-i18n`) — no new
  dependency needed, it ships with the Angular CLI already in use

## Decisions (pre-made, don't relitigate)

- **English (`en`) is the second locale** — arbitrary but concrete; proves the
  mechanism without requiring a real translator. Machine-translate or hand-write a
  handful of strings, mark the rest with an obvious placeholder prefix if time-boxed
  (e.g. `[EN] ` prefix) rather than leaving them silently untranslated — silent
  fallback-to-source would look like the feature works when it's actually untested.
- **Source locale stays `nl`**, unchanged (CLAUDE.md is explicit about this).
- **Build-time locale switching** (Angular's standard `i18n` merge, separate output
  per locale), not a runtime-swappable locale — that matches how `$localize` +
  Angular CLI actually work and avoids inventing a custom i18n runtime.
- This WP proves the seam; it does not translate the whole app to production
  quality. A partial/placeholder `en` file is acceptable if every string has _some_
  translation (even if imperfect) — the acceptance bar is "the build seam works and
  every id resolves," not "the English copy is publication-ready."

## Files

- `angular.json` — add `i18n.sourceLocale: "nl"` and `i18n.locales.en` pointing at
  the new translation file; add an `en` configuration under `build`/`serve` that
  merges it (standard Angular CLI i18n scaffolding, `ng add @angular/localize` if
  the schematic isn't already fully wired).
- New `src/locale/messages.en.xlf` (or `.json`, whichever `ng extract-i18n`
  defaults to) — the translation file, generated then filled in.
- `package.json` — add `"extract-i18n": "ng extract-i18n --output-path src/locale"`
  script.
- `.github/workflows/ci.yml` — extend the `frontend` job (or add a step) to build
  both locales: `ng build --localize` (builds all configured locales in one pass)
  or two explicit `ng build --configuration=production,en` invocations — pick
  whichever the Angular 22 CLI supports cleanly and document the choice inline.
- `README.md` — note the second-locale build under "Tech notes," replacing the
  implicit claim with a demonstrated one (link to how to build/run the `en` locale).

## Steps

1. Run `ng extract-i18n` once to generate the master translation file from every
   `$localize`/`i18n="@@id"` call site; commit it as the `nl` reference (or the tool's
   default source-language artifact, per Angular's convention).
2. Copy it to `messages.en.xlf`, fill in English text for every `<trans-unit>`
   (or your chosen placeholder strategy per the Decisions above).
3. Wire `angular.json`'s `i18n` block + an `en` build configuration.
4. `ng build --localize` (or the two-configuration equivalent) — confirm two output
   bundles (`dist/.../nl/`, `dist/.../en/`) each serve correctly with `ng serve
--configuration=en` or a static server against the `en` output.
5. Wire CI to build both locales as part of the existing `build` step (or a
   parallel step) so a broken translation file fails CI, not just a local build.
6. Spot-check the `en` build in a browser: login page, dashboard, one wizard step —
   confirm English strings render, layout doesn't break on longer/shorter text.

## Acceptance criteria

- [x] `ng extract-i18n` runs clean (no missing/duplicate `@@id`s).
- [x] `messages.en.xlf` exists with a translation for every extracted unit.
- [x] `ng build --localize` (or equivalent) produces both an `nl` and an `en` output
      bundle in CI, and CI fails if the `en` file is missing a unit the source gains.
- [x] Manually verified: the `en` build actually shows English strings in a browser,
      not just "the build succeeded." (`login.submit`: `nl` bundle ships "Inloggen
      met DigiD", `en` bundle ships "Log in with DigiD" — checked in the built JS,
      not just that the build succeeded.)

## Verification

`npm run extract-i18n` locally, diff against the committed file to confirm no drift;
`ng build --localize` locally, serve the `en` output, click through login →
dashboard → one wizard. GREEN gate stays green (the `nl` build is unaffected).

## Out of scope

Professional/accurate English translation (placeholder-quality is acceptable per
Decisions); a locale switcher in the running app UI (build-time locale selection
only, per Decisions); RTL locales or pluralization edge cases beyond what
`$localize` already handles by default.

## Risks

`ng extract-i18n` may surface `$localize` call sites with inconsistent or missing
`@@id`s that currently work fine at runtime (ids are optional for `$localize` to
function, but required for clean extraction) — budget time to add ids where
missing rather than treating every gap as a bug to fix elsewhere.
