i18next
Set up i18next for i18n in an Expo app with ReadyNative: react-i18next + expo-localization, 6 languages bundled incl. Arabic (RTL) · Expo Go OK
Expo Go: yes
Runs in Expo Go; no dev build needed for this module.
i18next plus react-i18next replace the core i18n shim in src/lib/i18n.ts, keeping exactly the same surface your screens already use: i18n.t(key, vars) and useT(). I'd pick it when you want the ecosystem - plurals, formatters, ICU add-ons, and translators who already know the format. Pick lingui if you'd rather have a CLI-driven catalog workflow, and none while you're still English-only. Keys are the English source strings, so a screen reads identically with or without this module.
Setup
bun run setup --i18n i18nextSix languages ship - English, Spanish, Russian, Chinese (Simplified), Portuguese and Arabic (src/locales/{en,es,ru,zh,pt,ar}.json); the device language wins, and Settings has a language picker that renders from LANGUAGES. To add a locale:
- Create
src/locales/<code>.jsonnext to the shipped ones (a test checks every locale has the same keys asen.json). - Register it: add the code to
LANGUAGES, a native name toLANGUAGE_LABELSand an entry inresourcesinsrc/lib/i18n.ts- and toRTL_LANGUAGESif it's written right-to-left (Hebrew, Persian, Urdu).LANGUAGESis the single source of truth - the Settings picker (and/examples/i18n, with--with-examples) render from it.
bun run gen screen --tab appends new tab titles to every locale file (English value, translate later). Locale files are yours: setup/clean never remove them.
Setup also adds expo-localization and its config plugin for the device language, persists the user's override under readynative:language through your storage adapter, registers I18nProvider (order 60), and adds a test (plus the /examples/i18n route with --with-examples).
Usage
import { useT } from "@/lib/i18n";
import { Text } from "@/components/ui";
const t = useT();
<Text>{t("Version {{version}}", { version })}</Text>;To add a key, write the English sentence in code and add it to every src/locales/*.json. Missing keys fall back to English, then to the key itself.
To switch language at runtime, use setLanguage(code) and read the current one with useLanguage(); deviceLanguage() gives you what the OS reports.
Right-to-left
Arabic is in RTL_LANGUAGES. setLanguage("ar") calls I18nManager.forceRTL(true) and restarts the app with reloadAppAsync(), because React Native applies a new layout direction only on launch; switching back to a left-to-right language does the reverse. Rows, stacks, tab bars and back buttons mirror on their own. On web it flips <html dir> in place, with no restart. isRTL(language) tells you the direction of any language.
- Expo Go resets the direction whenever it opens a project, so the text turns Arabic but the layout stays left-to-right there - check RTL in a dev build.
- The
expo-localizationconfig plugin keepssupportsRTLon (the default). Set["expo-localization", { supportsRTL: false }]in your app config if you never ship an RTL language. - Write layouts with
start/endin mind:marginLeft/paddingRightmirror automatically, absoluteleft/rightpositions and direction-bearing icons (arrows, chevrons) need a look.
Gotchas
- Expo Go works - it's pure JS plus
expo-localization. keySeparatorandnsSeparatorare off, so sentences containing dots or colons are valid keys.- On an async storage adapter (
async-storage), the stored language is only known afterinitI18n()resolves - gate first paint on the splash screen if a language flash would show. - Swapping stacks:
bun run setup --i18n nonerestores the shim re-export, removes the locales, the example and the three dependencies. Screens keep working -t(key)returns the English key.
Remove it
While modules/ exists (a tree set up with --keep-modules), setup does all of it:
bun run setup --i18n none --yes --keep-modulesIn a finalized tree setup is a stub, so you undo it by hand. Here is everything this module added:
- Delete the files that are still there (the demo screens are gone already unless you set up with
--with-examples):src/app/examples/i18n.tsx,src/lib/__tests__/i18n.test.tsx,src/locales/ar.json,src/locales/en.json,src/locales/es.json,src/locales/pt.json,src/locales/ru.json,src/locales/zh.json,src/screens/examples/i18n-example-screen.tsx. - Replace, don't delete
src/lib/i18n.ts: core code imports it, so swap in the no-op version frommodules/i18n/none/files/of a fresh clone of your tier repo - same exports, nothing behind them. - Uninstall the dependencies:
bun remove expo-localization i18next react-i18next. Keep any ofexpo-localization(also used byanalytics/posthog,consent/consent) that another module you picked still needs. - Drop the config plugin
expo-localizationfrom.readynative.json→modules.app.expo.plugins(that is whereapp.config.tsreads it from), then rebuild the dev build. - Unwrap the provider: delete
<I18nProvider>and its import fromsrc/providers.tsx. - Check it:
bun run typecheckandbun run lintpoint at anything that still imports the removed files;bun run gen:graphrefreshesdocs/ARCHITECTURE.md.
Reference
Everything below is generated from modules/i18n/i18next/module.json - the same file bun run setup reads, so it is what actually lands in your repo.
Install
bun run setup --i18n i18nextModule id: i18n/i18next (the default for this category).
Dependencies
| Package | Version | Kind |
|---|---|---|
expo-localization | ~57.0.2 | dependency (expo install) |
i18next | ^26.4.2 | dependency |
react-i18next | ^17.0.15 | dependency |
Config plugins
Merged into app.config.ts through .readynative.json (modules.app):
expo-localization
Providers
Rendered in src/providers.tsx (lower order = outermost):
| Order | Provider | From |
|---|---|---|
| 60 | I18nProvider | @/lib/i18n |
Compatibility
- Requires
storage=kv-store,mmkv,async-storage
Files
10 files copied to the project root
src/app/examples/i18n.tsxsrc/lib/__tests__/i18n.test.tsxsrc/lib/i18n.tssrc/locales/ar.jsonsrc/locales/en.jsonsrc/locales/es.jsonsrc/locales/pt.jsonsrc/locales/ru.jsonsrc/locales/zh.jsonsrc/screens/examples/i18n-example-screen.tsx