Step 2 (i18n): $localize sweep + JA_NEE dedup (M3, M4)

Wrap every user-facing Dutch string in Angular's first-party i18n — `i18n`/
`i18n-<attr>` in templates, `$localize` in TS (value-objects, machines, commands,
label constants, shared-component defaults). Source locale stays nl; a second
locale is now a translation file, not a code change.

- M3: ~145 strings localized with stable @@ ids across registratie,
  herregistratie, auth, shared/ui, shared/layout. Skipped: showcase, debug-state,
  scenario interceptor, generated client, specs/stories, raw status enum tags,
  internal parse* diagnostics.
- M4: single shared JA_NEE (localized labels) in radio-group; both wizard copies
  removed.

Gate green: lint, check:tokens, build, test 77/77.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-06-27 14:00:10 +02:00
co-authored by Claude Opus 4.8
parent 1c65025fef
commit 474c040410
36 changed files with 193 additions and 181 deletions
@@ -40,7 +40,7 @@ function validate(draft: Draft): Result<Errors, Valid> {
const straat = draft.straat.trim();
const postcode = parsePostcode(draft.postcode);
const errors: Errors = {};
if (!straat) errors.straat = 'Vul straat en huisnummer in.';
if (!straat) errors.straat = $localize`:@@validation.straat:Vul straat en huisnummer in.`;
if (!postcode.ok) errors.postcode = postcode.error;
if (straat && postcode.ok) {
return { ok: true, value: { straat, postcode: postcode.value, woonplaats: draft.woonplaats.trim() } };
@@ -85,11 +85,11 @@ function validateStep(step: StepId, d: Draft): Result<Errors, void> {
const errors: Errors = {};
switch (step) {
case 'adres': {
if (!d.straat || d.straat.trim() === '') errors.straat = 'Vul een straat en huisnummer in.';
if (!d.straat || d.straat.trim() === '') errors.straat = $localize`:@@validation.straat2:Vul een straat en huisnummer in.`;
const pc = parsePostcode(d.postcode ?? '');
if (!pc.ok) errors.postcode = pc.error;
if (!d.woonplaats || d.woonplaats.trim() === '') errors.woonplaats = 'Vul een woonplaats in.';
if (!d.correspondentie) errors.correspondentie = 'Maak een keuze.';
if (!d.woonplaats || d.woonplaats.trim() === '') errors.woonplaats = $localize`:@@validation.woonplaats:Vul een woonplaats in.`;
if (!d.correspondentie) errors.correspondentie = $localize`:@@validation.maakKeuze:Maak een keuze.`;
// E-mail is only required when 'email' is the chosen channel.
if (d.correspondentie === 'email') {
const e = parseEmail(d.email ?? '');
@@ -100,7 +100,7 @@ function validateStep(step: StepId, d: Draft): Result<Errors, void> {
case 'beroep': {
// A diploma must be chosen (or declared manually); its beroep is then known.
if (!d.diplomaId || !d.beroep) {
errors.diploma = 'Kies het diploma waarmee u zich wilt registreren, of voer het handmatig in.';
errors.diploma = $localize`:@@validation.diploma:Kies het diploma waarmee u zich wilt registreren, of voer het handmatig in.`;
break;
}
// Every policy question the chosen diploma raised must be answered. Which
@@ -108,7 +108,7 @@ function validateStep(step: StepId, d: Draft): Result<Errors, void> {
// they're answered.
const open: Record<string, string> = {};
for (const id of d.vraagIds ?? []) {
if (!(d.antwoorden[id] ?? '').trim()) open[id] = 'Beantwoord deze vraag.';
if (!(d.antwoorden[id] ?? '').trim()) open[id] = $localize`:@@validation.beantwoordVraag:Beantwoord deze vraag.`;
}
if (Object.keys(open).length > 0) errors.antwoorden = open;
break;
+8 -8
View File
@@ -28,30 +28,30 @@ export function tasksFromProfile(reg: Registration, eligibleForHerregistratie: b
if (eligibleForHerregistratie) {
const deadline = herregistratieDeadline(reg);
tasks.push({
title: 'Vraag uw herregistratie aan',
title: $localize`:@@task.herregistratie.title:Vraag uw herregistratie aan`,
description: deadline
? `Verleng uw registratie vóór ${formatNL(deadline)}.`
: 'U kunt nu uw herregistratie aanvragen.',
? $localize`:@@task.herregistratie.deadline:Verleng uw registratie vóór ${formatNL(deadline)}:deadline:.`
: $localize`:@@task.herregistratie.nodeadline:U kunt nu uw herregistratie aanvragen.`,
to: '/herregistratie',
actionLabel: 'Herregistratie aanvragen',
actionLabel: $localize`:@@task.herregistratie.action:Herregistratie aanvragen`,
});
}
if (reg.status.tag === 'Geschorst') {
tasks.push({
title: 'Uw registratie is geschorst',
title: $localize`:@@task.geschorst.title:Uw registratie is geschorst`,
description: reg.status.reden,
to: '/registratie',
actionLabel: 'Bekijk uw gegevens',
actionLabel: $localize`:@@task.bekijkGegevens.action:Bekijk uw gegevens`,
});
}
if (reg.status.tag === 'Doorgehaald') {
tasks.push({
title: 'Uw registratie is doorgehaald',
title: $localize`:@@task.doorgehaald.title:Uw registratie is doorgehaald`,
description: reg.status.reden,
to: '/registratie',
actionLabel: 'Bekijk uw gegevens',
actionLabel: $localize`:@@task.bekijkGegevens.action:Bekijk uw gegevens`,
});
}
@@ -5,5 +5,5 @@ export type BigNummer = Brand<string, 'BigNummer'>;
export function parseBigNummer(raw: string): Result<string, BigNummer> {
const t = raw.trim();
return /^\d{11}$/.test(t) ? ok(t as BigNummer) : err('Een BIG-nummer bestaat uit 11 cijfers.');
return /^\d{11}$/.test(t) ? ok(t as BigNummer) : err($localize`:@@validation.bigNummer:Een BIG-nummer bestaat uit 11 cijfers.`);
}
@@ -13,7 +13,7 @@ export function parseEmail(raw: string): Result<string, Email> {
// Deliberately lax: a single @ with non-empty, dot-bearing parts. Good enough
// for instant feedback; the server re-validates.
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)) {
return err('Voer een geldig e-mailadres in, bijv. naam@voorbeeld.nl.');
return err($localize`:@@validation.email:Voer een geldig e-mailadres in, bijv. naam@voorbeeld.nl.`);
}
return ok(t as Email);
}
@@ -10,7 +10,7 @@ export type Postcode = Brand<string, 'Postcode'>;
export function parsePostcode(raw: string): Result<string, Postcode> {
const t = raw.trim().toUpperCase();
if (!/^[1-9]\d{3}\s?[A-Z]{2}$/.test(t)) {
return err('Voer een geldige postcode in, bijv. 1234 AB.');
return err($localize`:@@validation.postcode:Voer een geldige postcode in, bijv. 1234 AB.`);
}
// Normalise to "1234 AB" — the parser also cleans up.
return ok(t.replace(/^(\d{4})\s?([A-Z]{2})$/, '$1 $2') as Postcode);
@@ -8,7 +8,7 @@ export function parseUren(raw: string): Result<string, Uren> {
const n = Number(t);
// Number('') is 0 — guard the empty string explicitly.
if (t === '' || !Number.isInteger(n) || n < 0) {
return err('Vul een geheel aantal in (0 of meer).');
return err($localize`:@@validation.uren:Vul een geheel aantal in (0 of meer).`);
}
return ok(n as Uren);
}