In this tutorial, we will learn how to implement React Native localization and internationalization.
Internationalization (I18n) makes it simpler to translate our React Native app into multiple languages. Once internationalization is performed, we will see how to perform React Native localization by supporting English and Russian languages. Finally, we will understand that localization is not just about translating to another language, but keeping it’s semantics intact by properly performing interpolation and pluralization.
The source code for the sample app we are going to build can be found on GitHub.
This post was initially penned by Akshay Kadam.
One World, Dozens of Languages
When you create an app, the primary language you choose is usually English. But here’s the thing:
Out of the world’s approximately 7.5 billion inhabitants, 1.5 billion speak English — that’s 20% of the Earth’s population. However, most of those people aren’t native English speakers. About 360 million people speak English as their first language.
So, only 360 million people out of 7.5 billion people are fluent in English. The rest of them use English as their second or third language. The market for other languages combined is much larger than that of English alone. Yet we only support English.
It’s fine to test the market with English when the majority of your target audience is English-speaking. However, when your target audience consists of other languages then you have to translate your app. As your app grows, translating becomes more and more important to support the local audience.
It is vital for people to use applications of all kinds without having to learn yet another language. We have to give up the idea of making people learn English in advance just to use a computer and have access to all of its programs. As the barrier to entry to make an app or starting a business is getting increasingly low, the fastest way to diversify yourself is by providing translation in the language of your target audience.
Why Should You Translate Your App?
According to CSA Research,
- 50% of all queries on Google are in languages other than English.
- 7 of the 10 top markets by iOS downloads and 9 of the 10 top markets by Google Play downloads are non-native English markets.
- 78% of online shoppers are more likely to make a purchase on online stores that are localized.
- About 72.1% of internet users prefer to dwell on websites translated into their native language.
- In Sweden – which has one of the world’s best non-native English speakers – over 80% of online shoppers prefer to make a purchase in their own language.
- Even among people with high proficiency in English, 60.6% prefer to browse the World Wide Web in their native language.
- Around 90% of online shoppers choose their native language when it’s available.
- Nearly 75% of Internet users prefer to read product information in their native language.
Basically, you can’t sell in English to your non-English speakers. If they can’t read, they won’t buy. You need to translate your app into their local language in order for them to understand and then buy your product.
Distomo found that localized apps generated up to 128% more ROI than non-localized apps.
Differences Between Internationalization and Localization
Definition of Internationalization (I18n)
I believe W3C said it best,
Internationalization is the design and development of a product, application or document content that enables easy localization for target audiences that vary in culture, region, or language.
In simpler words, internationalization is the process of designing a software application to adapt it to various languages and regions without engineering changes, thus enabling localization. It makes the process of implementing localization much simpler.
Fun fact, i18n is a numeronym for “internationalization” as there are exactly 18 characters between i
and n
.
What Tasks Does I18n Involve?
- Elimination of hard-coded text (strings) in the code.
- Colocating hard-coded text in a single place for easier editing.
- Support for right-to-left (RTL) languages, such as Urdu, Arabic, Hebrew, and Persian.
- Having enough breathing space as phrases in different languages have varying lengths.
- Unicode compliance.
Benefits of I18n
- Single source of truth.
- Reduced time to localize software in other languages.
- Maintenance becomes easy for future iterations of the software.
- Customer satisfaction.
Definition of Localization (L10n)
W3C provides a great definition for localization:
Localization refers to the adaptation of a product, application or document content to meet the language, cultural and other requirements of a specific target market (a locale).”
In simpler words, localization is the process of adapting internationalized software for a specific region or language by translating text and adding locale-specific components. Therefore, in order to implement localization, we must first implement internationalization.
L10n is also a numeronym for “localization” as there are exactly 10 characters between l
and n
.
What Tasks Does L10n Involve?
- Support for appropriate date format, e.g. French locale uses
dd/mm/yyyy
convention whereas Canadian locale usesyyyy-mm-dd
convention. - Support for appropriate time format, e.g. German, French, Romanian use 24-hour clock format even while speaking whereas people from some other countries use 12-hour clock format.
- Support for appropriate calendar format, e.g, Wednesday, 20 November 2019 or 20/11/2019.
- Optimizing graphics and messaging to meet the tastes and habits of the market.
If L10n is not done right, it can be an epic failure and sometimes hilarious as well.
Benefits of l10n
- Increase market share.
- Increase revenue.
- Gain a competitive advantage.
- Build customer rapport.
- Strengthen global presence.
- More return on investment (ROI).
Prerequisites
Expected Knowledge
For this tutorial, you need a basic knowledge of React Native and its concepts like state, props, hooks.
Additionally, you’ll require a basic knowledge of how to setup a simple bottom tab navigation using React Navigation.
This tutorial also assumes that you know how to use UI libraries like React Native Elements for faster prototyping.
Installation
Throughout the course of this tutorial, we’ll be using Yarn. If you don’t have yarn
on your PC, install it from the official website.
Also, make sure you’ve already installed react-native-cli
globally on your computer:
$ yarn global add react-native-cli
To make sure we’re on the same page, these are the versions used in this tutorial:
- Node 12.12.0
- NPM 6.11.3
- Yarn 1.19.1
- react-native-cli 2.0.1
- react-native 0.61.4
Creating Application
We are going to build a simple Shopping Cart app in order to demonstrate React Native localization and internationalization.
Go ahead and clone the starter kit from GitHub. Then switch to the starter
branch:
$ git clone git@github.com:deadcoder0904/TranslateRNApp.git
$ cd TranslateRNApp
$ git checkout starter
To run the project, type:
$ yarn start
In another terminal, while keeping the one above running, type the following:
$ yarn ios
(for iOS)
$ yarn android
(for Android)
This will automatically run the iOS Simulator even if it’s not yet opened.
Note that the emulator must be already started before running the above command. Otherwise it will throw an error in the terminal.
Entry Point
Let’s take a look at the initial code. Our main entry point is index.js
:
import {AppRegistry} from 'react-native'; import App from './App'; import {name as appName} from './app.json'; AppRegistry.registerComponent(appName, () => App);
index.js
just includes App
component from App.js
.
Implementing Routes
Our App.js
looks like:
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; import {NavigationNativeContainer} from '@react-navigation/native'; import React from 'react'; import {Platform, StatusBar} from 'react-native'; import {ThemeProvider} from 'react-native-elements'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import Ionicons from 'react-native-vector-icons/Ionicons'; import {Home} from './screens/Home'; import {Settings} from './screens/Settings'; const Tab = createBottomTabNavigator(); const isIOS = Platform.OS === 'ios'; const App = () => ( <> <NavigationNativeContainer> <StatusBar barStyle="dark-content" /> <SafeAreaProvider> <ThemeProvider> <Tab.Navigator screenOptions={({route}) => ({ tabBarIcon: ({focused, color, size}) => { let iconName; if (route.name === 'Home') { iconName = `${isIOS ? 'ios' : 'md'}-information-circle${ focused ? '' : '-outline' }`; } else if (route.name === 'Settings') { iconName = `${isIOS ? 'ios' : 'md'}-options`; } // You can return any component that you like here! return <Ionicons name={iconName} size={size} color={color} />; }, })} tabBarOptions={{ activeTintColor: 'tomato', inactiveTintColor: 'gray', }}> <Tab.Screen name="Home" component={Home} /> <Tab.Screen name="Settings" component={Settings} /> </Tab.Navigator> </ThemeProvider> </SafeAreaProvider> </NavigationNativeContainer> </> ); export default App;
We use @react-navigation/bottom-tabs
to add bottom tabs navigation in react-navigation
to switch between screens.
We use react-native-safe-area-context
to ensure our app remains confined between device screens on both Android and iOS.
The difference between SafeAreaProvider
and react-native
‘s SafeAreaView
is that SafeAreaView
only works for iOS while SafeAreaProvider
works for both Android and iOS.
We then use ThemeProvider
from react-native-elements
to make sure default theme is applied from react-native-elements
. react-native-elements
allows us to quickly create beautiful components with an intuitive API without having to write too many styles.
Later, we use Tab.Navigator
with the Tab.Screen
inside. The createBottomTabNavigator()
function from @react-navigation/bottom-tabs
returns Tab.Navigator
and Tab.Screen
.
Tab.Navigator
keeps track of which route we are currently at. Tab.Screen
focuses on which component to display when a specific route is navigated. For example, when a route with the name Home
is selected, it shows the Home
component. When a route with the name Settings
is selected, it shows the Settings
component.
In Tab.Navigator
, we have two props, namely, screenOptions
and tabBarOptions
:
screenOptions
displays an appropriate icon depending on the platform. If the platform isiOS
then it adds the prefixios
to it. When the platform isAndroid
then the prefix ismd
. The icons are used from theIonicons
component which is imported fromreact-native-vector-icons
.tabBarOptions
displaysgray
color when a particular bottom tab is inactive and it displays atomato
color when a particular bottom tab is active.
So, to summarize, we have two bottom tabs named Home
and Settings
which switch between Home
component and Settings
component respectively.
Implementation of the Home Screen
Our Home
component looks like:
import React, {useState} from 'react'; import {ScrollView, StyleSheet, View} from 'react-native'; import {Text} from 'react-native-elements'; import {useSafeArea} from 'react-native-safe-area-context'; import {Tile} from '../components/Tile'; const fruits = [ { name: 'Apple', price: '$3', pic: require('../assets/apple.png'), }, { name: 'Banana', price: '$2', pic: require('../assets/banana.png'), }, { name: 'Watermelon', price: '$5', pic: require('../assets/watermelon.png'), }, ]; export const Home = () => { const [total, changeTotal] = useState(0); const insets = useSafeArea(); const addToTotal = price => { changeTotal(total + price); }; const removeFromTotal = price => { changeTotal(total - price); }; return ( <ScrollView> <View style={[ styles.container, {paddingTop: insets.top, paddingBottom: insets.bottom}, ]}> <Text h1 h1Style={styles.grocery}> Grocery Shop </Text> <Text style={styles.date}>Today's date: 21/11/2019</Text> {fruits.map(fruit => { return ( <React.Fragment key={fruit.name}> <Tile fruit={fruit} addToTotal={addToTotal} removeFromTotal={removeFromTotal} /> </React.Fragment> ); })} <Text h3 h3Style={styles.total}> Total Sum: ${total} </Text> </View> </ScrollView> ); }; const styles = StyleSheet.create({ container: { marginTop: 10, }, grocery: { textAlign: 'center', marginBottom: 10, }, date: { textAlign: 'center', marginBottom: 20, fontSize: 20, }, total: { marginTop: 30, textAlign: 'center', color: 'red', }, });
And here’s our Tile
component:
import React, {useState} from 'react'; import {Dimensions, StyleSheet, View} from 'react-native'; import {Icon, Image, Text} from 'react-native-elements'; const {width} = Dimensions.get('window'); export const Tile = ({fruit, addToTotal, removeFromTotal}) => { const [cart, changeCart] = useState(0); const {name, pic, price} = fruit; const fruitPrice = +fruit.price.substring(1); const addToCart = () => { const noOfItems = cart + 1; changeCart(noOfItems); addToTotal(fruitPrice); }; const removeFromCart = () => { if (cart > 0) { const noOfItems = cart - 1; changeCart(noOfItems); removeFromTotal(fruitPrice); } }; return ( <> <Image resizeMode="contain" source={pic} style={{width, height: width * 0.8}} /> <View style={styles.flex}> <Icon name="pluscircleo" type="antdesign" onPress={addToCart} /> <Text h4 h4Style={styles.name}> {name} ({price}) </Text> <Icon name="minuscircleo" type="antdesign" onPress={removeFromCart} /> </View> <Text style={styles.cart}>Added {cart} items</Text> </> ); }; const styles = StyleSheet.create({ flex: { display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }, name: { margin: 20, }, cart: { textAlign: 'center', }, });
The Home
component should now look like this:
Implementation of the Settings Screen
Now lets see how our Settings
component looks like:
import React, {useState} from 'react'; import {StyleSheet, View} from 'react-native'; import {ListItem, Text} from 'react-native-elements'; import {useSafeArea} from 'react-native-safe-area-context'; const langs = ['en', 'ru']; export const Settings = () => { const [lang, changeLang] = useState('en'); const insets = useSafeArea(); return ( <View style={[styles.container, {paddingTop: insets.top}]}> <Text h4 h4Style={styles.language}> Change Language </Text> {langs.map((currentLang, i) => ( <ListItem key={i} title={currentLang} bottomDivider checkmark={currentLang === lang} onPress={() => changeLang(currentLang)} /> ))} </View> ); }; const styles = StyleSheet.create({ language: { paddingTop: 10, textAlign: 'center', }, });
This screen contains only a language switcher allowing us to choose the desired language. The selected language has a checkmark to the right. Notice, this doesn’t have any effect as we are yet to implement localization.
It currently displays:
Implementing React Native Internationalization
Now we are going to tweak our Translation app to add support for I18n.
Go ahead and create a localization/
folder. Inside it, create en.json
and ru.json
files.
Creating Translation Files
en.json
will contain our English translations. Go ahead and paste the following:
{ "shop.title": "Grocery Shop", "date.title": "Today's Date", "date.format": "21/11/2019", "app.currency": "$", "fruit.apple": "Apple", "fruit.apple.price.value": 3, "fruit.banana": "Banana", "fruit.banana.price.value": 2, "fruit.watermelon": "Watermelon", "fruit.watermelon.price.value": 5, "added.items.one": "Added {no} item", "added.items.endingWithZero": "Added {no} items", "added.items.endingWithOne": "Added {no} item", "added.items.endingWithTwoToFour": "Added {no} items", "added.items.endingWithOther": "Added {no} items", "cart.total.title": "Total Sum", "cart.total.value.currencyStart": "{currencyStart}{value}", "cart.total.value.currencyEnd": "{value}{currencyEnd}", "settings.change_language": "Change Language" }
ru.json
is going to store Russian translations. Its contents should be:
{ "shop.title": "Продуктовый магазин", "date.title": "Сегодняшняя дата", "date.format": "21.11.19", "app.currency": "₽", "fruit.apple": "яблоко", "fruit.apple.price.value": 10, "fruit.banana": "Банан", "fruit.banana.price.value": 7, "fruit.watermelon": "Арбуз", "fruit.watermelon.price.value": 25, "cart.total": "Итого", "added.items.one": "добавлено {no} товар", "added.items.endingWithZero": "добавлено {no} товаров", "added.items.endingWithOne": "добавлено {no} товар", "added.items.endingWithTwoToFour": "добавлено {no} товара", "added.items.endingWithOther": "добавлено {no} товара", "cart.total.title": "Итого", "cart.total.value.currencyStart": "{currencyStart}{value}", "cart.total.value.currencyEnd": "{value}{currencyEnd}", "settings.change_language": "Изменить язык" }
We have created translation files in JSON format containing keys with the corresponding values for each language our app is going to support. The {}
syntax in the values denote interpolation. Variables with that name will be interpolated with their value.
Before proceeding, make sure to install @react-native-community/async-storage, react-native-localization and react-native-localize by using the following command:
$ yarn add @react-native-community/async-storage react-native-localization react-native-localize
React Native Localization
Next let’s see how to introduce React Native localization. Create Translations.js
file in the components/
folder and paste the following code inside:
import AsyncStorage from '@react-native-community/async-storage'; // 1 import React, {createContext, useState} from 'react'; import LocalizedStrings from 'react-native-localization'; // 2 import * as RNLocalize from 'react-native-localize'; // 3 import en from '../localization/en.json'; import ru from '../localization/ru.json'; const DEFAULT_LANGUAGE = 'en'; const APP_LANGUAGE = 'appLanguage'; const languages = {en, ru}; const translations = new LocalizedStrings(languages); // 4 export const LocalizationContext = createContext({ // 5 translations, setAppLanguage: () => {}, // 6 appLanguage: DEFAULT_LANGUAGE, // 7 initializeAppLanguage: () => {}, // 8 }); export const LocalizationProvider = ({children}) => { // 9 const [appLanguage, setAppLanguage] = useState(DEFAULT_LANGUAGE); // 11 const setLanguage = language => { translations.setLanguage(language); setAppLanguage(language); AsyncStorage.setItem(APP_LANGUAGE, language); }; // 12 const initializeAppLanguage = async () => { const currentLanguage = await AsyncStorage.getItem(APP_LANGUAGE); if (currentLanguage) { setLanguage(currentLanguage); } else { let localeCode = DEFAULT_LANGUAGE; const supportedLocaleCodes = translations.getAvailableLanguages(); const phoneLocaleCodes = RNLocalize.getLocales().map( locale => locale.languageCode, ); phoneLocaleCodes.some(code => { if (supportedLocaleCodes.includes(code)) { localeCode = code; return true; } }); setLanguage(localeCode); } }; return ( <LocalizationContext.Provider value={{ translations, setAppLanguage: setLanguage, // 10 appLanguage, initializeAppLanguage, }}> {children} </LocalizationContext.Provider> ); };
What This Code Means
- We use
@react-native-community/async-storage
to store our selected language in the local database. This way, the next time we open the app, we use that language automatically without having to select it again. - We use
react-native-localization
to internationalize React Native application. This library uses a native library to get the current interface language. Then it loads and displays the strings matching the current interface locale or the default language if a specific localization can’t be found. react-native-localize
provides a toolbox for React Native app localization. It lets us find available locales, time, country, and calendar. It even searches for the best available languages from our device so that we can use it to apply localization.translations
contain all our localized translations depending on the language specified. We pass it throughLocalizedStrings
method fromreact-native-localization
.- We create a constant named
LocalizationContext
using React Native’screateContext
API. Here we pass intranslations
,setAppLanguage
,appLanguage
, andinitializeAppLanguage
. setAppLanguage
is an empty function at the moment.appLanguage
has a default value ofen
(English locale).initializeAppLanguage
is also an empty function at the moment.- In our
LocalizationProvider
method, we takechildren
as a prop and return them while wrapping withLocalizationContext.Provider
. Inside this function, we fill in the empty functions, i.e,setAppLanguage
andinitializeAppLanguage
. setAppLanguage
maps to thesetLanguage
in thevalue
prop ofLocalizationContext.Provider
.setLanguage
takes in alanguage
to be set as the primary language. It then calls thesetLanguage
API ontranslations
to get the translations of the specified language. It then sets the local stateappLanguage
usingReact
‘suseState
hook to pass it along withLocalizationContext.Provider
‘svalue
. Finally, it saves the primary language to the database usingAsyncStorage.setItem()
. This way, the next time our users open the app, the previously selected language is chosen automatically.initializeAppLanguage
firstly gets thecurrentLanguage
usingAsyncStorage.getItem()
. It then checks ifcurrentLanguage
is set. If yes — it callssetLanguage
. Otherwise, it matches the language that has available translations with the nearest language that is set on our device and then it callssetLanguage
.
Finally, we export both LocalizationContext
and LocalizationProvider
so we can import them throughout our app.
Importing LocalizationProvider
Open up App.js
and import LocalizationProvider
from ./components/Translations
and wrap ThemeProvider
with LocalizationProvider
as follows:
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; import {NavigationNativeContainer} from '@react-navigation/native'; import React from 'react'; import {Platform, StatusBar} from 'react-native'; import {ThemeProvider} from 'react-native-elements'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import Ionicons from 'react-native-vector-icons/Ionicons'; import {LocalizationProvider} from './components/Translations'; import {Home} from './screens/Home'; import {Settings} from './screens/Settings'; const Tab = createBottomTabNavigator(); const isIOS = Platform.OS === 'ios'; const App = () => ( <> <NavigationNativeContainer> <StatusBar barStyle="dark-content" /> <SafeAreaProvider> <LocalizationProvider> <ThemeProvider> <Tab.Navigator screenOptions={({route}) => ({ tabBarIcon: ({focused, color, size}) => { let iconName; if (route.name === 'Home') { iconName = `${isIOS ? 'ios' : 'md'}-information-circle${ focused ? '' : '-outline' }`; } else if (route.name === 'Settings') { iconName = `${isIOS ? 'ios' : 'md'}-options`; } // You can return any component that you like here! return <Ionicons name={iconName} size={size} color={color} />; }, })} tabBarOptions={{ activeTintColor: 'tomato', inactiveTintColor: 'gray', }}> <Tab.Screen name="Home" component={Home} /> <Tab.Screen name="Settings" component={Settings} /> </Tab.Navigator> </ThemeProvider> </LocalizationProvider> </SafeAreaProvider> </NavigationNativeContainer> </> ); export default App;
This makes sure everything inside the LocalizationProvider
component has access to the translations so that we can use them as well as change the language.
Localization of the Settings Screen
Now open up Settings.js
and paste the following:
import React, {useContext} from 'react'; import {StyleSheet, View} from 'react-native'; import {ListItem, Text} from 'react-native-elements'; import {useSafeArea} from 'react-native-safe-area-context'; import {LocalizationContext} from '../components/Translations'; export const Settings = () => { const insets = useSafeArea(); const { translations, appLanguage, setAppLanguage, initializeAppLanguage, } = useContext(LocalizationContext); // 1 initializeAppLanguage(); // 2 return ( <View style={[styles.container, {paddingTop: insets.top}]}> <Text h4 h4Style={styles.language}> {translations['settings.change_language']} {/* 3 */} </Text> {translations.getAvailableLanguages().map((currentLang, i) => ( {/* 4 */} <ListItem key={i} title={currentLang} bottomDivider checkmark={appLanguage === currentLang} onPress={() => { setAppLanguage(currentLang); }} /> ))} </View> ); }; const styles = StyleSheet.create({ language: { paddingTop: 10, textAlign: 'center', }, });
- We import
LocalizationContext
from./components/Translations
. - We then call
React
‘suseContext
method to get backtranslations
,appLanguage
,setAppLanguage
, andinitializeAppLanguage
. - Then, we call
initializeAppLanguage
. This makes sure our initial language is set. If we have never opened the app before or have not selected a different language before, then the English (en
) language gets set by default. If we have already selected a different language, that language will be selected instead of English. - We then employ
{translations['settings.change_language']}
instead ofChange Language
insideText
. The keysettings.change_language
comes from our translation files key. - Next, we utilize
translations.getAvailableLanguages()
to get all the available languages whose translation files we have specified. So in our case, we geten
andru
. We display them in a loop by usingListItem
fromreact-native-elements
. Lastly, we setcheckmark
totrue
whenappLanguage
fromContext
is equal tocurrentLang
.
Localization of the Home Screen
Now go ahead and open up Home.js
. Paste the following contents inside:
import React, {useContext, useState} from 'react'; import {ScrollView, StyleSheet, View} from 'react-native'; import {Text} from 'react-native-elements'; import {useSafeArea} from 'react-native-safe-area-context'; import {Tile} from '../components/Tile'; import {LocalizationContext} from '../components/Translations'; export const Home = () => { const {translations, initializeAppLanguage} = useContext(LocalizationContext); const [total, changeTotal] = useState(0); const insets = useSafeArea(); initializeAppLanguage(); // 1 // 2 const fruits = [ { // 3 name: translations['fruit.apple'], price: translations['app.currency'] + translations['fruit.apple.price.value'], pic: require('../assets/apple.png'), }, { name: translations['fruit.banana'], price: translations['app.currency'] + translations['fruit.banana.price.value'], pic: require('../assets/banana.png'), }, { name: translations['fruit.watermelon'], price: translations['app.currency'] + translations['fruit.watermelon.price.value'], pic: require('../assets/watermelon.png'), }, ]; // 4 const addToTotal = price => { changeTotal(total + price); }; const removeFromTotal = price => { changeTotal(total - price); }; return ( <ScrollView> <View style={[ styles.container, {paddingTop: insets.top, paddingBottom: insets.bottom}, ]}> {/* 5 */} <Text h1 h1Style={styles.grocery}> {translations['shop.title']} </Text> <Text style={styles.date}> {translations['date.title']}: {translations['date.format']} </Text> {fruits.map(fruit => { return ( <React.Fragment key={fruit.name}> <Tile fruit={fruit} addToTotal={addToTotal} removeFromTotal={removeFromTotal} /> </React.Fragment> ); })} <Text h3 h3Style={styles.total}> {/* 6 */} {translations['cart.total.title']}: {translations.formatString( translations['cart.total.value.currencyStart'], { currencyStart: translations['app.currency'], value: total, }, )} </Text> </View> </ScrollView> ); }; const styles = StyleSheet.create({ container: { marginTop: 10, }, grocery: { textAlign: 'center', marginBottom: 10, }, date: { textAlign: 'center', marginBottom: 20, fontSize: 20, }, total: { marginTop: 30, textAlign: 'center', color: 'red', }, });
- Firstly, we initialize app language by calling
initializeAppLanguage
. - We move our
fruits
array inside the component since we need to usetranslations
. - Then, enable our
fruits
array to usetranslations
. Forapple
, we setname
totranslations['fruit.apple']
. We concatenatetranslations['app.currency']
andtranslations['fruit.apple.price.value']
forprice
. Also we keep thepic
same as before. The reason we separatecurrency
andvalue
is because different locales use different currencies. We perform the same operations forbanana
andwatermelon
. - Lastly, declare
{translations['shop.title']}
,{translations['date.title']}
, and{translations['date.format']}
. Later, we loop over ourfruits
and pass eachfruit
to our custom madeTile
component withaddToTotal
andremoveFromTotal
.
The Tile Screen
Finally, to stitch it all together we need to internationalize our final file Tile.js
:
import React, {useContext, useState} from 'react'; import {Dimensions, StyleSheet, View} from 'react-native'; import {Icon, Image, Text} from 'react-native-elements'; import {LocalizationContext} from './Translations'; const {width} = Dimensions.get('window'); const translateNumber = (translations, cart) => { const key = 'added.items.'; if (cart === 1) { return translations.formatString(translations[key + 'one'], { no: cart, }); } if ( (cart % 10 === 0 || cart % 10 === 5 || cart % 10 === 6 || cart % 10 === 7 || cart % 10 === 8 || cart % 10 === 9) && (cart % 100 === 11 || cart % 100 === 12 || cart % 100 === 13 || cart % 100 === 14) ) { return translations.formatString(translations[key + 'endingWithZero'], { no: cart, }); } if (cart % 10 === 1 && cart % 100 !== 11) { return translations.formatString(translations[key + 'endingWithOne'], { no: cart, }); } if ( (cart % 10 === 2 || cart % 10 === 3 || cart % 10 === 4) && (cart % 100 !== 12 || cart % 100 !== 13 || cart % 100 !== 14) ) { return translations.formatString( translations[key + 'endingWithTwoToFour'], { no: cart, }, ); } return translations.formatString(translations[key + 'endingWithOther'], { no: cart, }); }; export const Tile = ({fruit, addToTotal, removeFromTotal}) => { const {translations} = useContext(LocalizationContext); const [cart, changeCart] = useState(0); // 1 const {name, pic, price} = fruit; const fruitPrice = +fruit.price.substring(1); // 2 const addToCart = () => { changeCart(cart + 1); addToTotal(fruitPrice); }; const removeFromCart = () => { if (cart > 0) { changeCart(cart - 1); removeFromTotal(fruitPrice); } }; return ( <> <Image resizeMode="contain" source={pic} style={{width, height: width * 0.8}} /> <View style={styles.flex}> {/* 3 */} <Icon name="pluscircleo" type="antdesign" onPress={addToCart} /> <Text h4 h4Style={styles.name}> {name} ({price}) </Text> <Icon name="minuscircleo" type="antdesign" onPress={removeFromCart} /> </View> {/* 4 */} <Text style={styles.cart}>{translateNumber(translations, cart)}</Text> </> ); }; const styles = StyleSheet.create({ flex: { display: 'flex', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }, name: { margin: 20, }, cart: { textAlign: 'center', }, });
- Here, we use a local state
cart
to keep track of the items number for each fruit usingReact
‘suseState
hook. Then we destructurename
,pic
, andprice
fromfruit
. Next, we getfruitPrice
by removing the currency from the start usingsubstring()
. Lastly convertstring
tonumber
using+
. - We have two functions, namely
addToCart
andremoveFromCart
. InaddToCart
, we first increase the value of thecart
variable (stored in the local state) by callingchangeCart
. Then we calladdToTotal
which delegates toaddToTotal
(inside the parent component). InremoveFromCart
, we first check if the items in thecart
are greater than zero. If yes, decrease our local state by callingchangeCart
withcart - 1
. Finally, we callremoveFromTotal
which delegates to theremoveFromTotal
function. - Later, we display
+
icon and along with the fruit name and its price. Also display the-
icon. Tapping the+
icon calls theaddToCart
function. Tapping the-
icon calls theremoveFromCart
function. - Finally, we call the
translateNumber
function. It accepts thetranslations
andcart
arguments while returning the appropriate translations. This process is known as pluralization. The conditions here are tricky since Russian language has quite complex pluralization rules which you may find at Unicode CLDR website. This process gets easier if you are using a localization service like Lokalise.
Now Russian Translations should appear as well when you toggle the language.
Make Translations Easy With Lokalise
So, we have learned how to add support for React Native localization. However, it is quite painful to support different languages while working on a big project. Localization is hard but it shouldn’t be. That’s where Lokalise comes in.
Lokalise provides a translation editor that is collaborative so different translators can collaborate on a single-project in real-time without having to worry about keep translations file in sync.
It also allows us to preview translations in real-time without having to wait for the next deployment.
Moreover, Lokalise has nice tools allowing to integrate with different services like GitHub, Slack, JIRA, Sketch, and others.
Setting up Lokalise for React Native is really easy. Follow these steps:
- To get started, grab your free trial.
- Download and install Lokalise CLIv2 that will be used to upload and download translation files.
- Open your personal profile page, navigate to the “API tokens” section, and generate a read/write token.
- Create a new project, give it some name, and set English as a base language.
- On the project page click the “More” button and choose “Settings”. On this page, you should see the project ID.
- To upload your translations files, run
lokalise2 file upload --token <token> --project-id <project_id> --lang-iso en --file localization/en.json
while providing your generated token and project ID. Note that on Windows you may also need to provide the full path to the file. This should upload English translation to Lokalise. Run the same command for the Russian locale (while providing the proper--lang-iso
and--file
options). - Navigate back to the project overview page. You should see all your translation keys and values there. Of course, it is possible to edit, delete them, as well as add new ones. Here you may also filter the keys and, for example, find the untranslated ones which is really convenient.
- After you are done editing the translations, download them back by running
lokalise2 file download --token <token> --project-id <project_id> --bundle_structure %LANG_ISO%.json --unzip_to ~/TranslateRNApp/localization/
.
That’s it. Check out the official documentation for the command line interface to learn about other commands and options. As you see, Lokalise reduces the number of complex tasks and make translation a piece of cake.
Conclusion
Localization is tricky to get right. But once it’s done, your business, reach and sales will increase exponentially.
Further reading
- Localization and internationalization
- React i18n: A step-by-step guide to React-intl
- I18n and l10n: List of tutorial developers
- Translation and localization: What’s the difference?
Make translation easy with Lokalise
Grab a FREE Lokalise trial and collaborate in real-time without having to worry about keeping translation files in syncStart now