How to set up localization in React Apps using LinguiJS and Crowdin.
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
- 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. - 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. - 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. - 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.
- Install a Vite React app.
npm create vite@latest my-project — — template react
- Install TailwindCSS.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p - Configure template paths in the
tailwind.config.js
- 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.
- Install core packages.
yarn add — dev @lingui/cli @lingui/vite-plugin babel-plugin-macros
yarn add @lingui/react @lingui/macro - 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
.
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.
2. After successful Log In, you can decide to create a new project or join an existing 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.
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.
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.
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.
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.
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
Step 7: Click on the Save button on the bottom for final save. You respository should be connected.
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.
- 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%.
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.
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
Sidenote: Arabic is Right-to-left(RTL) language and that has to be acounted for when changing languages.
Useful Resources
- Repository: https://github.com/sandygudie/localisation-article
- https://lingui.dev/guides/dynamic-loading-catalogs
- https://support.crowdin.com/github-integration/
- https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations
- https://blog.logrocket.com/how-to-set-up-internationalization-in-react-using-lingui-js/