React Internationalization for Large Scale Apps

Posted by Zain Sajjad on April 7, 2020

GSD

In recent posts we compared popular libraries available to develop multilingual React applications. We concluded that React-intl & React-i18next are top libraries for this purpose. Now we’ve put together a guide to address the common localization issues found when developing large scale React applications.

React-i18next is one of the widely adopted localization libraries for React applications. With a tiny bundle size of ~15kb and support for all major browsers, it can be goto choice for your next React application. The most outstanding feature of React-i18next is the namespacing and division of translation files into small chunks. This gives a performance edge in large scale applications. There are many plugins plugins to support your development flows. It also supports locize.com out of the box for translation management.

Let's Get Started with React Internationalization

We will start from the installation of react-i18next in the application and look into how it looks in our code. It's a wrapper around i18next, so we have to install both of these libraries in our project.

npm install react-i18next i18next

Integration

After installation its time to initialize the library. With scalability in mind, it is highly recommended to keep following code in a separate file. That file can be imported at the root of the application.

import i18n from "i18next";
import { initReactI18next } from "react-i18next";

// the translations
// (tip move them in a JSON file and import them)
const resources = {
  en: {
    translation: {
      "titleHeading": "Welcome to React and react-i18next"
    }
  }
};

i18n
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    resources,
    lng: "en",

    keySeparator: false, // we do not use keys in form messages.welcome

    interpolation: {
      escapeValue: false // react already safes from xss
    }
  });

  export default i18n;

As mentioned importing given code at root will initialize react-i18next in your application.

Usage

There are four different approaches to using translation in our application. Let's have a look at each of them.

Hook:

Since the release of hooks, React developers are writing more composed functional components. We can use react-i18next as a hook in our component, here is an example:

import { useTranslation } from 'react-i18next';

function Title () {
  const { t, i18n } = useTranslation();
  return <h1>{t('titleHeading’)}</h1>
}

Trans component

Component API is very intuitive for React developers, react-i18next has a Trans component for rendering. In its simplest form it looks likes as follows:

import { Trans } from 'react-i18next';

function Title () {
  return <Trans i18nKey="titleHeading" />
}

This component gives you a lot of power to add formatting to text. Here is an example:

// "titleHeading": "Hello <1>{{name}}</1>."
<Trans i18nKey="titleHeading">
Hello <strong>{{name}}</strong>.
</Trans>

"multiline": "Some newlines <1/> would be <3/> fine"
<Trans i18nKey="multiline">
Some newlines <br/> would be <br/> fine
</Trans>

With defined limitations like no nested tags and no re-render on namespace or language change, this component is really handy for formatting. One point of attention here, if you don’t require any formatting in your text, it is better to go for a hook or other APIs, as most of this can be achieved via those APIs as well.

Add ButterCMS to your React app in minutes

Higher Order Component (HOC)

HOCs are one of the widely adopted patterns for React apps. HOC API of react-i18next looks like:

import { withTranslation } from 'react-i18next';

function Title ({ t }) {
  return <h1>{t('titleHeading)}</h1>
}

// Wrapping Title in withTranslation
export default withTranslation()(Title);

Render Props

Besides the Trans component, react-i18next has another JSX component API. This component is based on the render props pattern. Here is how it looks:

import { Translation } from 'react-i18next';

export default function Title () {
  return (
    <Translation>
       {
t => <h1>{t('Welcome to React')}</h1>
  }
    </Translation>
  );
}

As seen, react-i18next has a variety of APIs to give you more control over the text in your applications. Which one of these patterns do you like most and why? I’d love to discuss this further with you in the comments for this article below.

React-i18next Formats

In this section, we will look into the formatting options react-i18next offers. Unlike react-intl it doesn’t have separate components or APIs for formatting number, dates & relative time, rather all of this is done via the powerful interpolation API and given nomenclature.

Interpolation

To concatenate dynamic values in the text, react-i18next has the interpolation API. Unlike other intl libraries it allows accessing values deep inside objects, here is an example:

{
  "simpleValue": "{{what}} is {{how}}",
  “deepValue”: “Hey {{user.name}}, Welcome to {{location.city}}”
}
 
const user = { 
    name: 'Jan',
};
const location = { 
    city: 'Singapore',
};

i18next.t(simpleValue, { what: ‘ButterCMS’, how: ‘amazing’ });
i18next.t(deepValue, { user, location });

This doesn’t end here, interpolation comes with a number of other options, including custom formatting that we will discuss later in this post. You can define the prefix & suffix of text and also unescape text for safety. Here is a list of all available options.

Pluralization

For Pluralization, react-i18next has defined nomenclature that we have to follow in the key of our messages. Starting from the simplest form,

{
  "message": "message",
  "message_plural": "messages",
  "messageWithCount": "{{count}} message",
  "messageWithCount_plural": "{{count}} messages"
}

// Here _plural is the reserved word for defining text for plural value

i18next.t('message', {count: 0}); // -> "messages"
i18next.t('message', {count: 1}); // -> "message"
i18next.t('message', {count: 3}); // -> "messages"

i18next.t('messageWithCount', {count: 0}); // -> "0 messages"
i18next.t('messageWithCount', {count: 1}); // -> "1 item"
i18next.t('messageWithCount', {count: 5}); // -> "5 messages"

This may work for languages or cases with very basic pluralization rules. For more sophisticated cases nomenclature has more terms, here is an example:

{
  "key_0": "zero",
  "key_1": "singular",
  "key_2": "two",
  "key_3": "few",
  "key_4": "many",
  "key_5": "other"
}

i18next.t('key', {count: 0}); // -> "zero"
i18next.t('key', {count: 1}); // -> "singular"
i18next.t('key', {count: 2}); // -> "two"
i18next.t('key', {count: 3}); // -> "few"
i18next.t('key', {count: 4}); // -> "few"
i18next.t('key', {count: 5}); // -> "few"
i18next.t('key', {count: 11}); // -> "many"
i18next.t('key', {count: 99}); // -> "many"
i18next.t('key', {count: 100}); // -> "other"

This adds more power to pluralization for sophisticated languages and cases. But it doesn’t end here. React-i18next has amazing interval pluralization, here is how it looks:

{
  "key": "{{count}} item",
  "key_plural": "{{count}} items",
  "key_interval": "(1){one item};(2-7){a few items};"
}

i18next.t('key2_interval', {postProcess: 'interval', count: 1}); // "one item"
i18next.t('key2_interval', {postProcess: 'interval', count: 4});  "a few items"
// not matching into a range it will fallback to
// the regular plural form
i18next.t('key2_interval', {postProcess: 'interval', count: 100}); // "100 items"

Formatting

React-i18next comes with powerful formatting APIs that can be used with date, numbers and other custom formatting libraries. This API gives you complete control over the formatting mechanism of text. As an example we will plug in two formatting in our initialization, here is how to do this:

i18next.init({
  interpolation: {
    format: function(value, format, lng) {
      
      // A custom formatting
      if (format === 'uppercase') return value.toUpperCase();

      // Using other library like moment
      if(value instanceof Date) return moment(value).format(format);

      return value;
    }
  }
});

Our resource JSON will look like this:

{
  "key": "The current date is {{date, MM/DD/YYYY}}",
  "key2": "{{text, uppercase}} just uppercased"
}

Here is how to use it:

i18next.t('key', { date: new Date() });
// -> "The current date is 07/13/2016"

i18next.t('key2', { text: 'can you hear me' });
// => "CAN YOU HEAR ME just uppercased"

React-i18next Features

Beside the powerful API, a number of extraordinary features make react-i18next the go to solution for large scale React applications. Plugins and utils, namespacing, and nesting are three very important features that we’ll discuss further.

Plugins & Utils

React-i18next has a great ecosystem of plugins and utils built around it. This includes libraries for the detection of language and the extraction of messages from your application.

One of these plugins is a webpack loader that can translate your code and generate a bundle per each language. Some of these can be very handy for large scale applications with multilingual support. Here is our pick:

  • i18next Scanner to scan your code, extract translation keys/values, and merge them into i18n resource files.
  • i18next Loader webpack loader that can translate your code and generate bundle per each language
  • i18next-react-postprocessor embed React elements inside your i18next translation strings

Namespacing

Many of the intl libraries put all of the messages in a single file for translation. Though this approach is great for small scale applications, it's a pitfall when your application grows in size. It's never easy to handle thousands of messages in a single file. To avoid this pitfall, react-18next divides your messages into namespaces. For example, quite often we want to segregate our validation messages & user action labels from our app. With react-i18next you can create 

actions.json -> eg. Button labels 'save', 'cancel'

validation.json -> All validation texts

Whereas all messages related to a particular module or component can remain in their own namespaces. This makes it more manageable for large scale applications.

From the performance perspective, it's not good to load all of your messages upfront on the first load. Dividing messages in namespace allows us to load only required messages.

Here is how to handle namespace loading:

i18next.init({
  ns: ['common', 'moduleA', 'moduleB'],
  defaultNS: 'moduleA'
}, (err, t) => {
  i18next.t('myKey'); // key in moduleA namespace (defined default)
  i18next.t('common:myKey'); // key in common namespace
});

// load additional namespaces after initialization
i18next.loadNamespaces('anotherNamespace', (err, t) => { /* ... */ });

Add ButterCMS to your React app in minutes

Nesting

In order to build a glossary of your app, nesting can be a very handy tool. It allows you to access other translation keys in your messages. For example:

{
  "nesting1": "1 $t(nesting2)",
  "nesting2": "2 $t(nesting3)",
  "nesting3": "3",
}

i18next.t('nesting1'); // -> "1 2 3"

For more sophisticated cases nesting can also pass objects to other keys. For example:

{
      "girlsAndBoys": "$t(girls, {'count': {{girls}} }) and {{count}} boy",
      "girlsAndBoys_plural": "$t(girls, {'count': {{girls}} }) and {{count}} boys",
      "girls": "{{count}} girl",
      "girls_plural": "{{count}} girls"
}

i18next.t('girlsAndBoys', {count: 2, girls: 3});
// "3 girls and 2 boys"

Conclusion

i18next is in the OSS arena since 2011 thus is much more battle-tested than other localization libraries. Not just its powerful API but also its outstanding feature set complimented with plugins and utils make i18next a complete solution of localization of applications. The philosophy of learn once translate everywhere is another benefit for teams of developers working on different tech stacks at the same time. It becomes easier to carry the same mental models across different projects consistently. React-i18next is surely a goto solution for large scale applications looking for performance and extensibility.

Sign up to receive more articles about ButterCMS and React.
    
Zain Sajjad

Zain is a Senior Frontend Engineer at Peekaboo Guru. He is passionate about anything and everything React, React Native, JavaScript & Mobile Machine Learning.

ButterCMS is the #1 rated Headless CMS

Related articles

Don’t miss a single post

Get our latest articles, stay updated!