mcpField
M
migrate-i18next-to-lingui

migrate-i18next-to-lingui

github.com/lingui開発10176 回インストール1 週間前に更新
リポジトリ

migrate-i18next-to-lingui について

migrate-i18next-to-linguiは、Reactプロジェクトをi18nextからLinguiへ移行する開発者向けのスキルです。コード、カタログ、ビルドツールの変更を段階的に案内し、移行後の検証や未対応文字列の確認も扱います。

主な機能

  1. 01初期化とReactコンポーネントを段階的に移行
  2. 02文字列、複数形、名前空間を変換
  3. 03Vite、Babel、Next.jsなどのビルドツールを設定
  4. 04カタログ、型、ビルド結果を検証
  5. 05i18nextの残存と未ローカライズ文字列を確認

活用シーン

  1. 01Reactアプリをi18nextからLinguiへ移行
  2. 02既存の翻訳カタログや複数形メッセージを変換
  3. 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 の文字列を移行する

シンプルな文字列
i18nextLingui
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 プロパティを使用してください。

追加リソース

コメント