DEV Community

Cover image for React Native i18n: A Practical Guide to Multi-Language Mobile Apps
Famitha M A
Famitha M A

Posted on • Originally published at fami-blog.hashnode.dev

React Native i18n: A Practical Guide to Multi-Language Mobile Apps

React Native i18n: A Practical Guide to Multi-Language Mobile Apps

Most i18n bugs in React Native apps trace back to two decisions made early: hardcoding strings, and using string concatenation to build sentences. Both feel fine when you're shipping in English. Both are catastrophic the day you add a second locale.

This is the practical playbook I wish I had when I first added Spanish, Japanese, and Arabic to a React Native app: which library to pick, the patterns that scale, and the traps that only show up in production.

TL;DR

  • Use i18next + react-i18next + expo-localization (or react-native-localize on bare RN).
  • Never hardcode strings, never concatenate to build sentences.
  • Use ICU pluralization — count === 1 ? 'a' : 'b' is wrong in most languages.
  • Plan for RTL from day one with marginStart/marginEnd instead of left/right.
  • Use namespaces (auth.json, home.json) once you cross ~500 keys.
  • Format dates/numbers/currencies with the Intl API.
  • Hook up a translation management service (Crowdin, Lokalise, Locize) before you ship to a third locale.

Why i18n Is an Architectural Decision

Retrofitting translations into a codebase that hardcodes strings everywhere is one of the most painful refactors in mobile development. For a 50-screen app, expect 1-3 weeks of dedicated work to extract strings, set up a translation library, and validate every screen.

The discipline of always using t('home.welcome') instead of "Welcome" from day one means you never have a "what strings did we miss?" problem when you add a second language. It also doubles as a code review smell test.

Picking a Library

Library Best For Bundle Size
i18next + react-i18next Most apps ~50KB
react-intl (FormatJS) ICU MessageFormat purists ~80KB
LinguiJS Type-safe, compile-time extraction ~20KB

For 95% of React Native projects, default to i18next unless you have a specific reason not to.

Minimal Setup (Expo)

// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as Localization from 'expo-localization';
import en from './locales/en/common.json';
import es from './locales/es/common.json';

i18n
  .use(initReactI18next)
  .init({
    resources: {
      en: { common: en },
      es: { common: es },
    },
    lng: Localization.locale.split('-')[0],
    fallbackLng: 'en',
    defaultNS: 'common',
    interpolation: { escapeValue: false },
    compatibilityJSON: 'v3',
  });

export default i18n;
Enter fullscreen mode Exit fullscreen mode
// App.js
import './i18n';
import { useTranslation } from 'react-i18next';

export default function App() {
  const { t } = useTranslation();
  return <Text>{t('greeting', { name: 'Maria' })}</Text>;
}
Enter fullscreen mode Exit fullscreen mode

7 Patterns That Scale

1. Use interpolation, not concatenation

// Bad
t('hello') + ' ' + user.name + '!'

// Good
t('greeting', { name: user.name })
// en.json: { "greeting": "Hello, {{name}}!" }
// ja.json: { "greeting": "{{name}}さん、こんにちは!" }
Enter fullscreen mode Exit fullscreen mode

2. Use ICU pluralization

English has 2 plural forms, Russian has 3, Arabic has 6. Don't roll your own.

{
  "items_one": "{{count}} item",
  "items_other": "{{count}} items"
}
Enter fullscreen mode Exit fullscreen mode

3. Plan for RTL from day one

  • Use marginStart/marginEnd instead of left/right.
  • Mirror directional icons: transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }].
  • Switching between LTR/RTL requires an app restart — design your language switcher UX accordingly.

4. Namespace your translations

locales/en/
├── common.json
├── auth.json
├── home.json
└── errors.json
Enter fullscreen mode Exit fullscreen mode

Lazy-load namespaces per screen to reduce startup load on lower-end Android.

5. Format with the Intl API

new Intl.DateTimeFormat(i18n.language, { dateStyle: 'long' }).format(date);
new Intl.NumberFormat(i18n.language, { style: 'currency', currency: 'USD' }).format(amount);
Enter fullscreen mode Exit fullscreen mode

Hermes supports Intl natively as of RN 0.71+. If you're older, use the formatjs polyfill — but upgrade Hermes if you can.

6. Set up a translation pipeline

Hook your locales/ folder to Crowdin, Lokalise, or Locize before you ship to a third language. CI should fail the build on missing keys in production locales.

7. Test with pseudo-localization

Use a "fake locale" that wraps every string in brackets and adds 30% length. Catches truncation, hardcoded strings, and layout breakage before real translators see anything.

How RapidNative Handles This

RapidNative is an AI app builder that generates React Native + Expo code from natural-language prompts. The generated components use translation keys (t('auth.sign_in_button')) instead of hardcoded strings, and the translation JSON is scaffolded alongside the code — so adding a new locale is dropping in a translated JSON file, not refactoring imports.

If you're starting a new project and don't want to build the i18n scaffolding by hand, that's a faster path to "i18n-ready by default."

Wrapping Up

Multi-language support gets dramatically more expensive over time. Build it in from day one — translation keys, ICU plurals, RTL-aware layouts, real translation pipeline — and your app can ship to any market without a future refactor.

What pattern do you wish you'd known earlier? Drop it in the comments — always curious how other teams handle the RTL transition specifically.

Top comments (0)