agentsclimarketplace

Migrate i18next to lingui

Skill lingui/skills/migrate-i18next-to-lingui

Agent Skills to help developers using AI agents with Lingui

Install
npx -y skills add lingui/skills --skill migrate-i18next-to-lingui

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

8.8 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it

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

# Core (always required)
npm install @lingui/core @lingui/react

# CLI (dev)
npm install --save-dev @lingui/cli

# Macro support - pick one based on build tool:
# Babel
npm install --save-dev babel-plugin-macros
# SWC
npm install --save-dev @lingui/swc-plugin
# Vite
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"], // match your existing locales
  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

useTranslationuseLingui

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

i18nextLingui
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";

// i18next: t('navigation.home')
const msg = t({ id: "navigation.home", message: "Home" });

Module-level / lazy strings

Before (i18next):

const LABELS = {
  active: "Active",
  inactive: "Inactive",
};
// translated at render time

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):

// lingui.config.js
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    # extracts all messages → .po files
npx lingui compile    # compiles .po → .js message catalogs

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>
// or
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

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.