i18n in React Native with Expo


Project on Github

To make this article comprehensive, I have created a repository with a real application where you can analyze the code and see the complete implementation of the examples mentioned. Visit the repository on GitHub: app-internationalization.

Image app-internationalization in Chinese Image app-internationalization in Portuguese of Brazilian Image app-internationalization in English

First, install the Libraries

You need to install the necessary libraries for react-i18next, i18next, and expo-localization.

npx expo install expo-localization react-i18next i18next

In this example, I use AsyncStorage, so you’ll need to install it as well. However, if you use another solution to persist the data, feel free to replace it accordingly.

npx expo install @react-native-async-storage/async-storage

Now, create the configuration file in your src directory. Create a file named ./i18n/index.ts with the content below:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import * as Localization from "expo-localization";
import AsyncStorage from "@react-native-async-storage/async-storage";
import translationEn from "./locales/en-US/translation.json";
import translationPt from "./locales/pt-BR/translation.json";
import translationZh from "./locales/zh-CN/translation.json";

const resources = {
  "pt-BR": { translation: translationPt },
  "en-US": { translation: translationEn },
  "zh-CN": { translation: translationZh },
};

const initI18n = async () => {
  let savedLanguage = await AsyncStorage.getItem("language");

  if (!savedLanguage) {
    savedLanguage = Localization.locale;
  }

  i18n.use(initReactI18next).init({
    compatibilityJSON: "v3",
    resources,
    lng: savedLanguage,
    fallbackLng: "pt-BR",
    interpolation: {
      escapeValue: false,
    },
  });
};

initI18n();

export default i18n;

In this example, I am using AsyncStorage to persist the internationalization data in case the user manually changes the language. Additionally, the configuration with expo-localization is used to get the device’s current language.

Import the i18n File in your root App

“I use it in _layout.tsx, but if your root file is index.ts or another file, you need to import it in that root file instead.” Example import in the file _layout.tsx of the root App:

import { useEffect } from 'react';
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import 'react-native-reanimated';
import '@/i18n'; // This line imports the i18n configuration
import { useColorScheme } from '@/hooks/useColorScheme';

export default function RootLayout() {
  const colorScheme = useColorScheme();
  const [loaded] = useFonts({
    SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
  });

  useEffect(() => {
    if (loaded) {
      SplashScreen.hideAsync();
    }
  }, [loaded]);

  if (!loaded) {
    return null;
  }

  return (
    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <Stack>
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen name="+not-found" />
      </Stack>
    </ThemeProvider>
  );
}

Now you need to create your translation files and use them in your components.

Create files for translated locales

In the i18n folder, create a folder named locales. Inside the locales folder, create subfolders for each locale, such as en-US, pt-BR, or zh-CN. Inside each subfolder, create a JSON file named translation.json with your translation entries. Below are examples of these JSON files.

name file: ./i18n/locales/en-US/translation.json

{
  "language": "Language",
  "home": {
    "title": "Home",
    "welcome": "Welcome",
    "subtitle": "Example i18n App!",
    "description": "This is an example React Native application demonstrating how to implement internationalization (i18n) using react-i18next. The app allows users to switch between different languages for a more localized experience.",
    "exploringLanguages": "Exploring Languages",
    "exploringLanguagesDescription": "Click on country flags to explore the app's content in different languages.",
    "learnMore": "Want to Learn More?",
    "repositoryLinkText": "Project repository on GitHub",
    "articlesLinkText": "More articles"
  },
  "features": {
    "title": "Features",
    "collapsibles": {
      "i18n": {
        "title": "Internationalization with i18next",
        "description": "Uses react-i18next for language management, allowing the app to be localized for different languages."
      },
      "persistent": {
        "title": "Persistent Language Selection",
        "description": "Uses AsyncStorage to persistently store the user's preferred language, providing a consistent experience across app restarts."
      },
      "fallback": {
        "title": "Language Fallback",
        "description": "Defaults to the device's language if no language preference is saved."
      },
      "switching": {
        "title": "Easy Language Switching",
        "description": "Users can switch languages by tapping on country flags."
      }
    }
  }
}

name file: ./i18n/locales/pt-BR/translation.json

{
  "language": "Idioma",
  "home": {
    "title": "Início",
    "welcome": "Bem-vindo",
    "subtitle": "App de Exemplo com i18n!",
    "description": "Este é um exemplo de aplicativo React Native que demonstra como implementar internacionalização (i18n) usando react-i18next. O aplicativo permite aos usuários alternar entre diferentes idiomas para uma experiência mais localizada.",
    "exploringLanguages": "Explorando Idiomas",
    "exploringLanguagesDescription": "Clique nas bandeiras dos países para explorar o conteúdo do aplicativo em diferentes idiomas.",
    "learnMore": "Quer Saber Mais?",
    "repositoryLinkText": "O repositório do projeto no GitHub",
    "articlesLinkText": "Mais artigos"
  },
  "features": {
    "title": "Funcionalidades",
    "collapsibles": {
      "i18n": {
        "title": "Internacionalização com i18next",
        "description": "Utiliza react-i18next para gerenciamento de idiomas, permitindo que o aplicativo seja localizado para diferentes idiomas."
      },
      "persistent": {
        "title": "Seleção de Idioma Persistente",
        "description": "Utiliza AsyncStorage para armazenar persistentemente o idioma preferido do usuário, proporcionando uma experiência consistente ao reiniciar o aplicativo."
      },
      "fallback": {
        "title": "Fallback de Idioma",
        "description": "Padrão para o idioma do dispositivo se nenhuma preferência de idioma for salva."
      },
      "switching": {
        "title": "Troca Fácil de Idioma",
        "description": "Os usuários podem trocar de idioma tocando nas bandeiras dos países."
      }
    }
  }
}

name file: ./i18n/locales/zh-CN/translation.json

{
  "language": "语言",
  "home": {
    "title": "开始",
    "welcome": "欢迎",
    "subtitle": "i18n示例应用!",
    "description": "这是一个使用react-i18next实现国际化(i18n)的React Native示例应用。该应用允许用户在不同语言之间切换,以提供更本地化的体验。",
    "exploringLanguages": "探索语言",
    "exploringLanguagesDescription": "点击国家旗帜以在不同语言下探索应用内容。",
    "learnMore": "想了解更多?",
    "repositoryLinkText": "GitHub上的项目仓库",
    "articlesLinkText": "更多文章"
  },
  "features": {
    "title": "功能",
    "collapsibles": {
      "i18n": {
        "title": "使用i18next进行国际化",
        "description": "使用react-i18next进行语言管理,使应用程序能够在不同语言环境下本地化。"
      },
      "persistent": {
        "title": "持久化语言选择",
        "description": "使用AsyncStorage持久化存储用户的首选语言,提供应用重启后的一致体验。"
      },
      "fallback": {
        "title": "语言回退

",
        "description": "如果未保存语言首选项,则默认使用设备的语言。"
      },
      "switching": {
        "title": "简便的语言切换",
        "description": "用户可以通过点击国家旗帜来切换语言。"
      }
    }
  }
}

Excellent! Now you have translation files for English, Portuguese, and Chinese.

Use your translations in components

Now, you need to use the translations in your components and create a list of flags for changing the locale using the useTranslation hook.

// import hook
import { useTranslation } from "react-i18next";

// inside a component
const { t } = useTranslation();
const text = t('example.text');

An example of basic usage in a real component:

import React, { useEffect } from "react";
import { StyleSheet, View, ScrollView, TouchableOpacity } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ThemedText } from "@/components/ThemedText";
import { useTranslation } from "react-i18next";
import Brasil from "./flags/Brasil";
import USA from "./flags/USA";
import China from "./flags/China";

const flags = [
  { component: Brasil, lang: "pt-BR", name: "Brasil" },
  { component: USA, lang: "en-US", name: "USA" },
  { component: China, lang: "zh-CN", name: "China" },
];

export function Language() {
  const { i18n, t } = useTranslation();
  const currentLanguage = i18n.language;

  useEffect(() => {
    const loadLanguage = async () => {
      const savedLanguage = await AsyncStorage.getItem("language");
      if (savedLanguage) {
        i18n.changeLanguage(savedLanguage);
      }
    };
    loadLanguage();
  }, [i18n]);

  const changeLanguage = async (lang: string) => {
    await AsyncStorage.setItem("language", lang);
    i18n.changeLanguage(lang);
  };

  return (
    <View style={styles.container}>
      <ThemedText style={styles.text}>{t('language')}</ThemedText>
      <ScrollView
        horizontal
        showsHorizontalScrollIndicator={false}
        contentContainerStyle={styles.flagsContainer}
      >
        {flags.map(({ component: Flag, lang, name }) => (
          <TouchableOpacity
            key={name}
            onPress={() => changeLanguage(lang)}
            style={[
              styles.flag,
              currentLanguage === lang && styles.activeFlag,
              currentLanguage !== lang && styles.inactiveFlag,
            ]}
          >
            <Flag width={45} height={45} />
          </TouchableOpacity>
        ))}
      </ScrollView>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    justifyContent: "center",
  },
  flagsContainer: {
    flexDirection: "row",
    paddingVertical: 10,
  },
  flag: {
    paddingHorizontal: 10,
  },
  activeFlag: {
    transform: [{ scale: 1.2 }],
  },
  inactiveFlag: {
    opacity: 0.5,
  },
  text: {
    fontSize: 22,
    lineHeight: 32,
    marginTop: -6,
  },
});

Finished! You now have a React Native app with internationalization support for multiple languages, accessible to people around the world. Happy coding and enjoy your #hacktoberfest!

References

If you need references, check out the links below for more examples:

Need Help?

Comment or get in touch with me. I’d be happy to help, and it was nice to meet you.