Mastering Multiple Language Support in React Native: Part 3

Mastering Multiple Language Support in React Native: Part 3

This post was originally published on Medium by me on July 22, 2020

In this final chapter of the three part tutorial, we will discuss some of the advanced features offered by i18next, these topics will include rendering dynamic strings using features such as interpolation, formatting, setting timezone locale, pluralization, nesting, context, and JSON objects and arrays.

If you want to learn about configuring localization and internationalization with i18next, jump to part 1 of this series.

If you want to learn about organizing translations for a medium to large application and providing language change control to the users, hop on to part 2 of the series.

If you want to view code for this part, please clone the repo at: https://github.com/naishe/rn_multilanguage and checkout part_3 branch.

With all the formality done, let’s earn some elbow grease, shall we?

INTERPOLATION

The most frequently used utility is interpolation. Interpolation allows you to provide a templated string like { howdy: “Hey {{ username }}, how are you doing today?”} and render it with the dynamic value you pass from the rendering code.

t("howdy", { username: "Nishant"});
// returns: Hey Nishant, how are you doing?

But, life isn’t easy. Most of the time you have things like user object and userBill object and you need to print something like Hey <username>, your account has been suspended until your last bill Rs <bill amount> is cleared. Here is the trick:

// String template  
accountSuspended: "Hey {{ user.username }}, your account has been suspended until your last bill Rs {{ userBill.total }} is cleared."

// Rendering  
const user = {  
 username: "Nishant",  
 intelligenceLevel: 7  
};

const userBill = {  
 total: 420.69,  
 paymentDueDate: "May the fourth"  
};

t("accountSuspended", { user, userBill })  
// returns: Hey Nishant, your account has been suspended until your last bill Rs 420.69 is cleared.

FORMATTING

You can create your custom formatting function and pass its name along with the string to be formatted to i18next.t and it will do it for you. You need to register your formation functions in init configuration under the interpolation attribute as format key.

For example, if you want to write a formatting function that takes a number and prints the Indian flag that many times if language code is hi, Hindi, else prints world emoji those many times. If it is not a number it returns whatever is passed. The code looks like as below:

I haven’t found a great use case where I would use formatting. One of the most useful things we can do is we can instruct Moment.js (https://momentjs.com/) to use locale whenever the language preference changes.

You just need to add a listener after initializing i18next, like this:

// i18next configuration file

//Make sure you import the locales  
import 'moment/min/locales';  
import moment from 'moment';

// — removed for brevity — 

i18n.on('languageChanged', (lng: string) => moment.locale(locale));

A caveats here. Moment does not load all the locales so you need to import the locales.

Let’s have a look how formatting code looks like:

<Text>{t('flagsCount', {count: 3})}</Text>

<Text>  
  Moment.js Locale 42 minutes ago:  
  {moment().subtract(42, 'minutes').fromNow()}  
</Text>

And it renders:

Format in action, also automatically switches locale for Moment.JS

☝️ Format in action, also automatically switches locale for Moment.JS


PLURALIZATION

Different languages handle pluralization differently. It is not uncommon to come across uses where you need to display difference strings depending on the quantity of subject matter. Here is an example in English:

Singular: 1 knife found during the autopsy
Plural: 3 knives found during the autopsy

To display different content based on the number passed to i18next.t function, you need to define two keys, one regular key to be used for singular cases and another key suffixed with _plural for plural usage.

{  
  autopsy: “{{count}} knife was found during the autopsy”,  
  autopsy\_plural: “{{count}} knives were found during the autopsy”,  
}

// returns:  
// t(‘autopsy’, {count: 1}) = 1 knife was found during the autopsy  
// t(‘autopsy’, {count: 7}) = 7 knives are found during the autopsy

However, this way of writing sentences does not have human touch to it, it is still too beep-boop-1-krrr-knife-was-found-isque. We would probably want something like this:

No knife was found on the dead body.
A knife was found during the autopsy.
7 knives were found during the autopsy. Looks like a work of a deranged maniac.

To that end, let’s try specifying what number triggers which translation. So, for example, a set of keys like this should serve our purpose:

{  
 autopsy_0: "No knife was found on the dead body.",  
 autopsy: "A knife was found during the autopsy.",  
 autopsy_plural: "{{ count }} knives were found during the autopsy. Looks like a work of a deranged maniac.",  
}

// returns:  
// t(‘autopsy’, {count: 0}) = 0 knives were found during the autopsy. Looks like a work of a deranged maniac.

// t(‘autopsy’, {count: 1}) = A knife was found during the autopsy.

// t(‘autopsy’, {count: 7}) = 7 knives were found during the autopsy. Looks like a work of a deranged maniac.

Hmmm, it did not work that way expected. I was disappointed to learn this.

Limitations of Plural: The variable has to be named count. I would have liked a case where you can tell which field in a JSON object controls the pluralization. But, this is just a minor inconvenience.

You need to know which locale supports what kind of pluralization, so you cannot arbitrarily create keys key_0, key_1, key_2, and so on and expect it to work. You can use this lookup utility to see what suffixes work for your locale https://jsfiddle.net/sm9wgLze

Also, have a look at this issue: https://github.com/i18next/i18next/issues/1220

Let me explain what I just said, for example:

hi supports key (for count = 1) and key_plural (for count other than 1, even zero and negative counts)

en supports key (for count =1) and key_plural (for all other values of count)

ar — Arabic — supports key_0 (for count=0), key_1 (for count=1), key_2 (for count=2), key_3 (for count=3 to 10), key_4 (for count=11 to 99), key_5 (for count=100 and above)

So, pluralization does not look much fun anymore. If you still want to use count based translation, the good people from i18next have provided a post processor https://github.com/i18next/i18next-intervalplural-postprocessor You can learn about it on their GitHub repo.


CONTEXT

Context can be thought of as “which variation of a key can be used in a given context”. Case in point, ThatMate (https://thatmate.com) is a sexual and mental health awareness platform for teenagers. We have been planning to use binary and gender neutral pronouns in English locales. Here is quick glance on what it looks like

Learn more about gender spectrum at https://lbgtrc.msu.edu/educational-resources/pronouns/

☝️ Learn more about gender spectrum

{  
  loveThyself_she: "She is in love with herself",

  loveThyself_he: "He is in love with himself",

  loveThyself_they: "They is in love with themself",   
  //yes, "is" is the right helping verb for they here

  loveThyself_ze: "Ze is in love with hirself",

  loveThyself_ey: "Ey is in love with eirself",  
}

We want the appropriate translation to appear on screen based on what the user has chosen as their preferred pronoun. So, if a user’s profile says their preferred pronoun is “ey”, we want then to see the last variations of “loveThyself”. My context here is “ey”

Context is a really powerful tool. For example, Hindi, ‘hi’ does not have equivalent five pronouns. It has just three pronouns, for male, for female, and a gender neutral. We need to fallback on gender neutral pronouns in Hindi for ‘ze’ and ‘ey’ pronouns. But that does not mean we will have to create five keys the same as the case above and have ‘ze’, ‘ey’ and ‘they’ have the same content. We can just define, three and non matching will fallback to suffix-less translation. So, the Hindi keys look like this:

{  
  loveThyself_she: 'वह अपने से प्रेम करती है।,  
  loveThyself_he: 'वह अपने से प्रेम करता है।',  
  loveThyself: 'वो अपने से प्रेम करते हैं।',  
}

And to access these contexts you need to call `i18next.t` with key and context as options. So, for a code that look like this:

const genderPronouns = \['she', 'he', 'they', 'ze', 'ey'\];

// — code removed for brevity

{   
  genderPronouns.map((context) =>   
    (<Text key={context}>{t('loveThyself', {context})}</Text>)  
  )  
}

The outcome in en language code is as follows:

All five genders have their appropriate pronouns

☝️ All five genders have their appropriate pronouns

And, the same code render the following in hi language:

Hindi has just three forms of the pronouns

☝️Hindi has just three forms of the pronouns

If you look closely, even though you do not get the script, the last three fallback to the translation that does not have any context (no suffix).

— — — — — — —

There are only a few good things in the world. You can count them if you think hard and long lying on your back well fed and healthy in a serene environment. Context is one of those. Remember how disappointing it was that pluralization could not help us with cases where we specified translations for count = 0, 1, and for all other values of count. Context with help of interpolation can do that. The following code gives the desired result:

<Text>{t(‘autopsy’, {count: 0, context: ‘0’})}</Text>  
<Text>{t(‘autopsy’, {count: 1, context: ‘1’})}</Text>  
<Text>{t(‘autopsy’, {count: 7, context: ‘7’})}</Text>

The result:

Pluralization combined with context can do what Pluralization should have done

☝️ Pluralization combined with context can do what Pluralization should have done


OBJECTS AND ARRAYS

Language resources are just a big JSON object with string keys and values that could string, JSON objects or arrays. Sometimes you may want i18next to return you the object or array instead of string. One such case appeared when we wanted to display a random funny loading message from a list of loading messages. If we get a list of strings, we could just do list[ Math.floor( Math.random() * list.length ) ]. Luckily for us, we can retrieve a key as a JSON object or array, just by instructing i18next so.

{  
  funnyLoadingMessages: [  
    "Maybe the server gnomes are sleeping",  
    "What is taking so long?",  
    "Damn! I can believe I paid for this app.",  
    "One mississippi, two mississippi, …",  
  ]  
}

And the following code returns the array

t("funnyLoadingMessages", { returnObjects: true });

You can apply interpolation and pluralization on the return as well.


REFERENCING OTHER KEYS
You can reference one key into another key’s translations. For example, something like this:

{  
  salutation: "howdy!",  
  goodnight: "$t( salutation ) What are you doing so late up? I am signing off. Good night, m8!"  
}

t("goodnight");  
// returns: howdy! What are you doing so late up? I am signing off. Good night, m8!

And as always, interpolation, context, etc works as expected.


This concludes the third and the final chapter of this tutorial. But, by no means it is a substitute of the documentation offered by i18next (https://www.i18next.com/) and react-i18next (https://react.i18next.com/), so if you want to dive deeper please look into those resources.

Here is how the final app looks like

Left is Hindi locale, and right is English

☝️ Left is Hindi locale, and right is English

Of course, you’d give the repo a try, but just in case, you wanted to see

☝️ Of course, you’d give the repo a try, but just in case, you wanted to see

Please press the clap button 50 times (yes, you can) if you liked the article. I wrote this article while looking for a complete How-To for multiple language support on React Native in the middle of a break neck ongoing sprint, so there is a good chance that I have made buckets of spelling and grammatical mistakes. Please be kind and let me know if you come across mistakes of any kind. I hope my tardy sense of humor did not bore you to death.

Read Part 1

Read Part 2