How to Offer Multi-language Support in a React Native App

Published on Aug 16, 2021

12 min read

REACT-NATIVE

Internationalization is an important feature to overcome the language barrier among people who use a particular software application. Not every app requires us to consider a global customer base. But if you have plans to include support for international users in your app, you’ll need internationalization in your React Native app.

i18next is an internationalization framework written in JavaScript and provides methods for localizing the app and implementing the other standard i18n features.

In this tutorial, let's take a look at the steps to add multi-language support to a React Native app using i18n.

Prerequisites

🔗

To follow this tutorial, please make sure you are familiarized with JavaScript/ES6, basics of React and meet the following requirements in your local dev environment:

  • Node.js version 12.x.x or above installed.
  • Have access to one package manager such as npm or yarn or npx.
  • react-native-cli installed, or use npx.

Setting up a React Native app

🔗

After initializing a React Native project, make sure to install the external libraries to follow along with this tutorial. Navigate inside the project directory, and then run the following command install the following libraries:

yarn add react-i18next i18next @react-navigation/native @react-navigation/bottom-tabs @react-native-async-storage/async-storage react-native-vector-icons react-native-screens react-native-safe-area-context react-native-reanimated react-native-localize react-native-gesture-handler
# after this step, for iOS, install pods
npx pod-install ios

React Native Vector Icons will be used for adding icons in the app. React Navigation is used to add and enable navigation between screens in the app. Make sure to initialize and configure navigation as described in React Navigation library getting started doc.

The following libraries are going to be used for adding multi-language support to the app:

  • i18next: internationalization library.
  • react-i18next: provides binding for React and React Native projects using Hooks, High Order Components (HOCs), etc. We will use the useTranslation hook to translate the text within React Native function components.
  • react-native-localize: provides helper functions to figure based on the device's localized language preference.
  • @react-native-async-storage/async-storage: is an unencrypted, asynchronous, persistent, key-value storage system that is global to the app. It is used to store the user's language preference such that it persists when the app restarts.

🔥 Tip: Always make sure to check out installation steps in the documentation of libraries installed in a React Native app. Some may differ and change over time. It's hard to keep a blog post up to date with all these changes.

Building a React Native app

🔗

After installing libraries, let's setup the React Native app with mock screens and navigation.

Create a src/ folder inside the project root directory and inside it, create the following files and folders:

  • /constants
    • /translations
    • IMLocalize.js
  • /navigation
    • RootNavigator.js
  • /screens
    • /HomeScreen.js
    • SettingsScreen.js
  • /components
    • LanguageSelector.js

Start by adding a RootNavigator.js file inside the /navigation folder. It will have both screens as tabs and some configuration to display an icon and a label for each tab.

1import * as React from 'react';
2import { Text, View } from 'react-native';
3import { NavigationContainer } from '@react-navigation/native';
4import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
5import Ionicons from 'react-native-vector-icons/dist/Ionicons';
6
7import HomeScreen from '../screens/HomeScreen';
8import SettingsScreen from '../screens/SettingsScreen';
9
10const Tab = createBottomTabNavigator();
11
12export default function RootNavigator() {
13 return (
14 <NavigationContainer>
15 <Tab.Navigator
16 screenOptions={({ route }) => ({
17 tabBarIcon: ({ focused, color, size }) => {
18 let iconName;
19
20 if (route.name === 'Home') {
21 iconName = focused ? 'ios-home' : 'ios-home-outline';
22 } else if (route.name === 'Settings') {
23 iconName = focused ? 'ios-settings' : 'ios-settings-outline';
24 }
25
26 return <Ionicons name={iconName} size={size} color={color} />;
27 },
28 tabBarActiveTintColor: 'tomato',
29 tabBarInactiveTintColor: 'gray',
30 headerShown: false
31 })}
32 >
33 <Tab.Screen name="Home" component={HomeScreen} />
34 <Tab.Screen name="Settings" component={SettingsScreen} />
35 </Tab.Navigator>
36 </NavigationContainer>
37 );
38}

Next, let's add code snippets for screens. In HomeScreen.js, add the following code. For now, it only displays a Text component:

1import React from 'react';
2import { Text, View } from 'react-native';
3
4export default function HomeScreen() {
5 return (
6 <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
7 <Text>Home</Text>
8 </View>
9 );
10}

Similarly, the SettingsScreen.js file will also display a Text component:

1import React from 'react';
2import { Text, View } from 'react-native';
3
4export default function SettingsScreen() {
5 return (
6 <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
7 <Text>Settings!</Text>
8 </View>
9 );
10}

Now, modify the App.js file to add the following code snippet:

1import React from 'react';
2
3import RootNavigator from './src/navigation/RootNavigator';
4
5export default function App() {
6 return <RootNavigator />;
7}

At this point, if you run the npx react-native run-ios or npx react-native run-android command, you should see the following screen on a simulator/emulator or on a device:

cb1

Create translation files

🔗

Initially, we will like to translate tab names based on the language selected within the app. To do this, we need to create translation config files.

You can organize these translation files in the way you want, but here we are following a pattern. Inside constants/translations/ directory, let's create subdirectories for each language to support in this demo app. The languages supported here are en for English and fr for French.

Inside each language directory, create separate files that will split the translations from commonly used text to translate specific texts such as for tab navigation labels. Under i18n, this separation leads to creating namespaces for each language. Later in the tutorial, you will see how to access the value of a key, for example, home from the namespace navigation to translate the tab bar label.

Here is how the directory structure would like under translations/:

cb1

Inside en/common.js file, add the following snippet:

1export default {
2 hello: 'Hello',
3 languageSelector: 'Select Your Language'
4};

Inside en/navigate.js file, add the following code snippet:

1export default {
2 hello: 'Bonjour',
3 languageSelector: 'Sélecteur de langue'
4};

Next, inside add translated tab labels for each language in their corresponding navigate.js files:

1// en/navigate.js
2export default {
3 home: 'Home!',
4 settings: 'Settings'
5};
6
7
8// fr/navigate.js
9export default {
10 home: 'Écran principal',
11 settings: 'Le réglage'
12};

Lastly, export these translated texts:

1// en/index.js
2import common from './common';
3import navigate from './navigate';
4
5export default {
6 common,
7 navigate
8};
9
10// fr/index.js
11import common from './common';
12import navigate from './navigate';
13
14export default {
15 common,
16 navigate
17};

Adding multi-language support configuration

🔗

Now that you have translation files ready and dependencies installed, let's configure how to create a configuration using those libraries installed earlier.

All of this configuration will live inside IMLocalize.js file. Start by importing the following dependencies. Also, define a LANGUAGES object that requires each language file as an object and using JavaScript syntax of Object.keys convert the LANGUAGES object to an array.

1import i18n from 'i18next';
2import { initReactI18next } from 'react-i18next';
3import AsyncStorage from '@react-native-async-storage/async-storage';
4import * as RNLocalize from 'react-native-localize';
5
6import en from './translations/en';
7import fr from './translations/fr';
8
9const LANGUAGES = {
10 en,
11 fr
12};
13
14const LANG_CODES = Object.keys(LANGUAGES);

The i18n is configured in a certain way. The initial step it requires is to detect a language. Hence, define your own custom language detector. It will check the user's stored language preference when the app starts. If the user's language preference is not available, you will need to define a fallback language or find the best available language to fall back on.

Create a LANGUAGE_DETECTOR configuration object:

1const LANGUAGE_DETECTOR = {
2 type: 'languageDetector',
3 async: true,
4 detect: callback => {
5 AsyncStorage.getItem('user-language', (err, language) => {
6 // if error fetching stored data or no language was stored
7 // display errors when in DEV mode as console statements
8 if (err || !language) {
9 if (err) {
10 console.log('Error fetching Languages from asyncstorage ', err);
11 } else {
12 console.log('No language is set, choosing English as fallback');
13 }
14 const findBestAvailableLanguage =
15 RNLocalize.findBestAvailableLanguage(LANG_CODES);
16
17 callback(findBestAvailableLanguage.languageTag || 'en');
18 return;
19 }
20 callback(language);
21 });
22 },
23 init: () => {},
24 cacheUserLanguage: language => {
25 AsyncStorage.setItem('user-language', language);
26 }
27};

Then, add the configuration initialize i18n. It will start by detecting the language, passing the i18n instance to react-i18next, and initializes using some options. This option makes i18n available for all React Native components.

1i18n
2 // detect language
3 .use(LANGUAGE_DETECTOR)
4 // pass the i18n instance to react-i18next.
5 .use(initReactI18next)
6 // set options
7 .init({
8 resources: LANGUAGES,
9 react: {
10 useSuspense: false
11 },
12 interpolation: {
13 escapeValue: false
14 },
15 defaultNS: 'common'
16 });

These options may vary depending on your React Native project. We recommend you to go through available configuration options for i18n.

Next, import the IMLocalize file in App.js file:

1// after other import statements
2import './src/constants/IMLocalize';

Creating a Language Selector component

🔗

Since you have initialized the languages in the React Native app, the next step is to allow the user to select between different languages available inside the app.

Inside LanguageSelector.js file, start by importing the following libraries:

1import React from 'react';
2import { View, Text, StyleSheet, Pressable } from 'react-native';
3import Ionicons from 'react-native-vector-icons/dist/Ionicons';
4import { useTranslation } from 'react-i18next';

The useTranslation hook will allow accessing i18n instance inside this custom component which is used to change the language.

Next, define an array of LANGUAGES.

1const LANGUAGES = [
2 { code: 'en', label: 'English' },
3 { code: 'fr', label: 'Français' }
4];

Then, define the function component Selector. It will allow the user to switch between different languages inside the app and also enlist the available languages.

It will get the currently selected language from the i18n instance. Using a handler method called setLanguage, you can allow the functionality to switch between different languages from the LANGUAGES array defined above this function component.

This function component uses Pressable from React Native to change the language.

1const LANGUAGES = [
2 { code: 'en', label: 'English' },
3 { code: 'fr', label: 'Français' }
4];
5
6const Selector = () => {
7 const { i18n } = useTranslation();
8 const selectedLanguageCode = i18n.language;
9
10 const setLanguage = code => {
11 return i18n.changeLanguage(code);
12 };
13
14 return (
15 <View style={styles.container}>
16 <View style={styles.row}>
17 <Text style={styles.title}>Select a Language</Text>
18 <Ionicons color="#444" size={28} name="ios-language-outline" />
19 </View>
20 {LANGUAGES.map(language => {
21 const selectedLanguage = language.code === selectedLanguageCode;
22
23 return (
24 <Pressable
25 key={language.code}
26 style={styles.buttonContainer}
27 disabled={selectedLanguage}
28 onPress={() => setLanguage(language.code)}
29 >
30 <Text
31 style={[selectedLanguage ? styles.selectedText : styles.text]}
32 >
33 {language.label}
34 </Text>
35 </Pressable>
36 );
37 })}
38 </View>
39 );
40};
41
42const styles = StyleSheet.create({
43 container: {
44 paddingTop: 60,
45 paddingHorizontal: 16
46 },
47 row: {
48 flexDirection: 'row',
49 alignItems: 'center',
50 justifyContent: 'space-between'
51 },
52 title: {
53 color: '#444',
54 fontSize: 28,
55 fontWeight: '600'
56 },
57 buttonContainer: {
58 marginTop: 10
59 },
60 text: {
61 fontSize: 18,
62 color: '#000',
63 paddingVertical: 4
64 },
65 selectedText: {
66 fontSize: 18,
67 fontWeight: '600',
68 color: 'tomato',
69 paddingVertical: 4
70 }
71});
72
73export default Selector;

Import the Selector component inside the SettingsScreen.js file:

1import React from 'react';
2import { View } from 'react-native';
3
4import Selector from '../components/LanguageSelector';
5
6export default function SettingsScreen() {
7 return (
8 <View style={{ flex: 1, backgroundColor: '#fff' }}>
9 <Selector />
10 </View>
11 );
12}

Here is the output in the simulator after this step:

cb1

Using the useTranslation hook

🔗

The useTranslation hook has two important functions that you can utilize inside your React Native app. You have already seen the first one (i18n instance) in the previous step. The next is called t (my personal guess is that it is short for translation) function. You can refer the namespaces defined in the translation files and pass them as arguments to this function.

Let's see that in action. Let's start with the LanguageSelector component itself. It has a title called Select a Language. While defining the translation files, we've already defined its translation in both English and French languages in their corresponding common.js files.

The initial step to getting the t function is to import the useTranslation hook. However, the LanguageSelector.js file already has it from the previous section.

Modify the following line to get the t function from the hook inside the Selector component:

1const { t, i18n } = useTranslation();

Next, modify the Text component contents used to define the title:

1<Text style={styles.title}>{t('common:languageSelector')}</Text>

Here is the output. The default or the initial language in our case is English. When the next language is selected, it translates the title on the Settings screen.

cb1

You can also modify the text strings according to the previously defined namespaces in the translation files.

For an example, the RootNavigator will be modified as follows:

1import * as React from 'react';
2import { Text, View } from 'react-native';
3import { NavigationContainer } from '@react-navigation/native';
4import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
5import Ionicons from 'react-native-vector-icons/dist/Ionicons';
6import { useTranslation } from 'react-i18next';
7
8import HomeScreen from '../screens/HomeScreen';
9import SettingsScreen from '../screens/SettingsScreen';
10
11const Tab = createBottomTabNavigator();
12
13export default function RootNavigator() {
14 const { t } = useTranslation();
15 return (
16 <NavigationContainer>
17 <Tab.Navigator
18 screenOptions={({ route }) => ({
19 tabBarIcon: ({ focused, color, size }) => {
20 let iconName;
21
22 if (route.name === 'Home') {
23 iconName = focused ? 'ios-home' : 'ios-home-outline';
24 } else if (route.name === 'Settings') {
25 iconName = focused ? 'ios-settings' : 'ios-settings-outline';
26 }
27
28 return <Ionicons name={iconName} size={size} color={color} />;
29 },
30 tabBarActiveTintColor: 'tomato',
31 tabBarInactiveTintColor: 'gray',
32 headerShown: false
33 })}
34 >
35 <Tab.Screen
36 name="Home"
37 component={HomeScreen}
38 options={{ tabBarLabel: t('navigate:home') }}
39 />
40 <Tab.Screen
41 name="Settings"
42 component={SettingsScreen}
43 options={{ tabBarLabel: t('navigate:settings') }}
44 />
45 </Tab.Navigator>
46 </NavigationContainer>
47 );
48}

Here is the final output:

cb1

Conclusion

🔗

This completes our tutorial on how to add multi-language support in a React Native app. There are different strategies you can use inside your app to provide translation support. This tutorial is just one of the examples.

Please don't mind my translation for French text corresponding to English text. I am not good at it at all. 😅

Useful Links


More Posts

Browse all posts

Aman Mittal author

I'm a software developer and a technical writer. On this blog, I write about my learnings in software development and technical writing.

Currently, working as the documentation lead at 𝝠 Expo. Read more about me on the About page.


Copyright ©  2019-2024 Aman Mittal · All Rights Reserved.