Files
atomic-design-poc/src/app/registratie/domain/change-request.machine.ts
Edwin van den Houdt 474c040410 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>
2026-06-27 14:00:10 +02:00

83 lines
2.9 KiB
TypeScript

import { Result, assertNever } from '@shared/kernel/fp';
import { Postcode, parsePostcode } from '@registratie/domain/value-objects/postcode';
/** What the user is typing (raw, possibly invalid). */
export interface Draft {
straat: string;
postcode: string;
woonplaats: string;
}
/** After parsing — postcode is the branded type, so downstream can't get a raw one. */
export interface Valid {
straat: string;
postcode: Postcode;
woonplaats: string;
}
export type Errors = Partial<Record<keyof Draft, string>>;
/**
* The change-request (adreswijziging) form as one tagged union — the SAME idiom
* as the wizards, just single-step. `draft`/`errors` exist only while Editing;
* Submitting/Submitted/Failed carry the parsed `Valid`. Illegal states (submitting
* an invalid draft, a success screen with errors) are unrepresentable.
*/
export type State =
| { tag: 'Editing'; draft: Draft; errors: Errors }
| { tag: 'Submitting'; data: Valid }
| { tag: 'Submitted'; data: Valid; referentie: string }
| { tag: 'Failed'; data: Valid; error: string };
export const initial: State = {
tag: 'Editing',
draft: { straat: '', postcode: '', woonplaats: '' },
errors: {},
};
/** Parse via the value objects; on success hand back a Valid, else per-field errors. */
function validate(draft: Draft): Result<Errors, Valid> {
const straat = draft.straat.trim();
const postcode = parsePostcode(draft.postcode);
const errors: Errors = {};
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() } };
}
return { ok: false, error: errors };
}
export type Msg =
| { tag: 'SetField'; key: keyof Draft; value: string }
| { tag: 'Submit' }
| { tag: 'Retry' }
| { tag: 'SubmitConfirmed'; referentie: string }
| { tag: 'SubmitFailed'; error: string }
| { tag: 'Reset' }
| { tag: 'Seed'; state: State }; // mount a specific state (stories/tests)
export function reduce(s: State, m: Msg): State {
switch (m.tag) {
case 'SetField':
return s.tag === 'Editing' ? { ...s, draft: { ...s.draft, [m.key]: m.value } } : s;
case 'Submit': {
if (s.tag !== 'Editing') return s;
const r = validate(s.draft);
return r.ok ? { tag: 'Submitting', data: r.value } : { ...s, errors: r.error };
}
case 'Retry':
return s.tag === 'Failed' ? { tag: 'Submitting', data: s.data } : s;
case 'SubmitConfirmed':
return s.tag === 'Submitting' ? { tag: 'Submitted', data: s.data, referentie: m.referentie } : s;
case 'SubmitFailed':
return s.tag === 'Submitting' ? { tag: 'Failed', data: s.data, error: m.error } : s;
case 'Reset':
return initial;
case 'Seed':
return m.state;
default:
return assertNever(m);
}
}