migrate-i18next-to-lingui について
migrate-i18next-to-linguiは、Reactプロジェクトをi18nextからLinguiへ移行する開発者向けのスキルです。コード、カタログ、ビルドツールの変更を段階的に案内し、移行後の検証や未対応文字列の確認も扱います。
主な機能
- 01初期化とReactコンポーネントを段階的に移行
- 02文字列、複数形、名前空間を変換
- 03Vite、Babel、Next.jsなどのビルドツールを設定
- 04カタログ、型、ビルド結果を検証
- 05i18nextの残存と未ローカライズ文字列を確認
活用シーン
- 01Reactアプリをi18nextからLinguiへ移行
- 02既存の翻訳カタログや複数形メッセージを変換
- 03ビルド設定を検証し、未対応のUI文字列を検出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lingui/ skills --skill migrate-i18next-to-linguiインストール後、Claude Code で/migrate-i18next-to-linguiを入力して読み込まれたか確認します。
同梱ファイルreferences/catalog-conversion.mdreferences/migration-patterns.md
詳細
i18next から Lingui への移行
移行チェックリスト
- [ ] 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
ステップ 1:Lingui をインストールする
Node バージョンの条件:Lingui 6 は ESM のみで、Node.js ≥ 22.19(または ≥ 24)が必要です。古い Node を使用する場合は、すべての
@lingui/*パッケージを^5に固定してください。
# 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 @lingui/babel-plugin-lingui-macro
# SWC
npm install --save-dev @lingui/swc-plugin
# Vite (also install the Babel macro plugin above — see Step 3)
npm install --save-dev @lingui/vite-plugin
ステップ 2: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"],
},
],
});
ステップ 3:ビルドツールを設定する
Vite(vite.config.ts):
import { lingui } from "@lingui/vite-plugin";
import react from "@vitejs/plugin-react";
export default {
plugins: [
react({ babel: { plugins: ["@lingui/babel-plugin-lingui-macro"] } }),
lingui(),
],
};
lingui() だけではマクロは変換されません。react() 内の Babel マクロプラグインが変換を行います(@vitejs/plugin-react@^5 が必要です。v6 では babel オプションが削除されたため、代わりに SWC 版を使用してください)。
lingui() プラグインは .po カタログもその場でコンパイルするため、Vite ではアプリからカタログを直接インポートでき、lingui compile の手順を完全に省略できます。
// Dynamic import; the .po extension is mandatory
const { messages } = await import(`./locales/${locale}/messages.po`);
TypeScript で .po のインポートを型チェックするには、モジュールを宣言してください。
// src/vite-env.d.ts
declare module "*.po" {
import type { Messages } from "@lingui/core";
export const messages: Messages;
}
Babel(.babelrc または babel.config.js):
{ "plugins": ["macros"] }
SWC を使用する Next.js(next.config.js):
module.exports = {
experimental: {
swcPlugins: [["@lingui/swc-plugin", {}]],
},
};
ビルドツールに関する注意事項:@lingui/swc-plugin は使用する SWC ランタイムとバージョンを一致させる必要があります。プラグインのエントリーは [name, options] のタプルでなければならず、文字列だけを指定するとマクロが警告なく無効になります。また、@vitejs/plugin-react@6 では Babel のサポートが完全に削除されています。3 点すべてについては、swc-plugin-compatibility スキルを参照してください。
ステップ 4:i18n の初期化を置き換える
変更前(i18next):
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
i18next.use(initReactI18next).init({
lng: "en",
resources: { en: { translation: { key: "Hello world" } } },
});
変更後(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>;
}
ロケールの動的な切り替え:
async function changeLocale(locale) {
const { messages } = await import(`./locales/${locale}/messages`);
i18n.load(locale, messages);
i18n.activate(locale);
}
ステップ 5:React コンポーネントを移行する
useTranslation → useLingui
変更前:
import { useTranslation } from "react-i18next";
function MyComponent() {
const { t } = useTranslation();
return <p>{t("greeting")}</p>;
}
変更後:
import { useLingui } from "@lingui/react/macro";
function MyComponent() {
const { t } = useLingui();
return <p>{t`Hello World`}</p>;
}
Trans コンポーネント
変更前:
import { Trans } from "react-i18next";
<Trans i18nKey="welcome">Hello World!</Trans>
変更後:
import { Trans } from "@lingui/react/macro";
<Trans>Hello World!</Trans>
明示的な ID を使用する場合(i18next のキーを保持するとき):
<Trans id="welcome">Hello World!</Trans>
補間を含む Trans
変更前:
<Trans i18nKey="greeting" values={{ name }}>Hello {{ name }}!</Trans>
変更後:
<Trans>Hello {name}!</Trans>
ステップ 6:JS/TS の文字列を移行する
シンプルな文字列
| i18next | Lingui |
|---|---|
t('key') | t\Message text`` |
t('key', { name }) | t\Hello ${name}`` |
t('key', { defaultValue: 'Hi' }) | t\Hi`` |
変更前:
import i18next from "i18next";
const msg = i18next.t("greeting", { name: "Tom" });
変更後:
import { useLingui } from "@lingui/react/macro";
function MyComponent() {
const { t } = useLingui();
const msg = t`Hello ${name}`;
}
バニラ JS(コンポーネント外):
import { t } from "@lingui/core/macro";
const msg = t`Hello ${name}`;
i18next の明示的なキーを保持する
i18next のメッセージ ID を保持する場合:
import { t } from "@lingui/core/macro";
// i18next: t('navigation.home')
const msg = t({ id: "navigation.home", message: "Home" });
モジュールレベル/遅延文字列
変更前(i18next):
const LABELS = {
active: "Active",
inactive: "Inactive",
};
// translated at render time
変更後(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>;
}
ステップ 7:複数形を移行する
i18next では、複数形の形式ごとに個別のキーを使用します。Lingui では、1 つのメッセージ内で ICU MessageFormat を使用します。
変更前(i18next):
{
"item_one": "{{count}} item",
"item_other": "{{count}} items"
}
t("item", { count });
変更後(Lingui) - JSX:
import { Plural } from "@lingui/react/macro";
<Plural value={count} one="# item" other="# items" />
変更後(Lingui) - JS 文字列:
import { plural } from "@lingui/core/macro";
const msg = plural(count, {
one: "# item",
other: "# items",
});
完全一致(i18next の _0):
<Plural
value={count}
_0="No items"
one="# item"
other="# items"
/>
ステップ 8:名前空間を移行する
i18next の名前空間(useTranslation('common'))は、Lingui のカタログパスに対応します。方法は 2 つあります。
オプション A - 単一カタログ(最もシンプル):
名前空間を削除し、統合カタログを 1 つ使用します。すべてのソースディレクトリを含めるように lingui.config.js を更新してください。
オプション B - 複数カタログ(名前空間の分離を維持):
// lingui.config.js
catalogs: [
{
path: "<rootDir>/src/locales/{locale}/common",
include: ["src/components/common"],
},
{
path: "<rootDir>/src/locales/{locale}/auth",
include: ["src/components/auth"],
},
]
複数のカタログを使用する場合は、起動時にすべてを読み込みます。
import { messages as commonMessages } from "./locales/en/common";
import { messages as authMessages } from "./locales/en/auth";
i18n.load("en", { ...commonMessages, ...authMessages });
i18n.activate("en");
ステップ 9:既存の翻訳カタログを変換する
パターンについては、catalog-conversion.md を参照してください。
重要な概念: i18next はドット区切りのキーを持つ JSON を使用します。Lingui は、メッセージを ID とする .po ファイル(または指定した明示的な ID)を使用します。
ステップ 10:ビルドして検証する
以下の順序で完全な検証ループを実行してください。各段階では、その前の段階では検出できない問題を確認します。
npx lingui extract --clean # extracts all messages → .po files, drops obsolete entries
npx lingui compile # compiles .po → runtime message catalogs
npx tsc --noEmit # type errors from changed imports/APIs
npm run build # macro transform actually runs in the real build
@lingui/vite-plugin を使用して Vite で .po カタログを直接インポートする場合(ステップ 3)、lingui compile は省略してください。プラグインが開発時/ビルド時にコンパイルするため、管理が必要なコンパイル済みカタログファイルはありません。
package.json に追加します。
{
"scripts": {
"i18n:extract": "lingui extract",
"i18n:compile": "lingui compile"
}
}
TypeScript の場合:
npx lingui compile --typescript
移行で見落とした文字列を探す
ビルドが成功しても、移行した文字列が動作することが確認できるだけで、すべての文字列が移行されたとは限りません。次の2つを確認します。
1. i18next の残存がないこと。 以下はすべて何も出力しないはずです。
grep -rn "from ['\"]i18next\|from ['\"]react-i18next" src/
grep -rn "useTranslation\|i18nKey\|i18next.t(" src/
2. ユーザーに表示される文字列がラップされずに残っていないこと。 ツリー全体に eslint-plugin-lingui の no-unlocalized-strings ルールを実行します。このルールは、そもそも i18next に含まれていなかったハードコード文字列に加え、プレーンな文字列のまま残っている defaultValue のテキストも検出します。
// eslint.config.js
import pluginLingui from "eslint-plugin-lingui";
export default [
{
plugins: { lingui: pluginLingui },
rules: {
"lingui/no-unlocalized-strings": ["warn", {
ignore: ["^[A-Z0-9_-]+$"], // enums, constants
ignoreNames: ["className", "src", "data-testid"],
ignoreFunctions: ["console.*", "cn", "cva"],
}],
},
},
];
このスキャンは意図的に広範囲を対象とします。検出結果をそれぞれ確認し、ラップするか、UI に表示される文字列ではないこと(クラス名、キー、URL)を確認してください。その後も、このプラグインを恒久的なガードレールとしてインストールしたままにします。意図的にラップしない文字列があれば、黙ってスキップせずに報告してください。
調整済みのスキャナー設定、範囲を限定した修正ラウンド、残存項目の報告など、監査の全ループについては find-unwrapped-strings スキルを使用してください。上記のチェックは、その移行に限定した一部です。
手順 11:i18next を削除する
npm uninstall i18next react-i18next
よく使うパターンのリファレンス
日付/数値のフォーマット
変更前(i18next):
t("intlDateTime", { val: new Date() });
変更後(Lingui):
import { useLingui } from "@lingui/react/macro";
function Component() {
const { i18n } = useLingui();
return <span>{new Intl.DateTimeFormat(i18n.locale).format(date)}</span>;
}
コンテキスト(意味の区別)
変更前(i18next):
t("right", { context: "direction" });
変更後(Lingui):
<Trans context="direction">right</Trans>
// or
t({ message: "right", context: "direction" });
性別/選択
変更前(i18next):
{ "pronoun_male": "He", "pronoun_female": "She", "pronoun_other": "They" }
変更後(Lingui):
import { select } from "@lingui/core/macro";
const pronoun = select(gender, {
male: "He",
female: "She",
other: "They",
});
注意点
- **モジュールレベルで
t\...`を呼び出さないでください。** 代わりにmsg`...`を使用し、レンダリング時に_(descriptor)` で翻訳します。 - ロケールを変更した後は、
i18n.load(locale, messages)とi18n.activate(locale)の両方を呼び出します。 - コンポーネントで
useLinguiまたはTransを使用する前に、 アプリ全体を<I18nProvider i18n={i18n}>でラップします。 - 本番環境向けにビルドする前に、必ず
lingui compileを実行してください。 アプリがインポートするのはコンパイル済みの.jsカタログであり、.poファイルではありません。 - メッセージテキストを変更すると、生成 ID も変わります。 安定性が重要な場合は、明示的な
idプロパティを使用してください。