Making a Multilingual React App - with i18next

Let's see how to build a multilingual React App, using React-i18next, and some other packages in the i18next environment.

Making a web app with just one language may represent a limitation for your users. Internationalization is an important yet often ignored or missed step by developers. Setting up a React app with i18n support can sometimes turn into a tricky and time-consuming task, but it's actually easier than one might think.

Let's see how to build a multilingual React App, for this tutorial we'll be using React-i18next, and some other packages in the i18next environment.

1. Setting up a React App

Let's get started, I created a starter template on GitHub, you can either clone that or use create-react-app to create your own project.

NOTE: I'll be using Tailwind CSS in this tutorial, so if you're creating your own project with create-react-app make sure that you're installing Tailwind CSS to keep up with this tutorial here, installation guide. It's not necessary tho, use Vanilla CSS.🤷‍♂️

We're going forward with this folder structure.

multilingual-react-app-i18n
├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── .gitignore
├── tailwind.config.js
├── public
   ├── favicon.ico
   ├── robots.txt
   ├── index.html   
   ├── manifest.json  
└── src
    ├── app.js
    ├── index.js
    ├── index.css

2. Installing the Dependencies

We are going to add a few packages from i18next library.

npm i i18next react-i18next i18next-http-backend i18next-browser-languagedetector js-cookie classnames

3. Configuring i18next

I18next is the core of the i18n functionality while react-i18next extends and glues it to react. Create a new file i18n.js beside your index.js and add the following lines of code:

import i18n from 'i18next'
import Backend from 'i18next-http-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
import { initReactI18next } from 'react-i18next'

i18n
  // learn more: https://github.com/i18next/i18next-http-backend
  .use(Backend)
  // detect user language
  // learn more: https://github.com/i18next/i18next-browser-languageDetector
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  // for all options read: https://www.i18next.com/overview/configuration-options
  .init({
    fallbackLng: 'en',
    debug: false,
    // Options for language detector
    detection: {
        order: ['cookie'],
        caches: ['cookie'],
    },
    backend: {
        loadPath: '/assets/locales/{{lng}}/translation.json',
    },
  })

export default i18n

Here, we're selecting English as our fallback language and we're detecting the language selected by the user's cookies. You can choose other options as well like querystring, localStorage, sessionStorage, navigator, path, htmlTag, and subdomain. Refer to this for more info. The default options can be found here

4. Importing the i18n config

Now, let's import i18n.js in index.js, and let's use React Suspense to make sure our content is being rendered, with a loader as a fallback. React Suspense is a React component that suspends a component('s) being rendered until a certain condition has been met and will display a fallback option.

import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import './i18n';

const loader = (
  <div class="flex justify-center items-center">
    <div class="spinner-grow inline-block w-8 h-8 bg-current rounded-full opacity-0" role="status">
      <span class="visually-hidden">Loading...</span>
    </div>
  </div>
)

ReactDOM.render(
  <Suspense fallback={loader}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </Suspense>,
  document.getElementById('root')
);

5. Adding the Translations

Now in the public folder of your root directory, create a folder called locales and for each language that you want to use in your app, create a separate folder with the language code as the folder name and a translation.json file inside that. Refer to the following folder structure:

├── public
   ├── locales
      ├── en
         ├── translation.json
     └── ja
         ├── translation.json
     └── hi
         ├── translation.json

Here, I'm using three languages English, Hindi, and Japanese, feel free to add your preferred languages! Now, for each language let's add the translations.

en/translation.json

{
    "app_title": "Title",
    "language": "Language",
    "welcome_message": "Welcome! This Website is",
    "welcome_message2": "available in multiple languages",
    "description": "Here we're using the principles of i18n to make this React App multilingual."
}

hi/translation.json

{
    "app_title": "शीर्षक",
    "language": "भाषा",
    "welcome_message": "स्वागत! यह वेबसाइट अनेक",
    "welcome_message2": "भाषाओं में उपलब्ध है",
    "description": "यहां हम इस रियेक्ट ऐप को बहुभाषी बनाने के लिए i18n सिद्धांतों का उपयोग कर रहे हैं |"
  }

ja/translation.json

{
  "app_title": "タイトル",
  "language": "言語",
  "welcome_message": "いらっしゃいませ! このウェブサイトは",
  "welcome_message2": "複数の言語で利用可能です",
  "description": "ここでは、i18nの原則を使用して、このReactアプリを多言語対応にします"
}

NOTE: Your translations are stored in simple JSON files. The key will be used in your code and automatically substituted with a proper value based on the chosen language.

6. The Final Step

Now in our App.js, let's import useTranslation hook from react-i18next. We'll use the useTranslation hook inside our functional components to access the translation function or i18n instance.

Since we're using cookies for detecting the language, we'll use an npm package js-cookie for handling cookies.

import React, { useEffect } from "react"
import { useTranslation } from 'react-i18next'
import i18next from 'i18next'
import cookies from 'js-cookie'
import cn from 'classnames'

const languages = [
  {
    code: 'hi',
    name: 'हिन्दी',
  },
  {
    code: 'en',
    name: 'English',
  },
  {
    code: 'ja',
    name: '日本',
  },
]

function App() {
  const currentLanguageCode = cookies.get('i18next') || 'en'
  const currentLanguage = languages.find((l) => l.code === currentLanguageCode)
  const { t } = useTranslation()

  return (
    <>
      <section className="text-gray-600 body-font bg-indigo-100 h-screen">
        <div className="container mx-auto flex px-1 md:px-5 py-16 md:flex-row flex-col-reverse justify-center items-center">
            <div className="w-72 md:w-[600px] flex flex-col my-16 md:mb-0 px-0 md:px-7 items-center text-center">
              <h1 className="title-font sm:text-4xl text-3xl mb-4 font-medium text-gray-900">{t('welcome_message')}
                  <br className="inline-block" />{t('welcome_message2')}
              </h1>
              <p className="mb-8 leading-relaxed">{t('description')}</p>
              <div className="flex justify-evenly w-full space-x-4">
                  {languages.map(({ code, name }) => (
                    <a 
                      href="#"
                      onClick={() => {
                        i18next.changeLanguage(code)
                      }}
                      className={cn('inline-flex text-white border-0 py-2 px-4 focus:outline-none hover:bg-blue-600 rounded text-lg', currentLanguageCode === code ? 'bg-blue-600' : 'bg-blue-400')}
                    >
                      {name}
                    </a>
                  ))}
              </div>
            </div>
        </div>
      </section>
    </>
  )
}

export default App

And that's it we made our app multilingual. Test it out by running this command in your terminal:

npm  start

Now, just adding a little more detail here. Let's also change the page title according to the language selected.

Just above the return statement in App.js, add the following lines of code.

useEffect(() => {
    console.log('Setting page title')
    document.body.dir = currentLanguage.dir || 'ltr'
    document.title = t('app_title')
  }, [currentLanguage, t])

You can find the source code here.

Conclusion

In this article, we looked at how to build a multilingual React App. Of course, this React App can be enhanced further. Internationalization is a concept that you should be familiar with, and it is also easy to implement as we just saw. But sometimes, the main challenge lies in generating multilingual content. This should be affordable for everyone, regardless of their technology-related skills. You can also use Lokalise.

Let me know if you want a guide on how to implement this on a NodeJS backend, you can also check out i18next docs for more details. Don’t hesitate to post a comment if you have any additional questions or concerns.

Thank you for reading this article today, I hope that you found this helpful and until the next time!