Monday 30 December 2019

Implementing i18next to Your React Application

Some of you may know that I've been working on [this open source project](https://github.com/cswbrian/district-councils-dashboard) and some people requested a i18n feature lately. > i18n: Internationalization (i18n) is the process of developing products in such a way that they can be localized for languages and cultures easily. Later on, I implementated the feature using react-i18next to assert that needed translations get loaded or that your content gets rendered when the language changes. The site requires two languages so we need two translation json files, let's say ``en`` and ``zh``, located at ``web/src/locales/en/translation.json`` and ``web/src/locales/zh/translation.json`` respectively. Those two files should look like below web/src/locales/en/translation.json ```json { "candidate.nominateStatus.disqualified": "Disqualified" } ``` web/src/locales/zh/translation.json ```json { "candidate.nominateStatus.disqualified": "取消資格" } ``` Then, create a js file called ``i18n.js`` ```javascript import i18n from 'i18next' import { initReactI18next } from 'react-i18next' import translationEN from 'locales/en/translation.json' import translationZH from 'locales/zh/translation.json' i18n.use(initReactI18next).init({ fallbackLng: 'zh', debug: true, interpolation: { escapeValue: false, }, resources: { en: { translation: translationEN, }, zh: { translation: translationZH, }, }, react: { wait: true, }, keySeparator: '-', }) export default i18n ``` > By default, the ``keySeparator`` is ``.`` and we need to change it to other value since our key contains ``.``. In your ``index.js``, import ``i18n`` you just created ```javascript import './i18n' ``` The init has been complete. The next step is to replace all the hardcoded values. There are two types of changes - functional componments and class components. For functional components, we need to use ``useTranslation()``. ```javascript // Example: CandidatesContainer.js // --------------------------------------------- // 1. import useTranslation from react-i18next import { useTranslation } from 'react-i18next' // 2. define t from useTranslation() const { t } = useTranslation() // 3. use the t function with the key as the parameter const status = t('candidate.nominateStatus.disqualified') ``` For class components, we need to use ``withTranslation()``. ```javascript // Example: MainAreas.js // --------------------------------------------- // 1. import withTranslation from react-i18next import { withTranslation } from 'react-i18next' // 2. define t from the props const { t } = this.props // 3. use the t function with the key as the parameter const text = t('mainAreas.text1') // 4. wrap the class component with withTranslation HOC export default withTranslation()(MainAreas) ``` What if we need to show dynamic values based on the selected language? I created a function to handle this case. Our design allows a two-letter locale code in url. We distinguish the lang to select the expected value. ```javascript export const withLanguage = (name_en, name_zh) => { var lang = window.location.pathname.match(/^\/([\w]{2})\//) lang = lang ? lang[1] : 'zh' return lang === 'en' && name_en ? name_en : name_zh } ``` The above function is placed in ``utils/helper.js``. ```javascript // Example: Summary.js // --------------------------------------------- // 1. import withLanguage from utils/helper import { withLanguage } from 'utils/helper' // 2. withLanguage() takes 2 parameters - value in en and value in zh respectively // In this example, district.name_en will be used if the lang is en // if the lang is zh, district.name_zh will be used // if district.name_en is null, it will fall back to zh withLanguage(district.name_en, district.name_zh) // if you are not sure what the field names for both language are, check the query // which can be found either in the same file or in web/src/queries/gql.js ``` For interpolation, we need to surround the dynamic value by curly brackets in ``translation.json`` ```json { "districtNewVoterchartContainer.text1": "Voters increased by {{ n }}%" } ``` and pass an object with the key defined in curly brackets and the dynamic value in the second parameter ```javascript // Example: DistrictNewVoterChartContainer.js { t('districtNewVoterchartContainer.text1', { n: _.round(meta.increased * 100, 2) }) } ``` For the links in the menu, we also need to include the current lang in the url. Let's create a function in ``utils/helper.js`` ```javascript export const getCurrentLanguage = () => { return i18n.language || window.localStorage.i18nextLng || 'zh' } ``` and use it to retrieve the current language ```javascript // Example: web/src/components/pages/district/index.js // --------------------------------------------- // 1. import getCurrentLanguage from utils/helper import { getCurrentLanguage } from 'utils/helper' // 2. call getCurrentLanguage() to retrieve the current language handleChangeDistrict = (year, code) => { if (!year || !code) return const currentLanguage = getCurrentLanguage() this.props.history.push(`/${currentLanguage}/district/${year}/${code}`) } // possible currentLanguage value: en or zh (default) ``` That's it. For more, please check the i18n documentation [here](https://react.i18next.com/).

No comments:

Post a Comment

A Fun Problem - Math

# Problem Statement JATC's math teacher always gives the class some interesting math problems so that they don't get bored. Today t...