Lingui.js is a lightweight yet powerful internationalization (i18n) framework for JavaScript and React applications. It uses industry-standard PO (Portable Object) files for translation catalogs, powerful compile-time macros for clean developer experience, and produces optimized bundles under 2 kB. This guide covers everything from project setup to PO file workflows, pluralization, and automating translations with l10n.dev.
Lingui.js stands out among JavaScript i18n libraries for its combination of developer experience, performance, and professional translation workflows:
Install Lingui's core packages and development tools. The runtime packages (@lingui/core and @lingui/react) are needed in production, while the CLI, Vite plugin, and Babel macro are dev-only:
npm install @lingui/core @lingui/react
npm install --save-dev @lingui/cli @lingui/vite-plugin @lingui/babel-plugin-lingui-macroLingui requires two configuration files: the Lingui config (defining locales and catalog paths) and your build tool config (to enable compile-time macros).
Create a lingui.config.js file at your project root. This defines your source locale, target locales, where to store PO catalogs, and the catalog format. The PO format is the default and recommended choice:
// lingui.config.js
import { defineConfig } from "@lingui/cli";
import { formatter } from "@lingui/format-po";
export default defineConfig({
sourceLocale: "en",
locales: ["en", "fr", "de", "ja", "es", "zh-CN"],
catalogs: [
{
path: "<rootDir>/src/locales/{locale}/messages",
include: ["src"],
},
],
format: formatter({ lineNumbers: false }),
});If you use Vite, add the Lingui plugin alongside the React plugin with the Babel macro. This enables compile-time message transformation and on-the-fly catalog compilation:
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { lingui } from "@lingui/vite-plugin";
export default defineConfig({
plugins: [
react({
babel: {
plugins: ["@lingui/babel-plugin-lingui-macro"],
},
}),
lingui(),
],
});PO (Portable Object) is the industry-standard translation file format, originating from the GNU gettext system. Lingui uses PO as its default catalog format because it's widely supported by translation tools, TMS platforms, and professional translators worldwide.
#: src/components/WelcomeBanner.js:6
msgid "Welcome to our platform"
msgstr ""
#: src/components/WelcomeBanner.js:8
msgid "Hello {userName}, check out your <0>dashboard</0> to get started."
msgstr ""
#: src/components/CartSummary.js:5
msgid "{itemCount, plural, =0 {Your cart is empty} one {You have # item in your cart} other {You have # items in your cart}}"
msgstr ""After translation, each msgstr is populated with the localized version. Lingui preserves ICU MessageFormat syntax, indexed tags for React components, and placeholder variables across all translations.
#: src/components/WelcomeBanner.js:6
msgid "Welcome to our platform"
msgstr "Bienvenue sur notre plateforme"
#: src/components/WelcomeBanner.js:8
msgid "Hello {userName}, check out your <0>dashboard</0> to get started."
msgstr "Bonjour {userName}, consultez votre <0>tableau de bord</0> pour commencer."
#: src/components/CartSummary.js:5
msgid "{itemCount, plural, =0 {Your cart is empty} one {You have # item in your cart} other {You have # items in your cart}}"
msgstr "{itemCount, plural, =0 {Votre panier est vide} one {Vous avez # article dans votre panier} other {Vous avez # articles dans votre panier}}"Organize your PO catalogs inside a locales directory with one subfolder per locale. The Lingui CLI automatically creates and updates these files during extraction. Compiled JS modules are generated at build time and should not be edited manually.
src/
├── locales/
│ ├── en/
│ │ └── messages.po ← Source (English)
│ ├── fr/
│ │ └── messages.po ← French
│ ├── de/
│ │ └── messages.po ← German
│ ├── ja/
│ │ └── messages.po ← Japanese
│ ├── es/
│ │ └── messages.po ← Spanish
│ └── zh-CN/
│ └── messages.po ← Chinese Simplified
├── components/
├── i18n.ts
├── App.tsx
└── ...Lingui provides two main approaches for marking messages: JSX macros for component content and the useLingui hook for imperative strings. Both are compile-time macros that produce optimized output.
Wrap any JSX content in the Trans macro to make it translatable. Variables, HTML elements, and React components are fully supported - the macro transforms them into ICU MessageFormat with indexed tags automatically.
import { Trans } from "@lingui/react/macro";
function WelcomeBanner({ userName }) {
return (
<div>
<h1>
<Trans>Welcome to our platform</Trans>
</h1>
<p>
<Trans>
Hello {userName}, check out your <a href="/dashboard">dashboard</a> to
get started.
</Trans>
</p>
</div>
);
}For strings outside JSX - alert messages, aria labels, attribute values, or any imperative code - use the useLingui macro hook with tagged template literals:
import { useLingui } from "@lingui/react/macro";
function NotificationButton() {
const { t } = useLingui();
const showAlert = () => {
alert(t`Your changes have been saved.`);
};
return (
<button onClick={showAlert} aria-label={t`Save changes`}>
<Trans>Save</Trans>
</button>
);
}Lingui supports two approaches for message identification: auto-generated IDs (derived from the source message content) and explicit IDs (developer-defined keys). Both approaches work with PO files.
import { Trans } from "@lingui/react/macro";
// Auto-generated ID (from message content)
<Trans>Welcome to our platform</Trans>
// Explicit ID (developer-defined key)
<Trans id="nav.welcome">Welcome to our platform</Trans>Use the comment prop on any macro to add a description. This comment is extracted into the PO file as a translator note, providing critical context about where and how the message appears.
import { Trans } from "@lingui/react/macro";
<Trans comment="Shown on the homepage hero section above the fold">
Start building with confidence
</Trans>Lingui uses ICU MessageFormat for pluralization, which supports all CLDR plural categories. The Plural macro makes it easy to handle different plural forms directly in JSX:
import { Plural } from "@lingui/react/macro";
function CartSummary({ itemCount }) {
return (
<p>
<Plural
value={itemCount}
_0="Your cart is empty"
one="You have # item in your cart"
other="You have # items in your cart"
/>
</p>
);
}After extracting and compiling your messages, set up the i18n instance and wrap your app in the I18nProvider.
Create an i18n module that loads compiled message catalogs dynamically. The Lingui Vite plugin enables direct imports of PO files, which are compiled on the fly during development:
// src/i18n.ts
import { i18n } from "@lingui/core";
export async function loadCatalog(locale: string) {
const { messages } = await import(`./locales/${locale}/messages.po`);
i18n.load(locale, messages);
i18n.activate(locale);
}
// Initialize with default locale
loadCatalog("en");
export default i18n;Wrap your entire app with I18nProvider to make translations available to all components via React context:
// src/App.tsx
import { I18nProvider } from "@lingui/react";
import { i18n } from "./i18n";
function App() {
return (
<I18nProvider i18n={i18n}>
<YourAppContent />
</I18nProvider>
);
}Switching languages is straightforward: load the new catalog, activate the locale, and React automatically re-renders all translated content. Here's a complete language switcher component:
import { useState } from "react";
import { loadCatalog } from "./i18n";
import { Trans } from "@lingui/react/macro";
const LANGUAGES = {
en: "English",
fr: "Français",
de: "Deutsch",
ja: "日本語",
es: "Español",
};
function LanguageSwitcher() {
const [currentLocale, setCurrentLocale] = useState("en");
const handleChange = async (locale: string) => {
await loadCatalog(locale);
setCurrentLocale(locale);
};
return (
<select
value={currentLocale}
onChange={(e) => handleChange(e.target.value)}
>
{Object.entries(LANGUAGES).map(([code, name]) => (
<option key={code} value={code}>
{name}
</option>
))}
</select>
);
}Lingui's CLI provides two key commands that form the core of your translation workflow: extract to pull messages from source code into PO catalogs, and compile to convert PO files into optimized JavaScript modules for production.
# Extract messages from source code into PO catalogs
npx lingui extract
# Translate your PO files (manually, with a TMS, or with AI)
# Compile PO catalogs into optimized JS modules for production
npx lingui compilel10n.dev provides native support for PO files, making it the perfect companion for Lingui.js projects. Upload your source PO catalog and receive accurately translated files back:
Use the ai-l10n npm package to translate your Lingui PO catalogs from the command line or as part of a CI/CD pipeline. Install once, then translate to any number of languages in a single command.
# Install the CLI
npm install ai-l10n
# Translate your source PO file to multiple languages
npx ai-l10n translate src/locales/en/messages.po \
--languages fr,de,ja,zh-CN,es,koWire ai-l10n directly into your Lingui build process so translations are always up to date. Combine extraction, translation, and compilation in a single workflow.
Add extract, translate, and compile as scripts and chain them together. The i18n script runs the full pipeline; use it in prebuild to ensure translations are always current before shipping.
{
"scripts": {
"extract": "lingui extract",
"compile": "lingui compile",
"translate": "ai-l10n translate src/locales/en/messages.po --languages fr,de,ja,zh-CN,es,ko --update",
"i18n": "npm run extract && npm run translate && npm run compile",
"prebuild": "npm run i18n",
"build": "vite build",
"dev": "vite"
}
}If your team uses Make, declare extraction, translation, and compilation as separate targets with proper dependencies:
LANGUAGES = fr,de,ja,zh-CN,es,ko
extract:
npx lingui extract
translate:
npx ai-l10n translate src/locales/en/messages.po --languages $(LANGUAGES) --update
compile:
npx lingui compile
i18n: extract translate compile
dev: i18n
npx vite
build: i18n
npx vite buildFor CI/CD integration, incremental updates, batch translation across multiple files, and GitHub Actions workflow examples, see the Localization Automation Guide.
The l10n.dev VS Code extension brings PO file translation directly into your editor. Translate your Lingui catalogs without leaving VS Code.
Ready to reach global users? Translate your Lingui PO files directly in the l10n.dev workspace, automate with the npm CLI, or translate right inside VS Code:
Thank you for using l10n.dev to localize your Lingui.js project! 🚀
If this guide helped you, share it with other React developers who need to internationalize their applications with PO files.
Together, we can make JavaScript applications more accessible and ready for a global audience.