48660d3ec8
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).
3.3 KiB
3.3 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | ||||
|---|---|---|---|---|---|---|---|---|
| concept |
|
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/loginand/api/auth/me, and changed viaPUT /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/meresolves (App effect onuser.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
STRtable (ticket-encoding, site-metadata); can become asite_config.print_languagesetting 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, andindex.ts(init +setLanguage()). Type-safe key parity:Catalogis the shape ofsqwith string-typed values, so TypeScript forcesen.tsto 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.