React App Localization: Advanced Guide

Localization
Ninad Pathak
23 Aug 2024

17 min. read

Contents

I was sipping on my latte, looking at my app analytics. 

My app had just gone live, and I'd shared it with a few friends. 

One of them, from Spain, sent me this message:

💬 "The app looks great, but I can't completely understand it!" 

English is her second language, so that made sense. And that's when it hit me—I made the app only for English speakers.

So, I jumped into figuring out how to localize my React app in the next version. 

If you're reading this, chances are you've had a similar situation. Maybe you're just starting out with React Native, or maybe you're looking to take your app global. 

Either way, you're in the right place. I'll walk you through everything I learned about localizing React Native apps, from the basics to advanced techniques, and more.

Let's jump right in.

Why React localization matters

Only about 18% of the world, or 1.4 billion people, speak English. That may seem like a big number, but most of these people are never going to use your app. And we're still leaving out 82% of the world.  the most spoken languages worldwide statistics

Source: Statista

So, if you're planning on reaching wider audiences, localization cannot be an afterthought. It has to be baked into the app from the beginning. 

React localization is more than translating your app's text (though that's a big part of it). It's about making your app feel native to users around the world. This means adapting to different languages, yes, but also to different cultural norms, date formats, number systems, and even reading directions.

And there are a lot of benefits to localizing. Here's why you should consider localizing your apps:

  • Increased downloads: More people will download your app if it's available in their local language in the app store.
  • Better user retention: People are more likely to keep using an app that is easy to understand and feels like a familiar friend.
  • Higher ratings and reviews: Apps that speak and understand the users' language and society tend to get higher scores and reviews.
  • Competitive advantage: There is less competition in many non-English-speaking regions and you can stand out by localizing your content.
  • Compliance with local regulations: In some countries, digital goods must be offered in the local language.

At this point, you’re likely thinking, "This must take a lot of work." And you are right—localization does require continuous effort.

But with the right tools and mindset, it's not as difficult as it appears. And the payoff? It can be massive.

What localization options can you use for React Native apps

Before jumping into the code, let's understand two key terms:

  • React internationalization (i18n) involves designing your app to support various languages and dialects. This means separating translatable content and preparing the app for bidirectional text.
  • React localization (l10n) takes the i18n process further by creating versions of your project for different regions. It involves translating content and adapting it to local customs and preferences.

You have a few solid options to choose from when it comes to React Native localization as well as internationalization. Let's take a quick look at some popular choices:

  • react-native-localize: This is a lightweight, core module that gives you access to the device's locale information. It's great for finding out what language and area the user prefers, but it doesn't do the changes for you.
  • i18next with react-i18next: We're using this exact combo throughout the article as it's simple and comprehensive enough to get all the localization done. It also includes pluralization, formatting, and interpolation within the module. 
  • react-intl: While not a comprehensive localization library, react-intl helps you with date and number formatting and can be quite useful when combined with other comprehensive localization and internationalization libraries. 
  • Translation management platform: If you have a large, complex app with many pages and many translators working on it, using just the React libraries will be cumbersome and unorganized. You need a platform to help you organize translations, collaborate across teams working on the translations, and also track progress. 

Getting started with React Native localization

Now, let's get down to business.

I found a lot of libraries to make React apps work in different languages. But react-native-localize and i18next React tools were the most complete and simple to use among the ones I tried. Let's use them.

Step 1: Set up your React Native project

First things first, let's create a new React Native project:

npx react-native init LocalizationDemo
cd LocalizationDemo

Step 2: Install the required libraries

Now, let's install the necessary packages for localization:

npm install react-native-i18n i18next react-i18next i18next-browser-languagedetector i18next-http-backend

You can choose to exclude packages that you do not need from the ones listed above. If you just want the bare minimum set of packages, you could work with the below command:

npm install i18next react-i18next

Step 3: Setting up i18next

Now, let's set up our localization structure. I like to create a localization folder in my project root to keep everything organized. 

Here, create a file called i18n.js to set up our localization config.

Your i18n.js file might look like:

import i18n from  'i18next';
import { initReactI18next } from  'react-i18next';
import LanguageDetector from  'i18next-browser-languagedetector';
import HttpApi from  'i18next-http-backend';

i18n
  .use(initReactI18next)
  .use(LanguageDetector)
  .use(HttpApi)
  .init({
fallbackLng: 'en',
    detection: {
      order: ['cookie', 'localStorage', 'navigator', 'querystring', 'htmlTag', 'path', 'subdomain'],
      caches: ['cookie'],
    },
    backend: {
loadPath: '/locales/{{lng}}/homepage.json',
    },
    react: {
useSuspense: false,
    },
  });

export  default i18n;

This might seem like a lot, but the code is setting up i18n to work seamlessly with React. It also let's you detect the user's language preferences, and load translations from external files. It's like setting up the perfect workstation—once it's done, everything just flows.

Step 4: Create translation files

Now, let's create those translation files. In the same localization folder, create two folders en and es for English and Spanish locales. Then, create homepage.json in both of these folders.

Here's what the directory structure would look like:

locales/
  en/
    homepage.json
  es/
    homepage.json

We'll be making one translation file per page to make this setup easier to manage down the line. 

Next, add your translation strings to both the files.

// locales/en/homepage.json
{\
 "welcome": "Welcome",
 "description": "This is a localized app"
}

// locales/es/homepage.json
{\
 "welcome": "Bienvenido",
 "description": "Esta es una aplicación localizada"
}

Step 4: Integrate i18next into your React Native app

Great! Now we have our basic setup. But how do we use it in our app?

Let's update our App.js:

import React from  'react';
import { Text, View } from  'react-native';
import { useTranslation, I18nextProvider } from  'react-i18next';
import i18n from  './i18n';

const App = () => {
 const { t } = useTranslation();

 return (
 <View>
 <Text>{t('welcome')}</Text>
 <Text>{t('description')}</Text>
 </View>
  );
};

export  default () => (
 <I18nextProvider i18n={i18n}>
 <App />
 </I18nextProvider>
);

The useTranslation hook is a key feature of the react-i18next localization library. It provides an easy way to access translation functions within your React components, allowing you to dynamically translate text based on the user's selected language. a localized app mockup

When you run your app, you should see the text in either English or Spanish, depending on your device settings. 

But this setup works great for simple apps, but what if you're working on something more complex? That's where things can get a bit tricky.

Advanced localization with React Native

Now that we've got the basics down, let's dive into some advanced techniques that'll help you go beyond just text translation.

Handling plurals

Remember learning about singulars and plurals in school? While handling plurals comes naturally to us, you need to help your app a bit.

When coding, you actually have to let i18next know what the plural for every word should be. 

Let me give you an example here:

// locales/en/homepage.json
{
 "items": "{{count}} item",
 "items_plural": "{{count}} items"
}

// locales/es/homepage.json
{\
 "items": "{{count}} artículo",
 "items_plural": "{{count}} artículos"
}

You can use the above translation in your components like I've done below:

<Text>{t('items', { count: 1 })}</Text>
<Text>{t('items', { count: 5 })}</Text>

i18next will automatically use the correct form based on the count. 

Context-based translations

Sometimes, a word might have different translations depending on the context. i18next helps you easily handle this with the underscore naming convention.

// locales/en/homepage.json
{
 "read": "Read",
 "read_past": "Read"
}

// locales/es/homepage.json
{
 "read": "Leer",
 "read_past": "Leído"
}

Here's how to use these translations in your app:

<Text>{t('read')}</Text>

<Text>{t('read', { context: 'past' })}</Text>

This way, you can have different translations for the same word based on context. 

Formatting dates and numbers 

As I mentioned before, localization is more than just text translation. 

Different countries have different numbers and date formats. For instance, the US uses MM/DD/YYYY for dates, the UK uses DD/MM/YYYY, and the European documents use YYYY-MM-DD.

Imagine if you showed the same date, 01/03/2024, to a person from the US and the UK. 

The person from the US will interpret it as January 3, 2024 while the person from the UK will think it's March 1, 2024

Similarly, some countries, like Italy, use a comma for decimal separators and a period for number formatting. For instance, the number fifty thousand is written as 50.000 in Italian. To the English eye, this may seem like 50 followed by 3 decimal places and not 50,000.

49 dollars and 99 cents will be written as $49,99 when using Italian instead of $49.99 that the English world is used to seeing. poorly localized website

There are many more nuances that become difficult to manage, especially as our apps begin to grow. 

Luckily, React has a react-intl package that tracks these differences and automatically applies them to our app text. Let's first install the package using npm:

npm install react-intl

Now, I've created a simple example of how we can format numbers and dates using the react-intl package here:

import React from  'react';
import { Text, View } from  'react-native';
import { IntlProvider, FormattedDate, FormattedNumber } from  'react-intl';
import { useTranslation, I18nextProvider } from  'react-i18next';
import i18n from  './i18n';

const App = () => {
  const { t } = useTranslation();

 return (
    <View>
      <Text>{t('welcome')}</Text>
      <Text>{t('description')}</Text>
      <FormattedDate value={new Date()} year="numeric" month="long" day="numeric" />
      <FormattedNumber value={1000} />
    </View>
  );
};

export  default () => (
  <I18nextProvider i18n={i18n}>
    <IntlProvider locale="en">
      <App />
    </IntlProvider>
  </I18nextProvider>
);

Note these two lines:

<FormattedDate value={new Date()} year="numeric"  month="long"  day="numeric" />
<FormattedNumber value={1000} />

These use the react-intl methods to automatically internationalize the specific date and number strings based on the locale being used in the app.

Handling right-to-left (RTL) languages

A few languages, like Persian, Hebrew, and Arabic, are written from the right to the left. If you want to support these languages, you want to ensure that your UI supports this writing direction.

For instance, here's a page that uses the same left-to-right text alignment for RTL languagesLTR alignment for RTL text

This may "seem" normal to our eyes, but it can be quite a chore for the user to read and understand. 

To fix this, you can add the dir attribute to the index.html . This attribute dynamically sets the direction of text on the front-end.

import React from  'react';
import { useTranslation } from  'react-i18next';
import React from  'react';
import { Text, View } from  'react-native';
import { useTranslation, I18nextProvider } from  'react-i18next';
import i18n from  './i18n';
import  './App.css';

function  App() => {
 const { t, i18n } = useTranslation();
 document.body.dir = i18n.dir();
 return (
 <div className="App">
        {t('welcome')}
 </div>
    );
}

export  default App;

The current language direction will be given by i18n.dir(), and then the direction (ltr or rtl) will be given to the body dir property.

Keep in mind these two RTL tips:

  • If you don't want to use flexbox, you can use text-align: start too.
  • Avoid padding-left and instead go with padding-inline-start.

Dynamically changing locale based on user selection

Sometimes locale detection can be inaccurate. Or maybe a person is in a country where they don't understand the language. At such times, you want to give them the option to change the language. 

You'll see most websites have a language switcher somewhere on the page. For instance, here's Wise with its language-switching dropdown. an example of a language switcher

You can add a similar language switcher to your app pages, letting users switch between locales while the app automatically pulls the required language files. 

Here's a very simple example of how I've implemented a language switcher. You can use the same code for your app too.

import React, { useState, useEffect } from  'react';
import { Text, View, Button } from  'react-native';
import { useTranslation } from  'react-i18next';

const App = () => {
  const { t, i18n } = useTranslation();
  const [language, setLanguage] = useState('en');

  const changeLanguage = (lng) => {
    i18n.changeLanguage(lng);
    setLanguage(lng);
  };

 return (
    <View>
      <Text>{t('welcome')}</Text>
      <Button title="Switch to Spanish" onPress={() => changeLanguage('es')} />
      <Button title="Switch to English" onPress={() => changeLanguage('en')} />
    </View>
  );
};

This creates two buttons with the text "Switch to Spanish" and "Switch to English." On pressing any of these buttons, the app will change the language automatically and pull the required files. 

Lazy loading of translation files

While you want to support multiple locales, you also don't want to bog your app down by loading all translation files at once. And this becomes evident, especially after the app grows in size and you have multiple large translation files being downloaded at the same time when your app loads.

You can always use deferred Javascripts or dynamic imports. But for localization, I think it's best to use i18next's lazy loading features. 

Let me put this in perspective with a simple example:

// i18n.js
i18n
 .use(initReactI18next)
 .use(LanguageDetector)
 .use(HttpApi)
 .init({
 fallbackLng: 'en',
 backend: {
 loadPath: '/locales/{{lng}}/{{ns}}.json',
    },
 ns: ['common', 'homepage', 'profile'],
 defaultNS: 'common',
  });

You can see here that I've added a dynamic filepath with a set of variables like lng and ns. Whenever a user switches languages, the variables are populated with the required details, and only that language file is downloaded from the server. 

For instance, if the user is on the home page and changes to Spanish, the file path will become /locales/es/homepage.json.

I think we can already see how this will make our app way more efficient than simply downloading everything. 

Challenges with localizing complex React Native apps

Translating one page was definitely fun and easy. But as I went along, translating more and more pages, I noticed that this approach was extremely cumbersome. 

The main problem was keeping track of translations across multiple files. I kept moving between files, trying to find translations for specific strings. And when I had to update something on one page, it needed to be updated across all translation files.

There was also a problem with the contextual understanding of these text snippets. When used in different places in the app, the same word or phrase may have more than one meaning. For example, "post" could mean to send something or to do a task "after" another task.

There was also the whole nightmare of emailing translations to freelancers and getting them back into the app. It took me hours to copy and paste between spreadsheets and language JSON files.

I figured there had to be a better way. 

How to streamline React Native localization with Centus

Centus is an app localization platform that solves a lot of the problems I was facing.

With Centus, instead of managing individual JSON files, you manage your translations in the Centus dashboard. When you push new translations, they're automatically synced with your app without having to work on the JSON files.

Here's how it works:

First, sign up to Centus and create a new project creating a new project in Centus

Once in, enter project details.

As your project type, select Software. adding project details in Centus

Now you have your very first translation project in Centus.

To start translating your app, add JSON files to the project. It can be done in the Imports section, by clicking Select a file. importing files to Centus

Centus will automatically segment your JSON file into strings. Like this 👇 Centus editor

Now you can add translations for the languages you know. Just pre-translate the strings with Google Translate, DeepL, or Microsoft Translate and edit them manually.

For other languages, it's better to hand over your project to language professionals. Check this simple guide on how to find translators.

When you find translators and editors for your React translation project, go to the Contributors section of the dashboard. There, click Add people and enter their details and roles. adding users to Centus

Now your translators and editors can collaborate and share feedback, allowing you to ship your React app much faster. sharing feedback in Centus

Very often React Native localization also involves design tweaks. Luckily, Centus has got you covered. 

In collaboration with designers, your editors can adjust translations to ensure they fit within buttons. 

To start adjusting your React app design, use the Centus plugin for Figma. The plugin automatically pulls translations to Figma, sparing you manual effort and helping avoid truncated or overlapping text. Centus-Figma integration

When translations are ready, go to the Export section, There, choose languages, content, and file format. exporting files in Centus

To proceed, click Download export.

Congratulations! Your translations are ready and you can now merge the translated files. 

Testing your localized React Native app

After implementing all these localization features, how do we make sure everything's working correctly? 

We need to run testing. You could either do unit tests or ask someone on your team to manually go through the app, checking each part of the UI to find any missing translations or translation issues. 

1. Creating unit tests for automated testing

Start by creating a tests directory and add some test files:

import React from  'react';
import { render } from  '@testing-library/react-native';
import App from  '../App';
import i18n from  '../i18n';

test('renders welcome message in English', () => {
  i18n.changeLanguage('en');
  const { getByText } = render(<App />);
  expect(getByText('Welcome')).toBeTruthy();
});

test('renders welcome message in Spanish', () => {
  i18n.changeLanguage('es');
  const { getByText } = render(<App />);
  expect(getByText('Bienvenido')).toBeTruthy();
});

Now, you can run the test using the following command:

npm test

2. Running manual tests for more comprehensive testing

You can also test your app localization by manually switching between different languages and checking each screen of your app. Here's a checklist I use:

  1. Does all the text appear in the correct language?
  2. Are plurals handled correctly?
  3. Are dates and times formatted correctly?
  4. For RTL languages, is the layout correct?
  5. Do all images and icons look appropriate for each locale?

And this cannot be a one-time thing. Try keeping a cadence for running tests regularly as you update your website and your translations. You don't want to have untranslated sections on your website and risk giving your app an unprofessional or unpolished feel.

Wrapping up

Phew! We've covered all React localization steps, from how to set up translation to more complex topics like how to deal with plurals and RTL languages.

As you know by now, managing all those translation files, providing translation context, and keeping translations up-to-date is a challenge and a half.

Centus can help you manage the localization process, making it easy not just for you but also for language experts on your team. Why don't you give it a try?

Sign up for Centus and see how it can improve your app localization and internationalization experience!

Get the week's best content!

By subscribing, you are agreeing to have your personal information managed in accordance with the terms of Centus Privacy Policy

Enjoyed the article?

Share it with your colleagues and partners 🤩