What is internationalization?

Internationalization, commonly abbreviated as i18n due to the number of letters between the initial i and the final n, is the process of developing a mobile application or a website to support multiple languages and cultures depending on the user’s preferred language set on their devices or their physical location.

Why should we care about i18n as developers?

Suppose we either have built an application as a personal project and are seeking out more users, or we are developing a mobile app for a company where their potential final users will be diverse regarding their language and culture. In any of those cases, i18n is something that our application must contemplate.

Current i18n libraries

Nowadays, there are many i18n libraries for React Native apps, such as:

After doing some research to analyze them all, my conclusion was that react-i18next is the best option due to the fact that it has the largest developer community using it and is updated more frequently compared to the other libraries.

How to apply i18n to a React Native app?

What we are going to learn next is how to apply i18n to a React Native app and configure it in a way in which the corresponding language is applied based on the user’s preferred language set on their mobile device.

In order to use the react-i18next library, we just need to install it with the following command:

npm install react-i18next i18next --save

After executing the install command, we should create an i18next folder to hold all the i18n files:

  • react-i18next configuration file
  • translation files

If we want to aim our application at Spanish and English speakers, our i18next folder should look like this:

i18next example folder with english and spanish translations

i18next/index.ts

This is the file where we need to specify which translation files are going to be used, as well as the mechanism to detect the user’s preferred language (again, based on their mobile settings):

import i18n, { ModuleType } from 'i18next'

import { initReactI18next } from 'react-i18next'
import * as RNLocalize from 'react-native-localize'

import english from './translations/english.json'
import spanish from './translations/spanish.json'

/**
* Constants
*/

export const USER_PREFERRED_LANGUAGE = RNLocalize.getLocales()[0].languageCode

const MODULE_TYPE: ModuleType = 'languageDetector'

const LANGUAGE_DETECTOR = {
async: true,
cacheUserLanguage: () => {},
detect: (cb: (code: string) => void) => {
return cb(USER_PREFERRED_LANGUAGE)
},
init: () => {},
type: MODULE_TYPE,
}

const RESOURCES = {
en: english,
es: spanish,
}

/**
* i18next
*/

i18n
.use(LANGUAGE_DETECTOR)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
resources: RESOURCES,
// language to use if translations in user language are not available
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react!!
},
})

export default i18n

As you can see in the previous piece of code, react-native-localize was used to access the user’s language preference set on their mobile phones.

Important note: If the user’s preferred language is different from English and Spanish we can specify which language will be the fallback one with the field fallbackLng.

In the previous example, English was set as the fallback language.

To install react-native-localize, we just needed to run:

npm install react-native-localize --save

Translation files

Inside the /translations folder, JSON files were created for English and Spanish strings.

At this point, it is important to mention that the following taxonomy was thought to be able to create new translation keys easily:

  • Namespace: it refers to a particular screen in the app
  • Key: it refers to the “ID” of a particular text string. Keys are written in kebab-case indicating where the string is located inside a screen
  • Value: the text string itself

Let’s look at an example, imagining we only have the example-screen namespace in our english.json translation file:

"example-screen": {
"empty-screen-message": "Oops! Something went wrong :(.\nTry again {{userLastName}}, please.",
"header-title": "Example screen",
},

As shown above:

  • We have 2 key-value pairs inside the example-screen namespace
  • Keys are written in kebab-case
  • It is possible to use variables to construct text strings. To use them, we have to wrap them between curly brackets {{value}}

Implementation

Following the previous example, let’s see how to use the created keys:

First of all, we are going to call the useTranslation hook provided by react-i18next, and as a param, we should specify which namespace we are going to use in the current component:

import { useTranslation } from 'react-i18next'

const { t } = useTranslation('example-screen')

After that, we use the t function whenever we want to use a translation key:

// Example assigning the translation key to a variable
const emptyScreenMessage = t('empty-screen-message', {
userLastName: 'Valles',
})

// Example assigning the translation key to a "title" prop
<Layout
headerProps={{
title: t('header-title'),
withBack: true,
darkBackground: true,
}} />

You can notice that the variable {{ userLastName }} is passed as the second argument to the t function inside an object.

Take a look at the whole file:

import React from 'react'
import { useTranslation } from 'react-i18next'
import { View } from 'react-native'

import { EmptyScreen } from '#components/EmptyScreen'
import { Layout } from '#components/Layout'
import { C, apply } from '#theme/styles'

/**
* ExampleScreen Component
*/

export const ExampleScreen = () => {
const { t } = useTranslation('example-screen')

const emptyScreenMessage = t('empty-screen-message', {
userLastName: 'Valles',
})

return (
<Layout
headerProps={{
title: t('header-title'),
withBack: true,
darkBackground: true,
}}>
<View style={apply(C.flex, C.itemsCenter, C.justifyCenter)}>
<EmptyScreen text={emptyScreenMessage} />
</View>
</Layout>
)
}

Final result

example screen rendered with english translations applied

The previous screenshot was taken when the user device language was set to English.

To see the Spanish translations in action, first, we will have to create the strings values, so let’s do that in our spanish.json translation file:

"example-screen": {
"empty-screen-message": "¡Uy! Ocurrió un error inesperado :(\nIntenta de nuevo por favor, {{userLastName}}.",
"header-title": "Pantalla de ejemplo",
},

Second, we have to change the mobile language to Spanish, which in iOS can be found at:

Settings — General — Language & Region — Preferred Languages

Finally, after changing the language to Spanish this is the rendered screen:

example screen rendered with spanish translations applied

Dates translation

So far we’ve seen how to translate text strings, but what about dates?

There are different ways to translate dates, but I will explain how to do it by using the date-fns library, which is a popular modern JavaScript date utility library.

First, to install date-fns we have to execute the following command:

npm install date-fns

After the installation part, all we have to do is learn how to use the library to translate dates based on the user’s preferred language.

Looking at the i18n configuration file again, let’s focus on the following exported constant:

export const USER_PREFERRED_LANGUAGE = RNLocalize.getLocales()[0].languageCode

This constant is one that thanks to RNLocalize, has the string indicating which language the user has set on their mobile device. In our example, this string will be “es” for Spanish and “en” for English.

Let’s inspect now the format function provided by date-fns:

format(date: number | Date, format: string, options?: {
locale?: Locale | undefined;
weekStartsOn?: 0 | 1 | 3 | 2 | 4 | 5 | 6 | undefined;
firstWeekContainsDate?: number | undefined;
useAdditionalWeekYearTokens?: boolean | undefined;
useAdditionalDayOfYearTokens?: boolean | undefined;
} | undefined): string

As you can see above, the format function should receive the date to transform, the format type that we want to be applied to the date, and as a third optional parameter, an options object where the first key is the one that will help us, the locale key.

For English (US) and Spanish date-fns provides us with the correct Locale objects, which can be imported from the library:

import { es, enUS } from 'date-fns/locale'

So how can we apply the corresponding Locale based on the user’s preferred language to a date? Pretty easy, one solution would be:

import { es, enUS } from 'date-fns/locale'

const LOCALE = USER_PREFERRED_LANGUAGE === 'es' ? 'es' : 'en'

const LOCALES = {
es,
en: enUS,
}

const date = format(new Date(), 'PPPP', { locale: LOCALES[LOCALE] })

With the LOCALES object, we will get the es or enUS date-fns Locale based on the USER_PREFERRED_LANGUAGE value, and then use that Locale object to pass it as a parameter to the format function.

Important note: If you take a closer look at the second line of code maybe you will be asking yourself why we need that conditional to assign a value to the locale constant. As we said before, English is our default value when the user’s language is different from Spanish and English, so this is a way to set English as the fallback language for the formatted date.

Regarding the format type applied, I used ‘PPPP’ just for demonstration purposes. Here you can find the complete list of available format types.

Looking at the whole example code for an example screen:

import { format } from 'date-fns'
import { es, enUS } from 'date-fns/locale'

import React from 'react'
import { useTranslation } from 'react-i18next'
import { View } from 'react-native'

import { Layout } from '#components/Layout'
import { H1 } from '#components/typography'
import { USER_PREFERRED_LANGUAGE } from '#helpers/i18next'
import { C, apply } from '#theme/styles'

/**
* Constants
*/

const LOCALE = USER_PREFERRED_LANGUAGE === 'es' ? 'es' : 'en'

const LOCALES = {
es,
en: enUS,
}

/**
* ExampleScreen Component
*/

export const ExampleScreen = () => {
const { t } = useTranslation('example-screen')

const date = format(new Date(), 'PPPP', { locale: LOCALES[LOCALE] })

return (
<Layout
headerProps={{
title: t('header-title'),
withBack: true,
darkBackground: true,
}}>
<View style={apply(C.flex, C.itemsCenter, C.justifyCenter)}>
<H1>{date}</H1>
</View>
</Layout>
)
}

Below are the rendered screens in English and Spanish based on the previous code:

English example screen

example screen rendered with a date translated to english

Spanish example screen

example screen rendered with a date translated to spanish

Conclusions

As shown in the present article, configuring a React Native app to support multiple languages is not difficult at all, and it’s a super important feature if you want your app to be used by people from all over the world.

I hope you’ve learned what i18n is and you start configuring your RNative apps soon to support different languages 🚀.

Thanks a lot for reading!

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job

--

--