Internationalization (i18n) is the process of making a website or application adaptable to various different languages.
Across the world, people understand and use different languages in their daily lives. Americans may not necessarily understand Indonesian, and vice versa, Indonesians may not necessarily understand English.
Therefore, with the implementation of internationalization, we can create a website or application that supports languages people understand, allowing them to use it effectively according to their needs.
Implementation Steps
To implement internationalization in a React project, we can use a library called LinguiJS.
This library has several advantages, including:
- Can be used in general JavaScript projects.
- Can be used in frameworks other than React.
- Comes with good supporting tools to facilitate i18n implementation.
- Easy to use.
Alright, let's proceed with the implementation in the NextJS 14 framework (using the App Router). First, we need to install the library using npm through the terminal.
npm install --save-dev @lingui/cli
npm install --save-dev @lingui/macro @lingui/swc-plugin
npm install --save @lingui/react
To add new npm scripts to the package.json file, and to update the "build" script for compiling message catalogs before the NextJS build process, follow these steps. This is done to avoid errors during deployment to Vercel.
{
"scripts": {
"build": "npm run lingui:compile && next build",
"lingui:extract": "lingui extract",
"lingui:compile": "lingui compile"
}
}
Add the @lingui/swc-plugin to the next.config.js file as follows.
/** @type {import('next').NextConfig} */
const nextConfig = {
// ...Other config that you have
experimental: {
swcPlugins: [["@lingui/swc-plugin", {}]],
},
};
module.exports = nextConfig;
Next, create a lingui.config.js file in the project's root. This file contains LinguiJS configurations for your project.
/** @type {import('@lingui/conf').LinguiConfig} */
module.exports = {
locales: ["en", "id"],
sourceLocale: "en",
catalogs: [
{
path: "<rootDir>/app/locales/{locale}/messages",
include: ["app"],
},
],
format: "po",
};
Explanation regarding each property in the configuration:
- locales are the languages that will be applied to your project. Here, we use "en" for English and "id" for Indonesian.
- sourceLocale is the language used as the message ID, eliminating the need to manually define message IDs. Here, we use English sentences as message IDs.
- catalogs is the location where we'll store the message catalog files.
- format is the type of format used for the message catalog. Here, we use the "po" format, which is highly recommended.
Optionally, if you are using ESLint, you can install the LinguiJS plugin for ESLint and add the following configuration to your ESLint configuration file in your project, such as .eslintrc.json.
npm install --save-dev eslint-plugin-lingui
{
"plugins": ["lingui"]
}
Then, add the following paths to the .gitignore file to exclude translation results from being included in git commits.
app/locales/**/messages.js
Next, we need to create a Provider component at app/components/Provider.tsx.
"use client";
import { FC } from "react";
import { I18nProvider } from "@lingui/react";
import { i18n } from "@lingui/core";
// Import the compiled message catalog.
import { messages as enMessages } from "../locales/en/messages";
import { messages as idMessages } from "../locales/id/messages";
// Load all message catalogs.
i18n.load({
en: enMessages,
id: idMessages,
});
// Activate English as the default language.
i18n.activate("en");
type Props = {
children: JSX.Element;
};
const Provider: FC<Props> = ({ children }) => {
return (
<I18nProvider i18n={i18n}>
{children}
</I18nProvider>
);
};
export default Provider;
Use the Provider component in app/layout.tsx.
import { FC } from "react";
import Provider from "./components/Provider";
type Props = {
children: JSX.Element;
};
const RootLayout: FC<Props> = ({ children }) => {
return (
<html>
<body>
<Provider>{children}</Provider>
</body>
</html>
);
};
export default RootLayout;
After performing the above configurations, we can now implement them in the components that require it. For example, suppose we have a component like the following.
"use client"
import { FC } from "react";
import ContentBody from "./ContentBody";
const Content: FC = () => {
return (
<div>
<h1>Learning LinguiJS</h1>
<ContentBody value="LinguiJS is really good" />
</div>
);
};
export default Content;
We can modify it to use LinguiJS like this.
"use client"
import { FC } from "react";
import { Trans, msg } from "@lingui/macro";
import { useLingui } from "@lingui/react";
import ContentBody from "./ContentBody";
const Content: FC = () => {
const { _ } = useLingui();
return (
<div>
<h1>
<Trans>Learning LinguiJS</Trans>
</h1>
<ContentBody value={_(msg`LinguiJS is really good`)} />
</div>
);
};
export default Content;
After that, we need to run the following command to extract the sentences that will be translated.
npm run lingui:extract
The command also produces output in the form of a table with columns such as "Language," "Total Count," and "Missing." "Language" represents the languages supported in this project, which are English and Indonesian. "Total Count" indicates the total number of sentences to be translated. "Missing" indicates the number of sentences that have not been translated yet.
The command will generate several message catalog files in the "po" format in the directory you specified in the lingui.config.js configuration, which is in the app/locales/en and app/locales/id directories.
Here's an example from the file app/locales/en/messages.po.
#: app/components/Content.tsx:15
msgid "Learning LinguiJS"
msgstr "Learning LinguiJS"
#: app/components/Content.tsx:18
msgid "LinguiJS is really good"
msgstr "LinguiJS is really good"
Here's an example from the file app/locales/id/messages.po.
#: app/components/Content.tsx:15
msgid "Learning LinguiJS"
msgstr ""
#: app/components/Content.tsx:18
msgid "LinguiJS is really good"
msgstr ""
The empty sections in the file app/locales/id/messages.po can be filled with their translations.
#: app/components/Content.tsx:15
msgid "Learning LinguiJS"
msgstr "Belajar LinguiJS"
#: app/components/Content.tsx:18
msgid "LinguiJS is really good"
msgstr "LinguiJS sangat bagus"
After that, we need to compile the message catalog files with the following command.
npm run lingui:compile
The command will generate the messages.js file in the app/locales/en and app/locales/id directories. These files will be loaded into the previously created Provider.
With this, you've successfully created a website with support for multiple languages.
To switch languages, you can use the following example. If needed, combine it with Zustand + Persist middleware to store the selected language in localStorage. This ensures that when the web page is refreshed, the language remains the previously chosen one.
"use client"
import { FC } from "react";
import { useLingui } from "@lingui/react";
const LanguageSwitcher: FC = () => {
const { i18n } = useLingui();
return (
<div>
<button
type="button"
onClick={() => i18n.activate("en")}
>
EN
</button>
<button
type="button"
onClick={() => i18n.activate("id")}
>
ID
</button>
</div>
);
};
export default LanguageSwitcher;
Here, we can see that the workflow in broad strokes is as follows:
- Define the sentences to be translated using the Trans macro or other macros.
- Run the command to extract these sentences.
- Open the messages.po file in the app/locales/id directory.
- Fill in the translations in the empty quotation sections.
- Run the command to compile the message catalog.
LinguiJS provides numerous features, and you can explore them further in its official documentation at https://lingui.dev/introduction.
Conclusion
Internationalization is crucial for making websites or applications accessible to users from various countries worldwide. With LinguiJS, the implementation of internationalization becomes more straightforward.