| name | migrate-i18next-to-lingui |
| description | Migrate i18next/react-i18next projects to Lingui. Use when the user wants to replace i18next, react-i18next, useTranslation, i18n.t(), Trans i18nKey, or i18next JSON catalogs with Lingui equivalents. Covers package installation, config setup, code transformation patterns, plural migration, namespace handling, and catalog conversion. |
Migrate i18next to Lingui
Migration Checklist
- [ ] Step 1: Install Lingui packages
- [ ] Step 2: Create lingui.config.js
- [ ] Step 3: Set up build tooling (Babel/SWC/Vite)
- [ ] Step 4: Replace i18n initialization
- [ ] Step 5: Migrate React components (useTranslation → useLingui, Trans → Trans)
- [ ] Step 6: Migrate JS/TS strings (t() → t``)
- [ ] Step 7: Migrate plurals
- [ ] Step 8: Migrate namespaces
- [ ] Step 9: Convert existing translation catalogs
- [ ] Step 10: Run lingui extract && lingui compile
- [ ] Step 11: Remove i18next packages
Step 1: Install Lingui
npm install @lingui/core @lingui/react
npm install --save-dev @lingui/cli
npm install --save-dev babel-plugin-macros
npm install --save-dev @lingui/swc-plugin
npm install --save-dev @lingui/vite-plugin
Step 2: Create lingui.config.js
import { defineConfig } from "@lingui/cli";
export default defineConfig({
sourceLocale: "en",
locales: ["en", "de", "fr"],
catalogs: [
{
path: "<rootDir>/src/locales/{locale}/messages",
include: ["src"],
},
],
});
Step 3: Configure Build Tooling
Vite (vite.config.ts):
import { lingui } from "@lingui/vite-plugin";
import react from "@vitejs/plugin-react";
export default { plugins: [react(), lingui()] };
Babel (.babelrc or babel.config.js):
{ "plugins": ["macros"] }
Next.js with SWC (next.config.js):
module.exports = {
experimental: {
swcPlugins: [["@lingui/swc-plugin", {}]],
},
};
Step 4: Replace i18n Initialization
Before (i18next):
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "en",
resources: { en: { translation: { key: "Hello world" } } },
});
After (Lingui):
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { messages } from "./locales/en/messages";
i18n.load("en", messages);
i18n.activate("en");
function App() {
return <I18nProvider i18n={i18n}>{/* app */}</I18nProvider>;
}
Dynamic locale switching:
async function changeLocale(locale) {
const { messages } = await import(`./locales/${locale}/messages`);
i18n.load(locale, messages);
i18n.activate(locale);
}
Step 5: Migrate React Components
useTranslation → useLingui
Before:
import { useTranslation } from "react-i18next";
function MyComponent() {
const { t } = useTranslation();
return <p>{t("greeting")}</p>;
}
After:
import { useLingui } from "@lingui/react/macro";
function MyComponent() {
const { t } = useLingui();
return <p>{t`Hello World`}</p>;
}
Trans component
Before:
import { Trans } from "react-i18next";
<Trans i18nKey="welcome">Hello World!</Trans>
After:
import { Trans } from "@lingui/react/macro";
<Trans>Hello World!</Trans>
For explicit IDs (when preserving i18next keys):
<Trans id="welcome">Hello World!</Trans>
Trans with interpolation
Before:
<Trans i18nKey="greeting" values={{ name }}>Hello {{ name }}!</Trans>
After:
<Trans>Hello {name}!</Trans>
Step 6: Migrate JS/TS Strings
Simple strings
| i18next | Lingui |
|---|
t('key') | t\Message text`` |
t('key', { name }) | t\Hello ${name}`` |
t('key', { defaultValue: 'Hi' }) | t\Hi`` |
Before:
import i18next from "i18next";
const msg = i18next.t("greeting", { name: "Tom" });
After:
import { useLingui } from "@lingui/react/macro";
function MyComponent() {
const { t } = useLingui();
const msg = t`Hello ${name}`;
}
In vanilla JS (outside components):
import { t } from "@lingui/core/macro";
const msg = t`Hello ${name}`;
Preserving explicit keys from i18next
If you want to keep the i18next message IDs:
import { t } from "@lingui/core/macro";
const msg = t({ id: "navigation.home", message: "Home" });
Module-level / lazy strings
Before (i18next):
const LABELS = {
active: "Active",
inactive: "Inactive",
};
After (Lingui):
import { msg } from "@lingui/core/macro";
import { useLingui } from "@lingui/react";
const LABELS = {
active: msg`Active`,
inactive: msg`Inactive`,
};
function StatusDisplay({ status }) {
const { _ } = useLingui();
return <div>{_(LABELS[status])}</div>;
}
Step 7: Migrate Plurals
i18next uses separate keys per plural form. Lingui uses ICU MessageFormat in a single message.
Before (i18next):
{
"item_one": "{{count}} item",
"item_other": "{{count}} items"
}
t("item", { count });
After (Lingui) - JSX:
import { Plural } from "@lingui/react/macro";
<Plural value={count} one="# item" other="# items" />
After (Lingui) - JS strings:
import { plural } from "@lingui/core/macro";
const msg = plural(count, {
one: "# item",
other: "# items",
});
Exact matches (i18next _0):
<Plural
value={count}
_0="No items"
one="# item"
other="# items"
/>
Step 8: Migrate Namespaces
i18next namespaces (useTranslation('common')) map to Lingui catalog paths. Two approaches:
Option A - Single catalog (simplest):
Remove namespaces and use one unified catalog. Update lingui.config.js to include all source directories.
Option B - Multiple catalogs (preserves namespace separation):
catalogs: [
{
path: "<rootDir>/src/locales/{locale}/common",
include: ["src/components/common"],
},
{
path: "<rootDir>/src/locales/{locale}/auth",
include: ["src/components/auth"],
},
]
With multiple catalogs, load them all at startup:
import { messages as commonMessages } from "./locales/en/common";
import { messages as authMessages } from "./locales/en/auth";
i18n.load("en", { ...commonMessages, ...authMessages });
i18n.activate("en");
Step 9: Convert Existing Translation Catalogs
See catalog-conversion.md for and patterns.
Key concept: i18next uses JSON with dotted keys; Lingui uses .po files with the message as the ID (or an explicit ID you provide).
Step 10: Build & Verify
npx lingui extract
npx lingui compile
Add to package.json:
{
"scripts": {
"i18n:extract": "lingui extract",
"i18n:compile": "lingui compile"
}
}
For TypeScript:
npx lingui compile --typescript
Step 11: Remove i18next
npm uninstall i18next react-i18next
Common Patterns Reference
Date/Number Formatting
Before (i18next):
t("intlDateTime", { val: new Date() });
After (Lingui):
import { useLingui } from "@lingui/react/macro";
function Component() {
const { i18n } = useLingui();
return <span>{new Intl.DateTimeFormat(i18n.locale).format(date)}</span>;
}
Context (disambiguation)
Before (i18next):
t("right", { context: "direction" });
After (Lingui):
<Trans context="direction">right</Trans>
t({ message: "right", context: "direction" });
Gender / Select
Before (i18next):
{ "pronoun_male": "He", "pronoun_female": "She", "pronoun_other": "They" }
After (Lingui):
import { select } from "@lingui/core/macro";
const pronoun = select(gender, {
male: "He",
female: "She",
other: "They",
});
Pitfalls
- **Don't call
t\...`at module level.** Usemsg`...`instead and translate with_(descriptor)` at render time.
- After locale change, call both
i18n.load(locale, messages) and i18n.activate(locale).
- Wrap the entire app in
<I18nProvider i18n={i18n}> before any component uses useLingui or Trans.
- Always run
lingui compile before building for production; the app imports compiled .js catalogs, not .po files.
- Generated IDs change if message text changes. If stability matters, use explicit
id props.
Additional Resources