All writing
React Native

Complete Guide to React Native Internationalization (i18n)

Learn how to implement internationalization in your React Native app using react-i18next

Complete Guide to React Native Internationalization (i18n)

If you want your React Native app to reach users outside English-speaking markets, you need i18n. This is how I set it up with react-i18next.

What is i18n?

Internationalization (i18n) is the work of adapting an app to different languages and regions. The abbreviation comes from the 18 letters between the ‘i’ and the ‘n’ in “internationalization”.

Setting Up i18n in React Native

1. Installation

Install the packages:

yarn add i18next react-i18next
# or
npm install i18next react-i18next

2. Creating Language Files

Create a languages folder with JSON files for each language:

// languages/en.json
{
    "translation": {
        "welcome": "Welcome",
        "choose": "Select a language",
        "name": "Name",
        "portuguese": "Portuguese",
        "english": "English",
        "spanish": "Spanish"
    }
}

// languages/es.json
{
    "translation": {
        "welcome": "Bienvenido",
        "choose": "Seleccione un idioma",
        "name": "Nombre",
        "portuguese": "Portugués",
        "english": "Inglés",
        "spanish": "Español"
    }
}

// languages/pt.json
{
    "translation": {
        "welcome": "Seja Bem-Vindo",
        "choose": "Selecione um idioma",
        "name": "Nome",
        "portuguese": "Português",
        "english": "Inglês",
        "spanish": "Espanhol"
    }
}

3. Configuring i18n

Create an i18n.ts configuration file:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";

import en from "./languages/en.json";
import es from "./languages/es.json";
import pt from "./languages/pt.json";

const resources = {
  en,
  es,
  pt,
};

i18n.use(initReactI18next).init({
  compatibilityJSON: "v3",
  resources,
  lng: "en", // default language
  fallbackLng: "en",
  interpolation: {
    escapeValue: false,
  },
});

export default i18n;

Implementation Methods

Three ways to pick the language:

1. Manual Language Selection with Dropdown

import React, { useState } from "react";
import { View, Text } from "react-native";
import { useTranslation } from "react-i18next";
import DropDownPicker from "react-native-dropdown-picker";

export default function LanguageSelector() {
  const { t, i18n } = useTranslation();
  const [open, setOpen] = useState(false);
  const [value, setValue] = useState(i18n.language);

  const [items] = useState([
    { label: "English", value: "en" },
    { label: "Español", value: "es" },
    { label: "Português", value: "pt" },
  ]);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{t("choose")}</Text>
      <DropDownPicker
        open={open}
        value={value}
        items={items}
        setOpen={setOpen}
        setValue={setValue}
        onChangeValue={(value) => {
          i18n.changeLanguage(value);
        }}
        style={styles.dropdown}
      />
    </View>
  );
}

2. Device Location Based Language

import { useEffect } from "react";
import * as Location from "expo-location";
import { useTranslation } from "react-i18next";

export function useLocationBasedLanguage() {
  const { i18n } = useTranslation();

  useEffect(() => {
    async function setLanguageByLocation() {
      try {
        const { status } = await Location.requestForegroundPermissionsAsync();
        if (status !== "granted") return;

        const location = await Location.getCurrentPositionAsync({});
        const geocode = await Location.reverseGeocodeAsync({
          latitude: location.coords.latitude,
          longitude: location.coords.longitude,
        });

        const countryCode = geocode[0]?.isoCountryCode;
        const languageMap = {
          US: "en",
          ES: "es",
          BR: "pt",
          // Add more country-to-language mappings
        };

        const language = languageMap[countryCode] || "en";
        i18n.changeLanguage(language);
      } catch (error) {
        console.error("Error setting language by location:", error);
      }
    }

    setLanguageByLocation();
  }, []);
}

3. Device Language Based

import { useEffect } from "react";
import * as Localization from "expo-localization";
import { useTranslation } from "react-i18next";

export function useDeviceLanguage() {
  const { i18n } = useTranslation();

  useEffect(() => {
    const deviceLanguage = Localization.getLocales()[0].languageCode;
    const supportedLanguages = ["en", "es", "pt"];

    const language = supportedLanguages.includes(deviceLanguage)
      ? deviceLanguage
      : "en";

    i18n.changeLanguage(language);
  }, []);
}

Using Translations in Your Components

Call useTranslation and pass keys to t:

import React from "react";
import { View, Text } from "react-native";
import { useTranslation } from "react-i18next";

export default function WelcomeScreen() {
  const { t } = useTranslation();

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{t("welcome")}</Text>
      <Text>{t("name", { name: "John" })}</Text>
    </View>
  );
}

Best Practices

  1. Organize Translation Keys:
// Structured translation files
{
  "translation": {
    "common": {
      "welcome": "Welcome",
      "save": "Save"
    },
    "auth": {
      "login": "Login",
      "signup": "Sign Up"
    }
  }
}
  1. Handle Loading States:
function App() {
  const { t, i18n } = useTranslation();

  if (!i18n.isInitialized) {
    return <LoadingScreen />;
  }

  return <MainApp />;
}
  1. Remember Platform Differences:
// Configure based on platform
i18n.init({
  compatibilityJSON: Platform.OS === "android" ? "v3" : undefined,
  // other config...
});

Performance Tips

  1. Namespace Splitting: Split translations into namespaces for lazy loading
  2. Memoize Components: Use React.memo for components that only depend on translations
  3. Avoid Nested Translations: Keep translation keys flat when possible
// Good
{
  "welcome_message": "Welcome to our app",
  "user_greeting": "Hello, {{name}}"
}

// Avoid
{
  "messages": {
    "welcome": {
      "title": "Welcome to our app"
    }
  }
}

Testing Internationalization

Mock react-i18next so t returns the key:

import { render, screen } from "@testing-library/react-native";
import { useTranslation } from "react-i18next";

jest.mock("react-i18next", () => ({
  useTranslation: () => ({
    t: (key) => key,
    i18n: {
      changeLanguage: jest.fn(),
    },
  }),
}));

test("renders welcome message", () => {
  render(<WelcomeScreen />);
  expect(screen.getByText("welcome")).toBeTruthy();
});

Conclusion

Pick whichever language-selection method fits the app (manual, location, or device). The thing that matters most is settling on a translation key structure early and sticking to it. Retrofitting keys later is painful.

0 claps
If this was useful, let me know.
Prevent the Keyboard from Covering React Native UI Components Building Offline First Apps in React Native - A Complete Guide