AZ-001 — Localized shared preferences
Status
Done
Context
Read the epic contract, especially the inspected baseline and state/i18n decisions. The
current English Home/NotFound/layout and async states use literal strings. TanStack Query owns
server state; Zustand is installed but unused. AppProviders creates a query client, while
frontend/src/test/test-utils.tsx independently wraps only the query provider. There is no actual
shared preference or language-selection workflow yet.
Goal
Deliver language selection with persistent device preferences and isolated application providers, establishing state ownership before the MUI shell uses it.
Requirements
- Adopt
@reduxjs/toolkitandreact-reduxfor a small preferences slice. Remove unused Zustand dependency, update lockfile and techstack rationale. Retain TanStack Query and existing endpoint modules. Do not add RTK Query or mirror query state into Redux. - Preferences contain supported language (
en,sv,hu) and appearance (light,dark,system), defaulting to English/system. Appearance becomes visually effective in AZ-002; do not add a misleading inactive appearance selector now. Drawer state, form drafts and message requests remain outside Redux. Current user will belong to TanStack Query in AZ-006. - Use i18next/react-i18next with bundled frontend translation resources and stable semantic keys. Translate currently displayed application-owned copy, labels, fallback/async states and document title; preserve the database message as returned. Centralized Foodtruck Order identity itself remains a brand value, not a translated product name. AZ-002 centralizes that value.
- English is default and fallback; allow switching to Swedish or Hungarian through an accessible
compact language control on Home. On first load choose valid saved device preference, otherwise a
supported browser language, otherwise English. Normalize
sv-SE/en-GB/hu-HUto supported base languages; unsupported/corrupt values must not crash startup. - Persist only validated preferences in one namespaced localStorage value with a small schema version. Handle unavailable storage gracefully. Never persist credentials, user data, query cache, or server errors. No new persistence middleware dependency.
- Keep Redux as the application preference source; apply language changes to the i18n instance
through one synchronization point without dispatch/render loops. Set document
lang, and use Intl for future formatting with the selected locale. No remote translation CDN/service. - Missing keys fall back to English; unknown keys have a visible diagnostic in development, without flooding logs or crashing production. Include a parity test that fails for missing, extra, empty, or mismatched interpolation keys across the shipped resources. Do not silence missing keys globally. AZ-013 later adds a standalone resource command.
- Expose factories/test injection for isolated store, query client and i18n instances. Reuse the
real provider hierarchy in
renderApp; avoid module-singleton state shared between test cases or new provider instances created on ordinary rerenders. Existing test helpers may supply controlled language/preference state explicitly.
Non-goals
No user persistence/backend settings, account/login, MUI installation, navigation, cart/global domain state, translation of database content, generic store framework, or cross-tab sync engine.
Implementation guidance
Extend frontend/src/app/providers.tsx, query-client.ts, and src/test/ patterns; place small
preference/i18n modules under the existing app/ responsibility. Keep storage adaptation separate
from pure preference resolution so error handling is testable. Do not import backend/db modules. Use
RTL for actual selection/rerender behavior, and frontend integration tests to prove the real
API/query/page path still displays the database response. Test observable outcomes, not generated
Redux action shapes. Keep compatible dependency resolutions in the lockfile.
Acceptance criteria
- Changing English/Swedish/Hungarian updates Home, not-found and async-state copy and document language.
- Reload honors a valid saved choice; unsupported browser language, invalid saved data and denied storage fall back predictably. Provider rerender preserves selected language/cache.
- Separate app/test instances cannot leak store, i18n or query state into one another.
- Locale resource parity/interpolation checks fail on deliberate fixture defects.
- API boundary and database message behavior are unchanged; no server state lives in Redux.
- The selector is labeled, keyboard usable, and fits narrow content without horizontal overflow.
Verification
From root:
pnpm quality
pnpm --filter @foodtruck-order/frontend test
pnpm --filter @foodtruck-order/frontend test:coverage
pnpm --filter @foodtruck-order/frontend build
Add preference-resolution/storage unit cases and RTL language/fallback/provider integration cases. Manually check at 360×800 and 320px width, keyboard-only selection, refresh, browser language and storage denial; use the existing host startup from development §7. Existing complete-system CI still runs. No Docker reset is needed locally solely for translation/preference source changes.
Documentation
Update docs/techstack.md (Redux replaces unused Zustand; Query retained; i18n choice),
docs/architecture.md (state boundaries), docs/development.md (keys/resources/preferences), and
docs/test-strategy.md (provider isolation/parity tests). Update current-state/handover and task
status using the DoD, with exact evidence and remaining hosted checks.
Dependencies
Implemented vertical-slice frontend, existing query/API boundary, RTL/Vitest/static gates. No AZ prerequisite.
Future considerations
AZ-007 adds per-account preferences without persisting user identity in localStorage. A fourth language requires resource registration and translations, not new store architecture.