(this article is also available on Medium: https://medium.com/@iuliu/7-easy-steps-to-localize-your-react-native-app-6622658d1624)

Which library should you choose?

It is true that any translation library that works for React will also do the trick for React Native. There are about 5 or 6 main options that you could choose from, but after counting up the pros and cons of each one, we decided to go with i18next.

i18next is a free library that offers a quick "getting started" routine, as well as some nice plug-n-play features that you might find useful and opt for later.

Let's get started!

1. Install i18next

npm install i18next --save

2. Create empty translation file for English

Create a file and put it in a folder that works best for you (e.g. /translations/en.tsx), where you will later add translation files for all your supported languages. It should, for now, look something like this:

                
  const translation = {};
  export default translation;
            

3. Initialize the library and supply it with the file you just created

Add this code somewhere at the top of your root component:

                
        
  import i18n from 'i18next';
  import { initReactI18next } from 'react-i18next';
  
  import en from './translation/en';

  i18n
  .use(initReactI18next)
  .init({
    lng: 'en',
    fallbackLng: 'en',
    debug: true,
    interpolation: {
      escapeValue: false,
    },
    resources: {
      en: {
        translation: en,
      },
    },
  });
        
            

The initialization is pretty straightforward, but can take in quite a few configuration options based on your needs. You can see all of them here.

But right now, we can already go ahead and try it out:

                
  import i18next from 'i18next';
  
  render() {
    return (
      <Text>{i18next.t('Hello world!')}</Text>
    )
  }
            

4. Make the t function available globally

This will save a lot of time during development, as it removes the need to prefix the t function with i18next. every time.

Add this in your root component:

                
  import i18next from 'i18next';

  global.T = i18next.t;
            

Now, instead of importing 18next every time, you can just call T:

                
    render() {
      return (
        <Text>{T('Hello world!')}</Text>
      )
    }
            

Shorter, cleaner and quicker to write.

Feel free to use t instead of T. We prefer it for visibility.

5. Add strong typing to your T calls

This is not mandatory, but with some TS magic and very little code, we can achieve static checking for our keys when calling T. Modify your previous code as follows:

                
  import i18next from 'i18next';

  import translation from './translation/en';

  type TKeys = keyof typeof translation;

  function translate<K extends TKeys>(key: K, options?: Object) {
    return i18next.t(key, options);
  }

  
  global.T = translate;
  
            

We are importing the already existing translation file that has all our english keys, and using them to statically analyze and validate our T calls. Sweet!

Now, whenever we attempt to translate using a non-existing key, we get a static compiler warning:

Compiler error

6. NOT adding every single key by hand to our dictionary

So, after we've wrapped our labels like T('Some key'), we need to somehow get our key ('Some key') into the translation file.

You could do that manually, but depending on the size of your project it might be really time-consuming, and will definitely not feel like a coder's job.

Here's where one of i18next's many features comes into play. The tool that we will be using is called locize (read more about it and/or register for it here).

What it's gonna do for us: whenever we call i18next.t with a certain key, the library will automatically check if it exists, and if not, add it to a json that we can easily export and then put into our project. Nice!

Add the following to your init:

                
  import i18n from 'i18next';
  //install this before importing
  import Backend from 'i18next-locize-backend'; 
  import { initReactI18next } from 'react-i18next';
  
  import en from './translation/en';


  const locizeOptions = {
    projectId: 'take it from locize',
    apiKey: 'same',
    referenceLng: 'en',
  };


  i18n
  .use(initReactI18next)
  .use(Backend)
  .init({
    lng: 'en',
    fallbackLng: 'en',
    debug: true,
    interpolation: {
      escapeValue: false,
    },
    saveMissing: true,
    resources: {
      en: {
        translation: en,
      },
    },
  });
        
            

Now if you navigate to a screen that uses the T function, your key will automatically be added to locize:

Locize service interface

And whenever you feel like it, export it to a JSON using the "...More" context menu in the top right. The JSON will look like this:

                
  {
    'Be the first to start a discussion!': 'Be the first to start a discussion!',
    'Drum roll for the': 'Drum roll for the',
    'Other key': 'Other key'
  }
            

7. Changing the language

Language picker

Changing the language is simply telling i18next to "use another resource file" (e.g. fr.tsx). To do this, we need to wrap the component from which we want to change the language with the withTranslation HOC:

                
  export default withTranslation()(RootComponent)
            

And then it's as simple as calling

                
  this.props.i18n.changeLanguage(languageCode);
            

inside your component.

Here, languageCode is one of the provided keys inside of
resources in the i18next initialization object.

8. And we're done!

There are still some nice things you could do, such as putting your T inside of a custom Text component that you would use instead of the default one, to get you writing even less Ts, but other than that, these steps will get you to a point in which you can get to a fully translation-ready app in as little as a day. And then it's just a matter of sending the files to the translators!