Internationalization
Configure English and Simplified Chinese localization.
The Expo template localizes product UI with i18n-js and
expo-localization. It ships English and Simplified Chinese catalogs, exposes a
small useI18n() hook, and lets users change language from the Profile screen.
Key files
I18nProvider is mounted inside the root provider stack and exposes:
| API | Purpose |
|---|---|
t(key, options?) | Translate catalog keys through i18n-js |
locale | Current supported locale, en or zh |
setLocale(locale) | Persist and apply a language override |
The provider returns null until it hydrates the saved preference, which avoids
rendering the app in one language and immediately switching to another.
Locale persistence
Language is stored under app_locale. Native platforms use expo-secure-store;
web uses window.localStorage. The code imports expo-localization and computes
the device language with Localization.getLocales()?.[0]?.languageTag.
In the current template code, getStoredLocale() normalizes a missing stored
value to en, so device-locale detection does not actually win on a fresh
install. If you want first launch to follow the device language, return
null when app_locale is absent and fall back to the device locale after
that.
Add a language
Add the catalog
Create lib/i18n/<locale>.json with the same key shape as en.json and
zh.json.
Register it in lib/i18n.tsx
Import the JSON file, add it to translations, and update any user-facing
language labels.
Normalize the locale
Extend normalizeLocale() if the language needs aliases such as pt-BR,
zh-Hant, or region-specific fallback behavior.
Add the picker option
app/profile.tsx defines the language picker rows. Add the new option there so
users can persist the override.
Verify both platforms
Run native and web, switch language from Profile, kill and relaunch the app, and confirm the selected locale survives.
What is localized
Most app surfaces call t(...): auth, profile, Home, drawer labels, todos,
paywall/subscription, legal consent, permissions, notifications, showcase
catalog rows, and showcase demo screens.
The Component gallery is intentionally English-only. Its catalog comment says
copy lives as plain strings in lib/component/component-catalog.ts, and the
detail demos in app/component/[slug].tsx also use inline English labels. Treat
that tab as a developer-facing reference unless you decide to move those strings
into the catalogs.
There are a few inline operational toasts such as password-reset success copy in
auth routes. Search for string literals in app/ before claiming complete app
localization.
