DEV Community

Lucas
Lucas

Posted on

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 chineseImage app-internationalization in Portuguese of BrazilianImage 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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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>
  );
}


Enter fullscreen mode Exit fullscreen mode

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."
      }
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

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."
      }
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

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": "用户可以通过点击国家旗帜来切换语言。"
      }
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

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');



Enter fullscreen mode Exit fullscreen mode

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,
  },
});


Enter fullscreen mode Exit fullscreen mode

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.

Top comments (3)

Collapse
 
mniedbalec profile image
Marcin Niedbalec (bełk0cik')

I tried to add namespaces but it seems that they doesn't work in Expo or I am doing something wrong :D Could you please make an article how to seperate translations in separate files to each language in Expo?

Collapse
 
lucasferreiralimax profile image
Lucas • Edited

I understand the issue you're facing with namespaces in Expo. This article explains the process of creating separate files for each locale in detail, especially in the section on 'Create files for translated locales.' It might be that the namespaces you're using are set up differently.

@mniedbalec you can also check out this GitHub repository for an example of how it's implemented in a working application. Comparing it with your version might help you identify what's going wrong:

github.com/livresaber/app-internat...

Collapse
 
ribeiroevandro profile image
Evandro Ribeiro

Brilliant article!

It helped me a lot with implementing the functionality in my current project. I do have a question, though—I tried a few approaches, but without success. Would it be possible to add typing in a way that provides IntelliSense when using the translation? For example, could t('') automatically suggest available keys?