How to Internationalize a React App

shahednasser

Shahed Nasser

Posted on September 2, 2021

How to Internationalize a React App

This article was originally published on my personal blog

Internationalization, or i18n, is supporting different languages in your website or app. It allows you to gain users from different parts of the world, which leads to growing your website's traffic.

In this tutorial, we'll learn how to internationalize a React website including translating content and changing the layout's direction based on the language chosen.

You can find the full code for this tutorial in this GitHub repository.

Setup Website

First, we'll set up the React website with Create React App (CRA).

Run the following command:

npx create-react-app react-i18n-tutorial
Enter fullscreen mode Exit fullscreen mode

Once that is done, change the directory to the project:

cd react-i18n-tutorial
Enter fullscreen mode Exit fullscreen mode

You can then start the server:

npm start
Enter fullscreen mode Exit fullscreen mode

Install Dependencies

The easiest way to internationalize a React app is to use the library i18next. i18next is an internationalization framework written in Javascript that can be used with many languages and frameworks, but most importantly with React.

Run the following command to install i18next:

npm install react-i18next i18next --save
Enter fullscreen mode Exit fullscreen mode

In addition, we need to install i18next-http-backend which allows us to fetch translations from a directory, and i18next-browser-languagedetector which allows us to detect the user's language:

npm i i18next-http-backend i18next-browser-languagedetector
Enter fullscreen mode Exit fullscreen mode

Last, we'll install React Bootstrap for simple styling:

npm install react-bootstrap@next bootstrap@5.1.0
Enter fullscreen mode Exit fullscreen mode

Create the Main Page

We'll create the main page of the website before working on the internationalization.

Navigation Bar

We first need the Navigation component. Create src/components/Navigation.js with the following content:

import { Container, Nav, Navbar, NavDropdown } from "react-bootstrap";

function Navigation () {

  return (
    <Navbar bg="light" expand="lg">
      <Container>
        <Navbar.Brand href="#">React i18n</Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="me-auto">
            <NavDropdown title="Language" id="basic-nav-dropdown">
              <NavDropdown.Item href="#">English</NavDropdown.Item>
              <NavDropdown.Item href="#">العربية</NavDropdown.Item>
            </NavDropdown>
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}

export default Navigation;
Enter fullscreen mode Exit fullscreen mode

Heading

Then, we'll create src/components/Greeting.js with the following content:

function Greeting () {

  return (
    <h1>Hello</h1>
  );
}

export default Greeting;
Enter fullscreen mode Exit fullscreen mode

Text

Next, we'll create src/components/Text.js with the following content:

function Text () {

  return (
    <p>Thank you for visiting our website.</p>
  )
}

export default Text;
Enter fullscreen mode Exit fullscreen mode

Finally, we need to show these components on the website. Change the content of src/App.js:

import React from 'react';
import { Container } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import Greeting from './components/Greeting';
import Loading from './components/Loading';
import Navigation from './components/Navigation';
import Text from './components/Text';

function App() {

  return (
    <>
      <Navigation />
      <Container>
        <Greeting />
        <Text />
      </Container>
    </>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Run the server now, if it isn't running already. You'll see a simple website with a navigation bar and some text.

How to Internationalize a React App

Configuring i18next

The first step of internationalizing React with i18next is to configure and initialize it.

Create src/i18n.js with the following content:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from 'i18next-http-backend';
import I18nextBrowserLanguageDetector from "i18next-browser-languagedetector";

i18n
  .use(Backend)
  .use(I18nextBrowserLanguageDetector)
  .use(initReactI18next) // passes i18n down to react-i18next
  .init({
    fallbackLng: 'en',
    debug: true,

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

  export default i18n;
Enter fullscreen mode Exit fullscreen mode

We're first importing i18n from i18next. Then, we're adding i18next-http-backend and i18next-browser-languagedetector as plugins to i18n. We're also adding initReactI18next as a plugin to ensure that i18next works with React.

Next, we're initializing i18n by passing it an object of options. There are many options you can pass to the initializer, but we're passing 3 only.

fallbackLng acts as the default language in i18n if no language is detected. Language is detected either from the user's preferred language, or a language they previously chose when using the website.

debug enables debug messages in the console. This should not be used in production.

As for escapeValue in interpolation, we're setting it to false since React already escapes all strings and is safe from Cross-Site Scripting (XSS).

Adding the Translation Files

By default, i18next-http-backend looks for translation files in public/locales/{language}/translation.json, where {language} would be the code of the language chosen. For example, en for English.

In this tutorial, we'll have 2 languages on our website, English and Arabic. So, we'll create the directory locales and inside we'll create 2 directories en and ar.

Then, create the file translation.json inside en:

{
  "greeting": "Hello",
  "text": "Thank you for visiting our website.",
  "language": "Language"
}
Enter fullscreen mode Exit fullscreen mode

This will create 3 translation keys. When these keys are used, the string value that the key corresponds to will be outputted based on the chosen language. So, each language file should have the same keys but with the values translated to that language.

Next, we'll create the file translation.json inside ar:

{
  "greeting": "مرحبا",
  "text": "شكرا لزيارة موقعنا",
  "language": " اللغة"
}
Enter fullscreen mode Exit fullscreen mode

Using the i18n Instance

The next step is importing the file with the settings we just created in App.js:

import i18n from './i18n';
Enter fullscreen mode Exit fullscreen mode

Next, to make sure that the components are rendered once i18next and the translation files have been loaded, we need to surround our components with Suspense from React:

<Suspense fallback={<Loading />}>
    <Navigation />
    <Container>
        <Greeting />
        <Text />
    </Container>
</Suspense>
Enter fullscreen mode Exit fullscreen mode

As you can see, we're passing a new component Loading as a fallback while i18next loads with the translation files. So, we need to create src/components/Loading.js with the following content:

import { Spinner } from "react-bootstrap";

function Loading () {
  return (
    <Spinner animation="border" role="status">
      <span className="visually-hidden">Loading...</span>
    </Spinner>
  )
}

export default Loading;
Enter fullscreen mode Exit fullscreen mode

Now, we're able to translate strings in the App components and its sub-components.

Translating Strings with useTranslation

There are different ways you can translate strings in i18next, and one of them is using useTranslation hook. With this hook, you'll get the translation function which you can use to translate strings.

We'll start by translating the Greeting component. Add the following at the beginning of the component:

function Greeting () {
  const { t } = useTranslation();
    ...
}
Enter fullscreen mode Exit fullscreen mode

Then, inside the returned JSX, instead of just placing the text "Hello", we'll replace it with the translation function t that we received from useTranslation:

return (
    <h1>{t('greeting')}</h1>
  );
Enter fullscreen mode Exit fullscreen mode

Note how we're passing the translation function a key that we added in the translation.json files for each of the languages. i18next will fetch the value based on the current language.

We'll do the same thing for the Text component:

import { useTranslation } from "react-i18next";

function Text () {
  const { t } = useTranslation();

  return (
    <p>{t('text')}</p>
  )
}

export default Text;
Enter fullscreen mode Exit fullscreen mode

Finally, we'll translate the text "Language" inside the Navigation component:

<NavDropdown title={t('language')} id="basic-nav-dropdown">
Enter fullscreen mode Exit fullscreen mode

If you open the website now, you'll see that nothing has changed. The text is still in English.

Although technically nothing has changed, considering we are using the translation function passing it the keys instead of the actual strings and it's outputting the correct strings, that means that i18next is loading the translations and is displaying the correct language.

If we try to change the language using the dropdown in the navigation bar, nothing will happen. We need to change the language based on the language clicked.

Changing the Language of the Website

The user should be able to change the language of a website. To manage and change the current language of the website, we need to create a context that's accessible by all the parts of the app.

Creating a context eliminates the need to pass a state through different components and levels.

Create the file src/LocaleContext.js with the following content:

import React from "react";

const defaultValue = {
  locale: 'en',
  setLocale: () => {} 
}

export default React.createContext(defaultValue);
Enter fullscreen mode Exit fullscreen mode

Then, create the state locale inside src/App.js:

function App() {
  const [locale, setLocale] = useState(i18n.language);
Enter fullscreen mode Exit fullscreen mode

As you can see, we're passing i18n.language as an initial value. The language property represents the current language chosen.

However, as it takes time for i18n to load with the translations, the initial value will be undefined. So, we need to listen to the languageChanged event that i18n triggers when the language is first loaded and when it changes:

i18n.on('languageChanged', (lng) => setLocale(i18n.language));
Enter fullscreen mode Exit fullscreen mode

Finally, we need to surround the returned JSX with the provider of the context:

<LocaleContext.Provider value={{locale, setLocale}}>
      <Suspense fallback={<Loading />}>
        <Navigation />
          <Container>
            <Greeting />
            <Text />
          </Container>
    </Suspense>
</LocaleContext.Provider>
Enter fullscreen mode Exit fullscreen mode

Now, we can access the locale and its setter from any of the subcomponents.

To change the language, we need to have a listener function for the click events on the dropdown links.

In src/components/Navigation.js get the locale state from the context at the beginning of the function:

const { locale } = useContext(LocaleContext);
Enter fullscreen mode Exit fullscreen mode

Then, add a listener component that will change the language in i18n:

  function changeLocale (l) {
    if (locale !== l) {
      i18n.changeLanguage(l);
    }
  }
Enter fullscreen mode Exit fullscreen mode

Finally, we'll bind the listener to the click event for both of the dropdown links:

<NavDropdown.Item href="#" onClick={() => changeLocale('en')}>English</NavDropdown.Item>
              <NavDropdown.Item href="#" onClick={() => changeLocale('ar')}>العربية</NavDropdown.Item>
Enter fullscreen mode Exit fullscreen mode

If you go on the website and try to change the language, you'll see that the language changes successfully based on what you choose. Also, if you try changing the language then refreshing the page, you'll see that the chosen language will persist.

How to Internationalize a React App

Changing the Location of the Translation Files

As mentioned earlier, the default location of the translation files is in public/locales/{language}/translation.json. However, this can be changed.

To change the default location, change this line in src/i18n.js:

.use(Backend)
Enter fullscreen mode Exit fullscreen mode

To the following:

.use(new Backend(null, {
    loadPath: '/translations/{{lng}}/{{ns}}.json'
  }))
Enter fullscreen mode Exit fullscreen mode

Where the loadPath is relative to public. So, if you use the above path it means the translation files should be in a directory called translations.

{{lng}} refers to the language, for example, en. {{ns}} refers to the namespace, which by default is translation.

You can also provide a function as a value of loadPath which takes the language as the first parameter and the namespace as the second parameter.

Changing Document Direction

The next essential part of internationalization and localization is supporting different directions based on the languages you support.

If you have Right-to-Left (RTL) languages, you should be able to change the direction of the document when the RTL language is chosen.

If you use our website as an example, you'll see that although the text is translated when the Arabic language is chosen, the direction is still Left-to-Right (LTR).

This is not related to i18next as this is done through CSS. In this tutorial, we'll see how we can use RTL in Bootstrap 5 to support RTL languages.

The first thing we need to do is adding the dir and lang attributes to the <html> tag of the document. To do that, we need to install React Helmet:

npm i react-helmet
Enter fullscreen mode Exit fullscreen mode

Then, inside Suspense in the returned JSX of the App component add the following:

<Helmet htmlAttributes={{
          lang: locale,
          dir: locale === 'en' ? 'ltr' : 'rtl'
        }} />
Enter fullscreen mode Exit fullscreen mode

This will change the lang and dir attributes of <html> based on the value of the locale.

The next thing we need to do is surround the Bootstrap components with ThemeProvider which is a component from react-bootstrap:

<ThemeProvider dir={locale === 'en' ? 'ltr' : 'rtl'}>
    <Navigation />
    <Container>
        <Greeting />
        <Text />
    </Container>
</ThemeProvider>
Enter fullscreen mode Exit fullscreen mode

As you can see we're passing it the dir prop with the direction based on the locale. This is necessary as react-bootstrap will load the necessary stylesheet based on whether the current direction is rtl or ltr.

Finally, we need to change the class name of Nav in the Navigation component:

<Nav className={locale === 'en' ? 'ms-auto' : 'me-auto'}>
Enter fullscreen mode Exit fullscreen mode

This is only necessary since there seems to be a problem in the support for ms-auto when switching to RTL.

If you try opening the website now and changing the language to Arabic, you'll see that the direction of the document is changed as well.

How to Internationalize a React App

Conclusion

i18next facilitates internationalizing your React app, as well as other frameworks and languages. By internationalizing your app or website, you are inviting more users from around the world to use it.

The main parts of internationalization are translating the content, supporting the direction of the chosen language in your website's stylesheets, and remembering the user's choice. Using i18next you're able to easily translate the content as well as remembering the user's choice.

💖 💪 🙅 🚩
shahednasser
Shahed Nasser

Posted on September 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related