How to set up localization in React Apps using LinguiJS and Crowdin.

Sandy Goodnews
8 min readJun 7, 2023

--

Information may be available online but not accessible to some audiences. Integrating localization into applications solves the problem of language barriers and ensures participation from a diverse audience and culture. In this blog post, I want to talk about how you can add localization to your React Application.

Table of Content
Definitions
Prerequistes
Installation/Set-up
Configure translation process
Translation on Crowdin
Toggle Language
Demo
Useful Resources

Definitions

  1. What is Internationalization?
    Internationalization is done in the design or development phase of a project; the goal is to make it adapt to multiple languages and regions without needing future engineering changes. Internationalization( written as i18n because it contains 18 letters between i and n) includes choice in tool selection, programming stack, project organization, and structure, all to ensure the project is scalable and can adapt to various languages when needed.
  2. What is Localization(i10n)?
    Localization follows after internationalization; it focuses on the details of the translation process and involves ensuring that the translated language reflects the culture of the user in terms of context, page styling, and text direction without losing its original meaning.
  3. What is LinguiJs?
    LinguiJS is a lightweight and powerful JavaScript library for internationalization (i18n) of JavaScript projects. It’s popular and easy to use. Other Javascript localization libraries include React-intl-universal, i18next.
  4. What is Crowdin
    Crowdin is a tool for managing content translation for a project. It simplifies the language translation process for teams.

Prerequisites

  • Basic command-line knowledge.
  • Knowledge of Javascript.
  • Knowledge of React applications.

Installation/Set-up

Step 1: Set up a React application
We are using a Vite React app with TailwindCSS for styling. You can follow the installation process here.

  1. Install a Vite React app.
    npm create vite@latest my-project — — template react
  2. Install TailwindCSS.
    npm install -D tailwindcss postcss autoprefixer
    npx tailwindcss init -p
  3. Configure template paths in the tailwind.config.js
  4. Add the @tailwind directives for each of Tailwind’s layers to your ./src/index.css file.

Step 2: LinguiJs Installation
You can follow the installation process here.

  1. Install core packages.
    yarn add — dev @lingui/cli @lingui/vite-plugin babel-plugin-macros
    yarn add @lingui/react @lingui/macro
  2. Add macros plugin to Babel config (e.g: .babelrc).
{ 
“plugins”: [
“macros”
]
}

3. Update the vite.config.js with the plugins.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { lingui } from "@lingui/vite-plugin";

export default defineConfig({
plugins: [
react({
babel: {
// Use .babelrc files, necessary to use LinguiJS CLI
babelrc: true,
},
}), lingui()

],
});

4. Create lingui.config.js with the following config with your desired format. Our preferred language is English and Arabic.

module.exports = {
locales: ["en", "ar"],
catalogs: [
{
path: "src/locales/{locale}",
include: ["src"], //includes all files in the src folder
},
],
format: "po",
};

5. Update the scripts in your package.json .

{
“scripts”: {
“extract”: “lingui extract”,
“compile”: “lingui compile”,
}
}

6. Run the command yarn run extract .

yarn run extract

With the command above, a new folder called locales is created. We can see that we have no extracted content for translation yet.

Step: 3 Crowdin Setup
1. Open an account on Crowdin or Log in if you have an existing account.

Create Crowdin account

2. After successful Log In, you can decide to create a new project or join an existing project.

Create new project

3. Click on the Create project button and fill the project information as seen below. Our target languages is Arabic, you can select mutiple languages you have in mind to translate.

Create crowdin project.

Configure translation process

Step 1: Create a file i18n.js in the src folder with the following content.

import { i18n } from "@lingui/core";

export const locales = {
en: "English",
ar: "Arabic",
};
export const defaultLocale = "ar";

export async function dynamicActivate(locale) {
const { messages } = await import(`./locales/${locale}.po`);

i18n.load(locale, messages);
i18n.activate(locale);
}

Step 2: Update the main.jsx file

import React, { useEffect } from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { defaultLocale, dynamicActivate } from "./i18n.js";

const I18nApp = () => {
useEffect(() => {
// With this method we dynamically load the catalogs
dynamicActivate(defaultLocale);
}, []);

return (
<React.StrictMode>
<I18nProvider i18n={i18n}>
<App />
</I18nProvider>
</React.StrictMode>
);
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<I18nApp />);

Step 3: Let’s a create a webpage with content for translation, update the App.jsx file as show below.
Also notice we using the Trans macro from LinguiJs to wrap our text.

import "./App.css";
import { Trans } from "@lingui/macro";

function App() {
return (
<>
<header className="flex items-center justify-around">
<div className="flex items-center gap-8 justify-center">
<a className="hover:text-primary" href="/">
<Trans> Home</Trans>
</a>
<a className="hover:text-primary" href="/">
<Trans> Courses</Trans>
</a>
<a className="hover:text-primary" href="/">
<Trans> About</Trans>
</a>
<a className="hover:text-primary" href="/">
<Trans>Blog</Trans>
</a>
</div>
<div className="flex items-center gap-8 justify-center">
<button className="py-2 px-6 bg-primary rounded-lg text-white font-bold">
{" "}
<Trans> Log in</Trans>
</button>
<button className="py-2 px-6 bg-primary rounded-lg text-white font-bold">
<Trans> Sign Up</Trans>
</button>
</div>
</header>
<main className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center">
<h1 className="text-2xl font-bold pb-4 ">
<Trans>Learning with Open Innovation MOOCs</Trans>
</h1>
<div className="text-xl leading-8">
<Trans>
Localisation follows after Internationalization it focus on the
details of the translation process, it involves ensuring that the
translated language reflects the culture of the user in terms of
content, page displayed , text direction without losing it's
original meaning.
</Trans>
</div>
</main>
</>
);
}
export default App;

Step 4: Run yarn extract to extract English content.

yarn run extract
The extracted text in messages.po file in the English folder of the locale.
Extracted content in messages.po file of the English folder

The locale folder is updated , you can check the messages.po files, the English folder is populated with English content but no content for the Arabic.

Step5: Push your Application to GitHub.

Translation on Crowdin

Most times we would have thousands of text to translate and doing it manually is not quiet easy. Now this is where Crowdin comes in to ease the translation process.
Crowdin supports Github Integration and that’s how we will get our files into Crowdin.

Step1: Go to your profile, click and open the project you created earlier. You should see Integration in the tab. Click on Integration and Add Integration button.

Select Integration on the profile tab

Step2: The integration opens a market place for App and Integrations tool that can be used in crowdin, search for GitHub and install the App.

Install GitHub integration tool
Install Github integration tool on crowdin marketplace

Step 4 : On the Github integration , Click the setup integration button, select source and translation file mode. Authorize Crowdin to access your GitHub.

Step 5: Select the respository and branch. Click on the edit icon to configure branch.

Select repository and branch for translation

Step 6 : The Branch Configuration is for selecting the files and path for translation. Update the source files path and translated file path as show below. Click Add File Filter Save button

Branch Configuration

Step 7: Click on the Save button on the bottom for final save. You respository should be connected.

Connected respository on crowdin.

Step8: Translating the files
There are different ways to translate in Crowdin;

  • Pretranslate via MT ( This uses translation engines such as Google translate, Microsoft translator etc). Select the target language and files. All the content are translated at once.
pretranslate via MT
  • Using Editor : Click Go to Editor button, select the language option and translate each text . There are auto suggestion on translation.

Step 9: After the translating the files, you can refresh the browser to see the complete translation from 0% to 100%.

Translation completed

Step 10: Go to Integration tab and click Sync now button. Check your respository to see the updated pull request from Crowdin. Crowdin creates a pull request to your main branch when you set up branch configuration. Merge the pull request to your respository and pull the update in your local machine.

Updated ar.po file

Toggle Language

Let’s add a toggle functionality to our application. I will be using local storage to persit my language selection. You could use a context provider, see example here

Step 1: Create a component/LanguageToggler.jsx file in the src folder.
— Install react-select for the select options
yarn add react-select

// LanguageToggler.jsx

import React from "react";
import Select from "react-select";
import {dynamicActivate } from "../i18n.js";

export default function LanguageToggler() {
const languageOptions = [
{ value: "en", label: "English" },
{ value: "ar", label: "Arabic" },
];
const changeLanguage = (selectedOption) => {
window.localStorage.setItem("language", selectedOption.value);
dynamicActivate(selectedOption.value);
};
return (
<Select
className="basic-single"
classNamePrefix="select"
name="language"
options={languageOptions}
onChange={changeLanguage}
defaultValue={languageOptions.find((lang)=>lang.value === locale)}
/>
);
}

Step 2: Add LanguageToggler component to the App.tsx file.

import "./App.css";
import { Trans } from "@lingui/macro";
import LanguageToggler from "./component/languageToggler";

function App() {
return (
<>
....
....
....
<LanguageToggler/>
....
....
....
</>
);
}
export default App;

Step 3: Update the useEffect in the main.tsx file to get current language from local storage.

 useEffect(() => {
const currentLanguage = window.localStorage.getItem("language");
if (!currentLanguage) {
window.localStorage.setItem("language", defaultLocale);
dynamicActivate(defaultLocale);
} else {
dynamicActivate(currentLanguage);
}
})

Demo

Demo App

Sidenote: Arabic is Right-to-left(RTL) language and that has to be acounted for when changing languages.

Useful Resources

--

--

Sandy Goodnews

Software developer. Imaginative. I love to try, I write to relearn and reinforce while sharing the knowledge.