Antoine Mesnil

Developer, Designer & Solo entrepreneur

Quick i18n setup in React native with autocomplete

Cover Image for Quick i18n setup in React native with autocomplete

Introduction

I always make sure to set up i18n (internationalization) in my apps even if there will probably be only one language used. The pain of adapting an app that wasn't thought to be multilingual in the first place is too big to procrastinate on it. Fortunately, I now have an easy setup, with auto-completion with typescript, autodetection of the user language and more.


Setup

To get the user language and handle our translations, we use 2 libraries:

react-native-localization: a toolbox with a simple API to get the user's locale, units and timezone. (nb: if you use expo, expo-localization is also a good library to use)

react-i18next: a powerful and popular framework for internationalization in React / React-native.

npm install react-native-localize react-i18next i18next

After the installation, we need only one configuration file and JSON files that will contain our translations for each language.

Image description

import AsyncStorage from '@react-native-async-storage/async-storage'
import i18n, { LanguageDetectorAsyncModule } from 'i18next'
import { initReactI18next } from 'react-i18next'
import * as RNLocalize from 'react-native-localize'

import fr from './dictionaries/fr.json'

export const locales = {
  fr: {
    translation: fr,
  },
}

export const DEFAULT_LOCALE = 'fr'

export const defaultLanguage =
  RNLocalize.findBestAvailableLanguage(Object.keys(locales))?.languageTag || DEFAULT_LOCALE

export const currentLanguage = i18n.language || defaultLanguage

const useLanguageStorage: LanguageDetectorAsyncModule = {
  type: 'languageDetector',
  async: true,
  detect: async (callback) => {
    AsyncStorage.getItem('language').then((lang) => {
      if (lang) return callback(lang)
    })
  },
  init: () => null,
  cacheUserLanguage: async (language: string) => {
    AsyncStorage.setItem('language', language)
  },
}

i18n
  .use(useLanguageStorage)
  .use(initReactI18next)
  .init({
    fallbackLng: defaultLanguage,
    resources: locales,
    react: {
      useSuspense: false,
    },
  })

export default i18n

Then you will have to import that file in your App.tsx and that's it.


Autocompletion with Typescript

One annoying thing when using translation keys is typing the paths again and again. To improve the developer experience I add a small definition to get suggestions with Typescript, it helps to avoid mistakes and reduce the coding time.

Create a file react-18next.d.ts at the root of your project directory or in a types folder and add this code:

import 'react-i18next'

import fr from './i18n/dictionaries/fr.json'

declare module 'react-i18next' {
  // and extend them!
  interface Resources {
    translation: typeof fr
  }
}

With this setup, you should have something like this in VScode

Image description


Usage

Most common usage

Almost every time a text is used in a screen, you will add that text linked to a key in the JSON file, use the hook provided by i18next which is called useTranslation and use the key in your text

Image description


Access outside of components

Sometimes you may need to use your translations outside of your components in config files or whatever. To do that you can directly import your i18n file and access the t method

import i18next from './i18n'

i18next.t('my.key')

Nested texts/multiple styles

One powerful feature of react-i18next is the Trans component which can interpret custom tags in your translations to use a component for a part of your text.

"myKey":"it is my text <color>colored part</color> end of text"
<Text>
  <Trans
    components={{ color: <Text font="bold"
    color="primary" /> }}
  >
    {t("path.to.my.key"}
  </Trans>
<Text>

Update the language

To update the language, you use the same hook

const { t, i18n } = useTranslation('');
...
<Button onPress={()=>i18n.changeLanguage("de")>{i18n("switchLanguage")}<Button/>

Handle dates, currencies, API values

To handle dates, my go-to library is dayjs because it is a very popular library, complete and it has the same API as Momentjs (which I used by in the days).

import 'dayjs/locale/fr' // load on demand
dayjs.locale('fr') // use French locale globally
dayjs('2018-05-05').locale('fr-FR').format() // use on a specific text

For currencies you have multiple ways to tackle this problem, now I use Intl

const { i18n } = useTranslation()
<Text>{Intl.NumberFormat(i18n.language, {
    style: 'currency',
    currency: 'EUR'}).format(45012)}</Text>

On a final note, make sure that all your form/dynamic values are linked to a slug or a key that is related to your API. With that setup, add another language will be extremely fast instead of a tedious task.

Image description


😄 Thanks for reading! If you found this article useful, consider following me on Twitter, I share tips on development, design and share my journey to create my own startup studio