As a business grows, it’s only natural to consider expansion into markets in other countries and regions as a next step. If you have an app, it’s important to consider catering to the language needs of the communities you are expanding into as well—in app development, this is called Internationalization!
In this tutorial, we’re going to go through the process of adding multi-language support to a React Native app using i18next. If you’re unfamiliar with it, I18next is an internationalization framework written in and for JavaScript that provides a complete solution for localizing your product from web to mobile and desktop.
To follow along with this tutorial, please make sure that you are 1) familiarized with JavaScript/ES6, 2) understand the basics of React Native, and 3) able to meet the following requirements in your local dev environment:
12.x.x
or above installedDepending on your skill level and experience, it may also be beneficial to brush up on how to scaffold a new custom mobile app with Crowdbotics prior to jumping into this tutorial.
Now that we’ve gotten all of that out of the way, let’s get started!
After initializing a React Native project, it’s important to install the external libraries needed to properly follow along in this tutorial. To do this, navigate inside the project directory and run the following install command for 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
We’ll be using React Native Vector Icons for adding icons in our app and React Navigation to add and enable navigation between screens in the app. For React Navigation, make sure to initialize and configure navigation as described in the Getting Started documentation.
We will use the following libraries to add multi-language support to our app:
i18next
: As previously mentioned, this is our internationalization library.react-i18next
: This library 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 our React Native function components.react-native-localize
: This library provides helper functions based on the device’s localized language preferences.@react-native-async-storage/async-storage
: This 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 so that it remains constant even when the app restarts.🔥 Tip: It’s always a good rule of thumb to check out the installation steps laid out in the documentation for the libraries you’re installing in your React Native app. They are prone to changing over time, and it can be challenging to keep blog posts up-to-date with all of these changes.
Now that we’ve installed our libraries, let’s set up our React Native app with mock screens and navigation.
Create an src/
folder in the project root directory and inside of that, create the following files and folders:
/constants
/translations
IMLocalize.js
/navigation
RootNavigator.js
/screens
/HomeScreen.js
SettingsScreen.js
/components
LanguageSelector.js
Next, add the RootNavigator.js
file to the /navigation
folder. The snipped below is used to create the “Home” and “Settings” tabs located in the navigation at the bottom of the screen, and it includes some configuration to display an icon and a label for each.
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Ionicons from 'react-native-vector-icons/dist/Ionicons';
import HomeScreen from '../screens/HomeScreen';
import SettingsScreen from '../screens/SettingsScreen';
const Tab = createBottomTabNavigator();
export default function RootNavigator() {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = focused ? 'ios-home' : 'ios-home-outline';
} else if (route.name === 'Settings') {
iconName = focused ? 'ios-settings' : 'ios-settings-outline';
}
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray',
headerShown: false
})}
>
<Tab.Screen name='Home' component={HomeScreen} />
<Tab.Screen name='Settings' component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
Now that we have our nav bar, let’s add some code snippets for the screens they correspond to. In HomeScreen.js
, add the following code. For now, it will only display a Text
component, but we’ll be adding to this later in the tutorial:
import React from 'react';
import { Text, View } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home</Text>
</View>
);
}
Similarly, add the following snippet to the SettingsScreen.js
—this file will also display a Text
component for the time being:
import React from 'react';
import { Text, View } from 'react-native';
export default function SettingsScreen() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings!</Text>
</View>
);
}
Next, modify the App.js
file to add the following code snippet:
import React from 'react';
import RootNavigator from './src/navigation/RootNavigator';
export default function App() {
return <RootNavigator />;
}
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:
Now, we need to translate our tab names based on the preferred language selected within the app. To do this, we’ll need to create translation config files.
You can organize these translation files in whatever style you’d like, but it’s important to follow some kind of pattern. Inside the constants/translations/
directory, create subdirectories for each language you’d like to support in this demo app. For our purposes, we’re going to be using en
for English and fr
for French.
Inside each language directory, we’ll need to create separate files that we can use to split the translations for the text that we’ll use in our app, like the navigation tab labels we just created. Under i18n
, this separation leads to creating namespaces for each language. Later in the tutorial, we’ll discuss how to access the value of a key—for example, home
from the namespace navigation
to translate the tab bar label for the Home screen.
Here is how the directory structure will look under translations/
:
Inside en/common.js
file, add the following snippet:
export default {
hello: 'Hello',
languageSelector: 'Select Your Language'
};
Similarly, add the following code snippet to the fr/common.js
file:
export default {
hello: 'Bonjour',
languageSelector: 'Sélecteur de langue'
};
Next, we’ll want to add translated tab labels for each language in their corresponding navigate.js
files:
// en/navigate.js
export default {
home: 'Home!',
settings: 'Settings'
};
// fr/navigate.js
export default {
home: 'Écran principal',
settings: 'Le réglage'
};
Finally, export these translated texts:
// en/index.js
import common from './common';
import navigate from './navigate';
export default {
common,
navigate
};
// fr/index.js
import common from './common';
import navigate from './navigate';
export default {
common,
navigate
};
Now that we have translation files ready and dependencies installed, let’s create a configuration using those libraries we installed previously.
This configuration will live inside the IMLocalize.js
file, and we’ll want to start off by importing the following dependencies. Also, we’ll want to define our LANGUAGES
constants—it saves each language file as an object and, using the JavaScript syntax Object.keys
, will convert the LANGUAGES
objects to an array.
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as RNLocalize from 'react-native-localize';
import en from './translations/en';
import fr from './translations/fr';
const LANGUAGES = {
en,
fr
};
const LANG_CODES = Object.keys(LANGUAGES);
i18n
is configured in a very specific way. It will check the user’s stored language preference when the app starts, and it will default to the next available language that you suggest if the user’s language preference is not available through your app. As a result, you’ll need to define a fallback language.
To accomplish this, let’s create a LANGUAGE_DETECTOR
configuration object:
const LANGUAGE_DETECTOR = {
type: 'languageDetector',
async: true,
detect: callback => {
AsyncStorage.getItem('user-language', (err, language) => {
// if error fetching stored data or no language was stored
// display errors when in DEV mode as console statements
if (err || !language) {
if (err) {
console.log('Error fetching Languages from asyncstorage ', err);
} else {
console.log('No language is set, choosing English as fallback');
}
const findBestAvailableLanguage =
RNLocalize.findBestAvailableLanguage(LANG_CODES);
callback(findBestAvailableLanguage.languageTag || 'en');
return;
}
callback(language);
});
},
init: () => {},
cacheUserLanguage: language => {
AsyncStorage.setItem('user-language', language);
}
};
Once this is in place, add the configuration for i18n
below. It will start by detecting the language, passing the i18n instance over to react-i18next
, and then it will offer some other language recommends. This option makes i18n
available for all React Native components.
i18n
// detect language
.use(LANGUAGE_DETECTOR)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// set options
.init({
resources: LANGUAGES,
react: {
useSuspense: false
},
interpolation: {
escapeValue: false
}
});
These options may vary depending on your React Native project, so I recommend going through all of the available configuration options for i18n.
Next, let’s import the IMLocalize
file to the App.js
file:
// after other import statements
import './src/constants/IMLocalize';
Now that you have initialized the languages for your React Native app, the next step is to allow the user to select between different languages available within the app.
Inside the LanguageSelector.js
file, start by importing the following libraries:
import React from 'react';
import { View, Text, StyleSheet, Pressable } from 'react-native';
import Ionicons from 'react-native-vector-icons/dist/Ionicons';
import { useTranslation } from 'react-i18next';
The useTranslation
hook will allow you to access the i18n
instance inside the custom component used to change the language.
Next, define the array of LANGUAGES
you plan on supporting:
const LANGUAGES = [
{ code: 'en', label: 'English' },
{ code: 'fr', label: 'Français' }
];
Once we’ve defined our language array, we’ll want to define the function component Selector
. This is what will allow the user to switch between different languages inside the app.
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.
const LANGUAGES = [
{ code: 'en', label: 'English' },
{ code: 'fr', label: 'Français' }
];
const Selector = () => {
const { i18n } = useTranslation();
const selectedLanguageCode = i18n.language;
const setLanguage = code => {
return i18n.changeLanguage(code);
};
return (
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.title}>Select a Language</Text>
<Ionicons color='#444' size={28} name='ios-language-outline' />
</View>
{LANGUAGES.map(language => {
const selectedLanguage = language.code === selectedLanguageCode;
return (
<Pressable
key={language.code}
style={styles.buttonContainer}
disabled={selectedLanguage}
onPress={() => setLanguage(language.code)}
>
<Text
style={[selectedLanguage ? styles.selectedText : styles.text]}
>
{language.label}
</Text>
</Pressable>
);
})}
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingTop: 60,
paddingHorizontal: 16
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
},
title: {
color: '#444',
fontSize: 28,
fontWeight: '600'
},
buttonContainer: {
marginTop: 10
},
text: {
fontSize: 18,
color: '#000',
paddingVertical: 4
},
selectedText: {
fontSize: 18,
fontWeight: '600',
color: 'tomato',
paddingVertical: 4
}
});
export default Selector;
Import the Selector
component inside the SettingsScreen.js
file:
import React from 'react';
import { View } from 'react-native';
import Selector from '../components/LanguageSelector';
export default function SettingsScreen() {
return (
<View style={{ flex: 1, backgroundColor: '#fff' }}>
<Selector />
</View>
);
}
Here is the output in the simulator following this step:
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. Using this function, you can refer to the namespaces defined in the translation files and pass them as arguments to this function.
Let’s see it in action! Starting with the LanguageSelector
component itself, we can see that it has a title of Select a Language
. When defining the translation files, we have already defined its translation in both English and French in their corresponding common.js
files.
The first step to implementing the t
function is to import the useTranslation
hook, however, the LanguageSelector.js
file already has it from the previous section.
We’ll need to modify the following line to get the t
function from the hook inside the Selector
component:
const { t, i18n } = useTranslation();
Next, let’s modify the Text
component contents used to define the title:
<Text style={styles.title}>{t('common:languageSelector')}</Text>
In the emulator below, you’ll see the result of these updates. The default, or initial language, in our case is English, but when you select “Français”, it translates the title on the Settings screen to French.
You can also modify the text strings according to the previously defined namespaces in the translation files.
As an example, the RootNavigator
can be modified as followed:
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Ionicons from 'react-native-vector-icons/dist/Ionicons';
import { useTranslation } from 'react-i18next';
import HomeScreen from '../screens/HomeScreen';
import SettingsScreen from '../screens/SettingsScreen';
const Tab = createBottomTabNavigator();
export default function RootNavigator() {
const { t } = useTranslation();
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = focused ? 'ios-home' : 'ios-home-outline';
} else if (route.name === 'Settings') {
iconName = focused ? 'ios-settings' : 'ios-settings-outline';
}
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray',
headerShown: false
})}
>
<Tab.Screen
name='Home'
component={HomeScreen}
options={{ tabBarLabel: t('navigate:home') }}
/>
<Tab.Screen
name='Settings'
component={SettingsScreen}
options={{ tabBarLabel: t('navigate:settings') }}
/>
</Tab.Navigator>
</NavigationContainer>
);
}
Here is the final output:
This completes our tutorial on how to add multi-language support in a React Native app! If you’d like to add multi-language support for an app that you’re building, Crowdbotics can help. Our expert team of professional developers and engineers are here to ensure that you are able to offer the language options that you and your users need. Get in touch with us today for a detailed quote and timeline!
Source: Crowdbotics