# Phase 34: i18n Foundation - Context **Gathered:** 2026-04-13 **Status:** Ready for planning ## Phase Boundary Add a translation framework to GearBox with string extraction, locale-aware formatting, and ship English + German. UI chrome and system content are translated. Catalog data and user-generated content remain untranslated. Language selection is independent from market/currency (Phase 33) but both auto-detected from browser. ## Implementation Decisions ### Translation Scope & Boundaries - **D-01:** Translate UI chrome: buttons, labels, headings, navigation items, empty states, error messages, toast notifications, modal titles/descriptions, placeholder text - **D-02:** Translate system content: default category names (e.g., "Uncategorized"), onboarding flow text, MCP tool descriptions, email templates if any - **D-03:** Do NOT translate: catalog item names/descriptions, user-generated content (item names, notes, setup names, thread titles), category names created by users - **D-04:** Locale-aware formatting integrates with the existing `useFormatters()` hook — number formatting, date formatting, and pluralization handled by the i18n framework, weight/price formatting continues through existing formatters ### Library & Architecture - **D-05:** Claude's discretion on library choice — pick between react-i18next and Lingui based on best fit with React 19, Vite, Bun, Hono stack. Key criteria: hook-based API, lazy loading per locale, compile-time or runtime extraction, TypeScript support - **D-06:** Translation files stored as JSON in the repo: `src/client/locales/en.json`, `src/client/locales/de.json`. Checked into git. Switching to an external translation service (Crowdin/Lokalise) later is a CI/sync change, not a code change - **D-07:** Translations loaded client-side — the React app loads the appropriate locale JSON. Server-side strings (API error messages, MCP descriptions) use a simple server-side translation utility - **D-08:** Namespace support for organizing strings by feature area (e.g., `common`, `collection`, `threads`, `setups`, `onboarding`, `settings`) to keep files manageable as string count grows ### Language Selection UX - **D-09:** Language and market/currency are independent settings. A German expat in the UK can have GBP prices but German UI - **D-10:** Language auto-detected from browser locale on first visit (navigator.language). User can override in settings - **D-11:** Language picker in settings page — alongside but separate from the market/currency picker from Phase 33 - **D-12:** If browser locale has no matching translation (e.g., `ja`), fall back to English ### First Additional Language - **D-13:** German (de) ships alongside English (en) as the first additional language. Primary target market is EU/DE - **D-14:** German translations AI-generated by Claude during implementation. No formal review step — user catches and fixes issues organically during app usage - **D-15:** Translation quality approach for future languages: same AI-generated strategy. Professional/community translation deferred until there's a real user base requesting specific languages ### Claude's Discretion - Library choice between react-i18next and Lingui (evaluate DX, bundle size, extraction tooling, React 19 compatibility) - String key naming convention (flat vs. nested, dot notation style) - How to handle dynamic content interpolation patterns - Whether to extract strings from existing components in one pass or incrementally ## Canonical References **Downstream agents MUST read these before planning or implementing.** No external specs — requirements fully captured in decisions above. ### Existing Implementation (to integrate with) - `src/client/hooks/useFormatters.ts` — Central formatting hook for weight + price. i18n number/date formatting should integrate here - `src/client/lib/formatters.ts` — `formatWeight()` and `formatPrice()` functions. Locale-aware formatting may need to wrap or replace these - `src/client/hooks/useCurrency.ts` — Currency/market hook. Language selection is separate but both auto-detect from browser - `src/client/routes/settings.tsx` — Settings page where language picker will be added - `src/client/routes/__root.tsx` — Root layout where i18n provider wraps the app - `src/client/main.tsx` — App entry point for i18n initialization - `src/server/mcp/` — MCP tool descriptions need server-side translation - `src/client/components/onboarding/` — Onboarding flow has significant translatable text ### Phase 33 Integration - `src/client/hooks/useCurrency.ts` — Market auto-detection logic from Phase 33. Language auto-detection should follow the same pattern but remain independent ## Existing Code Insights ### Reusable Assets - `useFormatters()` hook: Already composites weight + price formatting. Extend to include locale-aware number/date formatting - `useSetting()` hook: Settings storage pattern — language preference fits here - Settings page: Existing pill-toggle pattern (used for weight units, currency) — reuse for language picker ### Established Patterns - Hooks for user preferences (`useWeightUnit`, `useCurrency`) — `useLanguage` follows the same pattern - Settings stored in DB via settings table, read via `useSetting()` hook - Component structure: presentational components in `components/`, route components in `routes/` ### Integration Points - `src/client/main.tsx`: Initialize i18n provider - `src/client/routes/__root.tsx`: Wrap app in i18n context provider - `src/client/routes/settings.tsx`: Add language picker - Every component with hardcoded English strings: needs `t()` calls (bulk extraction task) - `src/server/index.ts`: Server-side translation utility initialization for API errors and MCP descriptions ### Scale of String Extraction - Estimated 100-200 translatable strings across the app (buttons, labels, headings, empty states, error messages, onboarding flow) - Onboarding flow is the most string-heavy component ## Specific Ideas - Language picker uses the same pill-toggle pattern as weight units and currency in settings - Auto-detection: `navigator.language` → match to available locales → fallback to `en` - String extraction can be done incrementally — doesn't need to be all-at-once - German translations generated alongside English during implementation, not as a separate post-extraction step ## Deferred Ideas None — discussion stayed within phase scope. --- *Phase: 34-i18n-foundation* *Context gathered: 2026-04-13*