feat(api): zod schema error messages → i18n catalog keys #212

Open
opened 2026-06-21 16:36:50 +00:00 by james · 0 comments
Owner

Context

ADR-0025 §6 explicitly defers translating zod schema validator messages. Today the lib/dto/auth.ts / lib/dto/user.ts zod refinements still carry English strings inline ("A valid email is required.", "Password must be at least N characters."). The API-code → catalog-key indirection (LOGIN_ERROR_KEYS, REGISTER_ERROR_KEYS) routes top-level error codes through the catalog, but per-field zod issues still surface English regardless of Accept-Language.

Source

  • ADR-0025 §6 ("Zod schema messages: deferred"): "Follow-up ticket will introduce a small zodErrorMap-based helper that maps the short, finite set of ZodIssueCodes to catalog keys."
  • apps/api/lib/dto/auth.ts lines 53-56 names this as the deferred follow-up.
  • CLAUDE.md "No hardcoded user-facing strings" bullet calls it out: "(zod schema messages are deferred — see ADR-0025 §6.)"

Scope

  • Introduce a zodErrorMap-based helper in apps/api/lib/api/ that maps ZodIssueCode → catalog key.
  • Apply it to the auth DTOs (zRegisterRequest, zLoginRequest) so per-field validation errors in the errors[] array of Problem Details responses carry catalog keys instead of English strings.
  • Wire the universal client (apps/client/) and @carol/api-client to render those keys via react-i18next — the existing top-level code path (errors.${LOGIN_ERROR_KEYS[code]}) is the shape to mirror.
  • Extend to zNoteCreate, zNoteUpdate, zSkillCreate, etc. as a sweep across all DTOs that carry user-facing inline error strings.

Acceptance criteria

  • apps/api/lib/api/zodErrorMap.ts exposes a helper that maps ZodIssueCode (and per-issue context like params.minimum) to a { key, args } shape.
  • At least the auth DTOs use the helper; their refinements no longer carry English strings.
  • messages/en.json gains the catalog keys (e.g. errors.zod.invalidEmail, errors.zod.tooShort).
  • Tests cover that POSTing an invalid registration body returns an errors[] array whose entries carry the new keys, not English strings.
  • CLAUDE.md note in the i18n convention is updated to remove the "deferred" caveat.

Out of scope

  • Migrating every existing API code → key mapping in LOGIN_ERROR_KEYS / REGISTER_ERROR_KEYS (those already work; this ticket addresses the per-field gap only).

Composes with

  • ADR-0025 §6
  • Existing i18n setup in packages/i18n/
## Context ADR-0025 §6 explicitly defers translating zod schema validator messages. Today the `lib/dto/auth.ts` / `lib/dto/user.ts` zod refinements still carry English strings inline (`"A valid email is required."`, `"Password must be at least N characters."`). The API-code → catalog-key indirection (`LOGIN_ERROR_KEYS`, `REGISTER_ERROR_KEYS`) routes top-level error codes through the catalog, but per-field zod issues still surface English regardless of `Accept-Language`. ## Source - ADR-0025 §6 ("Zod schema messages: deferred"): *"Follow-up ticket will introduce a small `zodErrorMap`-based helper that maps the short, finite set of `ZodIssueCode`s to catalog keys."* - `apps/api/lib/dto/auth.ts` lines 53-56 names this as the deferred follow-up. - CLAUDE.md "No hardcoded user-facing strings" bullet calls it out: *"(zod schema messages are deferred — see ADR-0025 §6.)"* ## Scope - Introduce a `zodErrorMap`-based helper in `apps/api/lib/api/` that maps `ZodIssueCode` → catalog key. - Apply it to the auth DTOs (`zRegisterRequest`, `zLoginRequest`) so per-field validation errors in the `errors[]` array of Problem Details responses carry catalog keys instead of English strings. - Wire the universal client (`apps/client/`) and `@carol/api-client` to render those keys via `react-i18next` — the existing top-level code path (`errors.${LOGIN_ERROR_KEYS[code]}`) is the shape to mirror. - Extend to `zNoteCreate`, `zNoteUpdate`, `zSkillCreate`, etc. as a sweep across all DTOs that carry user-facing inline error strings. ## Acceptance criteria - [ ] `apps/api/lib/api/zodErrorMap.ts` exposes a helper that maps `ZodIssueCode` (and per-issue context like `params.minimum`) to a `{ key, args }` shape. - [ ] At least the auth DTOs use the helper; their refinements no longer carry English strings. - [ ] `messages/en.json` gains the catalog keys (e.g. `errors.zod.invalidEmail`, `errors.zod.tooShort`). - [ ] Tests cover that POSTing an invalid registration body returns an `errors[]` array whose entries carry the new keys, not English strings. - [ ] CLAUDE.md note in the i18n convention is updated to remove the "deferred" caveat. ## Out of scope - Migrating every existing API code → key mapping in `LOGIN_ERROR_KEYS` / `REGISTER_ERROR_KEYS` (those already work; this ticket addresses the per-field gap only). ## Composes with - ADR-0025 §6 - Existing i18n setup in `packages/i18n/`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
james/carol#212
No description provided.