The app speaks 19 languages: tiered i18n and the AI translation pipeline
The codebase survey was blunt: zero i18n infrastructure , roughly 660 user-facing text nodes across 53 files, a dozen alert dialogs, forty toasts, screen titles scattered across layout files. English was load-bearing everywhere. The reason to fix it then , rather than "after launch", was the closed-test window : Google Play makes you sit in testing for fourteen days regardless, and fourteen days of real people using translated builds is worth more than fourteen days of them using English. So the app learned six languages in one pass, then thirteen more. TL;DR — i18next + react-i18next + a plural-rules polyfill (the JS engine ships a stub Intl on some platforms, and Arabic needs six plural categories), eight namespaces, static resources so the first frame already has copy. Language is per-user, not per-device — the currency pattern — cached for the first frame, mirrored on the profile so it roams, cleared and restored on account switch; the Cognito locale attribute is written at sign-up so emails can follow later. Arabic flips the shell RTL with a native flag and a one-shot restart prompt, logical direction classes, and a font trick: the Arabic face is registered under the Latin font's names , so every existing style re-faces with zero call-site changes. Two tiers — six human-reviewable launch languages, thirteen machine-translated — generated by a diff-only pipeline that forces structured output because free-form JSON kept breaking on quotes. (Part 33 of Building CannyCart , a voice-first shopping app I'm building in public. Self-contained — no earlier context needed.) Four pillars, all cloned from patterns the app already had The plan's insight was that the app already contained every pattern i18n needed — a per-user preference (currency), a device-detected onboarding step (country), a searchable picker (voice language). i18n was the fourth instance of each, not a new discipline. The library layer is i18next + react-i18next — pure JS, no dev-client rebuild — with t