Files
parking_solution/wiki/concepts/i18n.md
T
julian 48660d3ec8 docs(wiki): reconcile with session — booth console, i18n, live WS
File concept pages for the operator-UI architecture ([[booth-console]]: stack,
/api/ws live feed, anti-CSWSH) and [[i18n]] (per-user server-stored language;
resolves a dangling code-comment link). Qualify the stale 'plain React' note on
react-vite-spa. Backfill log entries for the live WebSocket, frontend foundation,
and i18n builds (which had none), plus a reconciliation lint entry. Catalog
booth-exit-flow + the two new pages in index; fix the concept count (27→41).
2026-06-18 11:50:58 +02:00

3.3 KiB

type, tags, sources, updated, status
type tags sources updated status
concept
parking
frontend
i18n
localization
2026-06-18 open

Internationalization (i18n)

The operator UI ships in two languages: Albanian (default) and English. Language is a per-user preference stored server-side and loaded on login — not a browser/localStorage setting, not a site-wide one. So an operator's choice follows their account and is restored on every login from any booth. (Decided + built 2026-06-18.)

Decisions

  • Albanian is the default and fallback. English is the second language. A missing English key falls back to Albanian.
  • Per-user, server-side preference. users.language ('sq' | 'en', default 'sq'; migration 0003). Returned from /api/auth/login and /api/auth/me, and changed via PUT /api/auth/language (self-service, any signed-in role). It is deliberately NOT in the JWT (identity/role only) — so changing language is a DB write + immediate /me, with no token refresh / re-login. See local-jwt-auth.
  • Library: react-i18next (i18next). Chosen over a hand-rolled t() for pluralization, interpolation, and headroom beyond two languages. The active language is applied after /me resolves (App effect on user.language); the header SQ/EN toggle switches instantly and persists.
  • Printed tickets/receipts stay Albanian. Customer-facing paper is independent of the operator's UI language — an operator reading the UI in English still prints Albanian tickets. The print strings live in the device driver's STR table (ticket-encoding, site-metadata); can become a site_config.print_language setting later if a site ever needs English receipts.

As-built (2026-06-18)

  • Backend: users.language + the three auth touch-points above (apps/server/src/routes/auth.ts).
  • Frontend: apps/web/src/lib/i18n/ — sq.ts (default/fallback), en.ts, and index.ts (init + setLanguage()). Type-safe key parity: Catalog is the shape of sq with string-typed values, so TypeScript forces en.ts to supply every key (and the build fails on a missing/typo'd key). Keys are dot-namespaced by area (common, nav, status, auth, booth, pay, shift, site, permits, tariff).
  • Translated screens: the booth (booth-console — screen, pay/exit modal, active sessions, snapshots, status), Login, ShiftControl, SiteSettings, PermitManager, TariffComposer.

Open / deferred

  • SetupWizard is NOT translated (deliberate). Its content is mostly server-provided — driver labels and config-field labels/help come from the backend device-catalog API (device-registry, first-run-setup). Translating only its static chrome would leave a half-English screen; it's deferred until backend catalog i18n is scoped, then chrome + catalog localize together.
  • Server API error strings are still English (surfaced raw in the UI). v1 relies on the client mapping known errors; a fuller approach would translate by error code, not message.
  • Behaviour note (not a bug): a hard navigation (new URL) re-bootstraps the language from the user's stored preference via /me — so an un-persisted toggle resets. Correct: the stored pref wins. The toggle persists via the PUT, so it survives once saved.