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
- Organize Translation Keys:
// Structured translation files
{
"translation": {
"common": {
"welcome": "Welcome",
"save": "Save"
},
"auth": {
"login": "Login",
"signup": "Sign Up"
}
}
}
- Handle Loading States:
function App() {
const { t, i18n } = useTranslation();
if (!i18n.isInitialized) {
return <LoadingScreen />;
}
return <MainApp />;
}
- Remember Platform Differences:
// Configure based on platform
i18n.init({
compatibilityJSON: Platform.OS === "android" ? "v3" : undefined,
// other config...
});
Performance Tips
- Namespace Splitting: Split translations into namespaces for lazy loading
- Memoize Components: Use React.memo for components that only depend on translations
- 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.