How to build multilingual React.js application

Creating a suitable React.js sample to try out Gitloc

If you want to create your project from scratch, then you can use the following step-by-step guide, where we will create a multilingual React.js sample project using i18next and Gitloc

Create a React Application (more at reactjs.org and create-react-app.dev)

Prerequisites

Make sure you have Node.js and npm installed. You’ll need to have Node >= 14 on your local development machine. You can use nvm (macOS/Linux) or nvm-windows to switch Node versions between different projects.

It's best, if you have some experience with simple HTML, JavaScript and basic React.js, before jumping to localization. This react localization example is not intended to be a React beginner tutorial.

Now let's create a new react project named "my-app" with create-react-app.

To create a new app, you may choose one of the following methods:

npx
npx create-react-app my-app

*(npx comes with npm 5.2+ and higher, see instructions for older npm versions)

npm
npm init react-app my-app

*npm init <initializer> is available in npm 6+

Yarn
yarn create react-app my-app

*yarn create is available in Yarn 0.25+

Running any of these commands will create a directory called my-app inside the current folder. Inside that directory, it will generate the initial project structure and install the transitive dependencies:

my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── serviceWorker.js
    └── setupTests.js

No configuration or complicated folder structures, only the files you need to build your app. Once the installation is done, you can open your project folder:

cd my-app

Inside the newly created project, you can run some built-in commands:

npm start or yarn start runs the app in development mode. Open http://localhost:3000 to view it in the browser. The page will automatically reload if you make changes to the code. You will see the build errors and lint warnings in the console.

npm test or yarn test Runs the test watcher in an interactive mode. By default, runs tests related to files changed since the last commit (Read more about testing).

npm run build or yarn build Builds the app for production to the build folder. It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes. Your app is ready to be deployed.

Setup i18next (more at react.i18next.com)

In our example, we will create a project with 2 languages and separate files for translations.

Let's install some i18next dependencies:

npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend

Let's prepare an i18n.js file:

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

i18n
  // i18next-http-backend
  // loads translations from your server
  // 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({
    debug: true,
    languages: ['en', 'de'],
    fallbackLng: 'en',
    ns: ['common', 'welcome'],
    defaultNS: 'common',
  })

export default i18n;

Import this file in our index.js file:

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

// import i18n (needs to be bundled ;))
import './i18n';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Place translations into dedicated .json files in the public folder:

└── public
    └── locales
        ├── de
        │   ├── common.json
        │   └── welcome.json
        └── en
            ├── common.json
            └── welcome.json

Now define a language switcher component:

components/LanguageSwitcher/index.js
import { useTranslation } from "react-i18next";

const lngs = {
  en: { nativeName: "English" },
  de: { nativeName: "Deutsch" },
};

function LanguageSwitcher() {
  const { i18n } = useTranslation();

  return (
    <div>
      {Object.keys(lngs).map((lng) => (
        <button
          key={lng}
          style={{
            fontWeight: i18n.resolvedLanguage === lng ? "bold" : "normal",
            cursor: i18n.resolvedLanguage === lng ? "auto" : "pointer",
            color: "black",
            margin: "0 4px",
          }}
          type="submit"
          onClick={() => i18n.changeLanguage(lng)}
          disabled={i18n.resolvedLanguage === lng}
        >
          {lngs[lng].nativeName}
        </button>
      ))}
    </div>
  );
}

export default LanguageSwitcher;

And place it in the app

App.js
...
import LanguageSwitcher from "./components/LanguageSwitcher";

function App() {  
  return (
    <div className="App">
      ...
      <LanguageSwitcher/>
      ...
    </div>
  );
}

export default App;

Add translations usage (more at i18next.com and react.i18next.com)

Simple content can easily be translated using the provided t function. For the first text we just use a simple test key to directly invoke it.

You will get the t function by using the useTranslation hook or withTranslation hoc.

import { useTranslation } from 'react-i18next';

export function MyComponent() {
    const { t } = useTranslation();
    return <p>{t('test')}</p>
}
public/locales/en/common.json
{
  "test": "Test"
}

Now let's see how you can work with different namespaces. For the second text we will use thetitle key form welcome namespace.

import { useTranslation } from 'react-i18next';

// load a specific namespace
export function MyComponent() {
    // the t function will be set to that namespace as default
    const { t } = useTranslation('welcome');
    return <h1>{t('title')}</h1>
}

// load multiple namespaces
export function MyOtherComponent() {
    // the t function will be set to first namespace as default
    const { t } = useTranslation(['common', 'welcome']);
    return (
        <div>
            <p>{t('test')}</p> // will be looked up from namespace 'common'
            <h1>{t('title'), { ns: 'welcome' }}</h1> // from namespace 'welcome'
        </div>
    )
}
public/locales/en/welcome.json
{
  "title": "You did it!"
}

For the third text, we will interpolate description key form common namespace by using the <Trans> component. These can be referenced as <1>{{someSlot}}</1> in the translations

As long you have no React/HTML nodes integrated into a cohesive sentence (text formatting like strong, em, link components, maybe others), you won't need it - most of the times you will be using the good old t function.

import { useTranslation, Trans } from "react-i18next";

const fileName = "src\\App.js";

export function MyComponent() {
  const { t } = useTranslation();
  return (
    <Trans i18nKey="description.text">
      <code>{{ fileName }}</code>
      <a
        className="App-link"
        href="https://reactjs.org"
        target="_blank"
        rel="noopener noreferrer"
      >
        {t("description.reactLink")}
      </a>
    </Trans>
  );
}
public/locales/en/common.json
{
  "description": {
    "text": "Edit <0>{{fileName}}</0> and save to reload. See <1>docs</1> for more.",
    "reactLink": "docs"
  }
}

Finally, сonnect your local project folder to your new repository on GitHub.

Done! Now you can move to the next step - connect remote repository to Gitloc

Last updated