著者:
    作成:2026-09-13最終更新:2026-09-13

    i18next VS @intlayer/i18next | 同じAPI、異なるバンドルサイズ

    @intlayer/i18next@intlayer/react-i18next@intlayer/next-i18next は互換アダプターです。既存のコードが使用している i18next のAPI(useTranslationt()<Trans>i18n.changeLanguage()getFixedTserverSideTranslations など)をそのまま公開し、Intlayerによってコンパイルされた辞書からデータを提供します。コンポーネント自体は変更されず、内部のランタイムのみが置き換わります。

    本記事では、同一のNext.jsアプリケーションを next-i18next@intlayer/next-i18next のそれぞれでビルドしてその差異を測定しました。数値は Benchmark Bloom から引用しています。ライブラリ単体としての i18next と Intlayer の比較については i18next vs Intlayer をご覧ください。本記事では、既存のコードをそのまま維持した状態でアダプターが何をもたらすかに焦点を当てます。

    要約 (tl;dr): 同一のNext.jsアプリにおいて、next-i18next@intlayer/next-i18next に置き換えることで、ページごとのJavaScriptサイズがgzipで 218.5 KB から 150.7 KB へ削減され(基本構成比)、完全に最適化された next-i18next の構成(163.4 KB)よりもさらに 12.7 KB 小さくなりました。コンポーネントの平均サイズは 78.5 KB から 9.7 KB に激減し、他ページの文字列リークは ~90% から 0% に、ハイドレーション時間は 15.6 ms から 11.3 ms に、ランタイム自体も 19.7 KB から 9.4 KB に縮小しました。コンポーネントの書き換えは不要で、Providerファイルを1つ差し替えるだけで導入可能です。i18next のプラグイン(バックエンド、言語検出器)は受け入れられますが何もしません。ランタイム時にロードや検出を行う必要がなくなるためです。

    @intlayer/i18next とは

    i18next はランタイムです。i18n.init({ resources }) やバックエンドプラグインによって locales/{lng}/{ns}.json がグローバルインスタンスに読み込まれ、useTranslation("about") でコンポーネントが購読し、t("title") がレンダリング時にキーを検索します。名前空間、遅延読み込み、ページごとの名前空間リスト、型安全性はすべて開発者が手動で構成・維持する必要があります。

    互換アダプターはAPIを維持しつつ、そのインスタンスを置き換えます。

    1. インポートのエイリアス化。 @intlayer/next-i18next/plugincreateNextI18nPlugin()(または withI18next)が withIntlayer をラップし、Webpack / Turbopack のエイリアスを追加することで、next-i18nextreact-i18nexti18next がそれぞれの @intlayer/* パッケージに解決されるようにします。Vite環境では @intlayer/react-i18next/pluginreactI18nextVitePlugin() が同様の処理を行います。インポート文を変更する必要はありません。
    2. 信頼できる唯一の情報源としてのJSON。 syncJSON プラグインが既存の locales/{lng}/{ns}.jsonformat: "i18next" で読み込み({{name}}$t() のネスト、_one / _other、コンテキストサフィックスを正常に解析)、CLIやCMSによる更新時に翻訳を書き戻します。
    3. コールサイト(呼び出し箇所)でのバインディング。 Intlayerの最適化パスが useTranslation("about") を書き換え、アクティブなロケールにおける about 辞書を直接受け取る呼び出しに変換します。これにより、コンポーネントはグローバルストアを参照しなくなります。
    components/About.tsx
    // あなたのコード(変更なし)
    import { useTranslation } from "react-i18next";
    
    const About = () => {
      const { t } = useTranslation("about");
      return <h1>{t("title")}</h1>;
    };
    
    コンパイラが出力するコード(簡略化版)
    import _dicHash_about from "../.intlayer/dictionaries/about.mjs";
    import { useDictionary as useTranslation } from "@intlayer/react-i18next";
    
    const About = () => {
      const { t } = useTranslation(_dicHash_about);
      return <h1>{t("title")}</h1>;
    };
    

    このコード書き換えこそが、後述のコンポーネントサイズ縮小やページリーク解消の原動力となっています。

    アダプターが保持、無視、および代替しない機能

    i18next API@intlayer/* を適用した場合
    useTranslation("ns"), useTranslation("ns", { keyPrefix })✅ 保持。ビルド時に ns 辞書へバインドされ、コンテンツに沿って型付けされます
    t("key", { name }), {{interpolation}}, $t(key) ネスト✅ 保持
    key_one / key_other 複数形、key_male コンテキスト、returnObjects✅ 保持。複数形は Intl.PluralRules で評価されます
    components、番号付きタグ <1>...</1>values を持つ <Trans>✅ 保持
    withTranslation, Translation, I18nContext✅ 保持
    i18n.changeLanguage(), i18n.language, i18n.dir(), on("languageChanged")✅ 保持。changeLanguage がIntlayerのロケールを切り替えます
    getFixedT(lng, ns, keyPrefix), i18n.exists(), hasLoadedNamespace()✅ 保持
    i18n.use(Backend).use(LanguageDetector).init({...})⚠️ use() はプラグインの init を呼んで即終了します。読み込みや検出の対象がなくなるためです
    init({ resources }), addResourceBundle()⚠️ resources は警告付きで無視されます。バンドル削減の恩恵を得るためにJSONインポートを削除してください
    I18nextProvider i18n={i18n}⚠️ IntlayerProvider をレンダリングします。i18n プロパティは無視されます。App Routerではロケールを渡します(後述)
    serverSideTranslations(locale, ["common"]) (next-i18next)⚠️ 期待通りのオブジェクト構造を返しますが何も読み込みません。残しても削除しても無害です
    appWithTranslation(App) (next-i18next)✅ 保持
    next-i18next.config.js⚠️ 読み込まれません。ロケール設定は intlayer.config.ts で管理します
    名前空間を指定しない素の useTranslation()✅ ファイル全体の translation 辞書に対して解決されます(splitKeys: false

    ベンチマーク

    測定対象

    Benchmark Bloom スイートは、各構成で同一のアプリケーションをビルドして検証しています。10ページ(home、about、blog、careers、contact、FAQ、pricing、products、settings、team)、10ロケールenfresdeitptzhjakoru)、同一のコンポーネントおよびコンテンツで構成され、enfr のページを測定しています。

    next-i18next は、全ロケールのJSONを resources に直接インポートする構成(static)から、ルートごとに名前空間を分離してバックエンド経由で遅延ロードする構成(scoped-dynamic)まで、4つの読み込み戦略でビルドされました。アダプターは基本構成と同一のコンポーネントを用い、next.config.tsintlayer.config.ts、およびProviderファイルのみを変更してビルドされました。コンパイラがコンポーネント単位でコンテンツをスコープ化するため、手動の "scoped" バリアントは不要です。

    各ビルドで以下の指標を記録しています。

    • Lib size: i18nライブラリのみをインポートした空コンポーネントのgzipサイズ。
    • Page JS: 全ページ・全ロケールの平均ダウンロードJavaScriptサイズ(gzip)。
    • Locale leak %: ダウンロードされたJSのうち、ユーザーが閲覧していないロケールに属する翻訳文字列の割合。
    • Page leak %: ダウンロードされたJSのうち、ユーザーが滞在していないページに属する翻訳文字列の割合。
    • Component avg: 各コンポーネントを個別にコンパイルした場合の平均gzipサイズ。
    • E2E reactivity: 新しいロケールを選択してからDOM内の html[lang] が更新されるまでの実測時間(Playwright、5回試行)。
    • Hydration: Reactのハイドレーションフェーズにかかる時間。
    下記の測定値は 2026-09-12 時点のもので、next-i18next 16.3.0(react-i18next 17.0.13、i18next 26.4.2)および @intlayer/next-i18next 9.5.1 を使用しています。テストアプリは意図的に軽量化(1言語あたり数十行)されているため、リーク率は傾向を示しています。コンテンツが増えるほどリーク量は膨張しますが、ランタイムの固定コストは変わりません。

    Next.js での検証結果

    構成戦略Libサイズ (gz)ページJS平均 (gz)ロケールリークページリークコンポーネント平均 (gz)E2E反応速度ハイドレーション
    base (i18nなし)-0.0 KB141.0 KB0.0%0.0%0.9 KB13.4 ms11.8 ms
    next-i18nextstatic19.7 KB218.5 KB0.0%89.8%78.5 KB16.4 ms15.6 ms
    next-i18nextdynamic19.7 KB169.5 KB50.0%89.8%26.1 KB15.4 ms27.7 ms
    next-i18nextscoped-static19.7 KB220.1 KB0.0%89.8%78.9 KB16.4 ms14.7 ms
    next-i18nextscoped-dynamic19.7 KB163.4 KB0.0%0.0%27.1 KB15.9 ms15.1 ms
    @intlayer/next-i18nextstatic9.4 KB150.7 KB0.0%0.0%9.7 KB10.7 ms11.3 ms
    @intlayer/next-i18nextdynamic9.4 KB150.7 KB0.0%0.0%9.7 KB11.9 ms10.6 ms
    next-intlayer (ネイティブ)static5.5 KB141.3 KB0.0%0.0%8.5 KB15.5 ms16.9 ms
    next-intlayer (ネイティブ)dynamic5.5 KB141.3 KB0.0%0.0%6.9 KB15.3 ms15.9 ms

    データの読み解き方

    • 基本構成比でページあたり68 KBの軽量化。 resources: { en, fr, ... } の構成では、すべてのロケールと全名前空間が毎ページ送信され 218.5 KB に達します。同一コンポーネントをアダプター経由でビルドすると 150.7 KB に抑制されます。また、i18next のランタイム単体で19.7 KBあるのに対しアダプターは9.4 KBであるため、next-i18next の最も綿密な最適化構成(163.4 KB)よりもさらに12.7 KB軽量です。
    • コンポーネントに手を加えることなくリーク率0%を達成。 手動で厳密に分割した設定を除き、従来の next-i18next は約90%もの他ページ用文字列を同梱してしまいます。dynamic 設定ではページ間リークが残る上に、言語別バックエンドが translation 名前空間全体を取得するため 50%のロケールリーク が新たに発生します。アダプター構成では、既存コードのままで 0% / 0% を達成します。
    • コンポーネントが約8分の1のサイズに。 単体コンパイルされた useTranslation() コンポーネントは、t がグローバルストアに束縛されているため、インライン resources で平均 78.5 KB、バックエンド構成でも 26〜27 KB に達します。アダプターを導入すると平均 9.7 KB まで激減します。
    • ハイドレーションと切り替え速度の向上。 ハイドレーション時間は15.6 msから 11.3 ms に短縮されます(バックエンド取得がクリティカルパスに乗る dynamic の27.7 msと比較すると大幅な改善)。ロケール切り替えも15〜16 msから 11〜12 ms へ高速化します。
    • アダプターはネイティブランタイムとは別物。 next-intlayer はベースアプリ比わずか+0.3 KBの 141.3 KB です。アダプターはIntlayerのコア上に i18next のAPI互換レイヤー(補間構文、複数形・コンテキスト解決、<Trans> 解析)を保持しているため、ネイティブ比で+9.4 KBとなります。これは移行のための架け橋であり、最終ゴールではありません。
    Vite / TanStack Start 環境での react-i18next アダプターはこのテストには含まれていません。TanStack Start における基準値は i18next vs Intlayer で確認できます。

    なぜ数値が改善するのか

    components/ 配下のソースコードは一切変わっていません。この改善は、useTranslation が何にバインドされているかの違いによるものです。

    i18next の場合、コンポーネントはグローバルインスタンスにバインドされます。インスタンスに読み込まれたデータ(static の全言語、dynamic のアクティブ言語全体)は、useTranslation() を呼びすすべてのコンポーネントから参照可能になります。バンドラーはインスタンスが保持している単位未満にはコードを分割できず、ランタイムもどのキーが必要とされるかを予測できません。

    bash
    .
    ├── next-i18next.config.js
    ├── public/locales
       ├── en/translation.json           # 全ページの文字列
       └── fr/translation.json
    ├── i18n/i18n.ts                      # i18n.use(initReactI18next).init({ resources })
    └── components
        ├── AppProviders.tsx              # <I18nextProvider i18n={i18n}>
        └── About.tsx                     # useTranslation(); t("about.title")
    

    @intlayer/next-i18next の場合、コンポーネントは辞書ファイルに直接バインドされます。syncJSON が各名前空間ファイルを辞書に変換し、最適化パスが指定された辞書だけを直接インポートとしてコンポーネントに渡すため、バンドラーはページごと・ロケールごとに過不足なくコード分割できます。

    bash
    .
    ├── intlayer.config.ts                # syncJSON({ format: "i18next", source: ... })
    ├── public/locales
       ├── en/translation.json           # 変更不要、信頼できる情報源のまま
       └── fr/translation.json
    ├── .intlayer/                        # 生成物: 名前空間・ロケールごとに1つの辞書
    └── components
        ├── AppProviders.tsx              # <IntlayerClientProvider locale={locale}>
        └── About.tsx                     # useTranslation(); t("about.title")  ← 変更なし
    

    i18n/i18n.ts やその resources インポートは不要なデッドコードとなり、これによって68 KBの削減が実現します。

    3ステップでの移行手順

    1. インストール

      bash
      npx intlayer init --interactive
      

      このコマンドは i18next / react-i18next / next-i18next を自動検出し、intlayer、フレームワークパッケージ(next-intlayer または react-intlayer)、対応する @intlayer/* アダプター、および @intlayer/sync-json-plugin をインストールして intlayer.config.ts を初期設定します。元のパッケージは型定義の提供およびピア依存関係として必要となるため、インストールしたままにしておきます。

    2. ロケールファイルを指定する

      intlayer.config.ts
      import { Locales, type IntlayerConfig } from "intlayer";
      import { syncJSON } from "@intlayer/sync-json-plugin";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
          defaultLocale: Locales.ENGLISH,
        },
        dictionary: {
          importMode: "dynamic",
          format: "i18next",
        },
        plugins: [
          syncJSON({
            // i18nextの方言: {{name}}, $t(key), key_one / key_other, key_male
            format: "i18next",
            // 名前空間ごとのファイル構成: `useTranslation("about")` → about.json
            source: ({ locale, key }) => `./public/locales/${locale}/${key}.json`,
            location: "public/locales",
          }),
        ],
      };
      
      export default config;
      

      言語ごとに単一の translation.json のみを使用している場合(i18nextのデフォルト名前空間)、splitKeys: false を設定することでファイル全体が単一辞書として保持され、名前空間なしの useTranslation() もそのまま動作します。

    3. プラグインを追加する

      next.config.ts
      import type { NextConfig } from "next";
      import { withI18next } from "@intlayer/next-i18next/plugin";
      
      const nextConfig: NextConfig = {};
      
      export default withI18next(nextConfig);
      

      App Routerでは、クライアントコンポーネントは [locale] セグメントからロケールを取得します。アダプターの I18nextProvider はロケール引数を取らないため、Providerファイルを一度だけ更新します。

      components/AppProviders.tsx
      "use client";
      
      import { IntlayerClientProvider } from "next-intlayer";
      import type { LocalesValues } from "intlayer";
      
      export const AppProviders = ({
        locale,
        children,
      }: {
        locale: LocalesValues;
        children: React.ReactNode;
      }) => (
        <IntlayerClientProvider locale={locale}>{children}</IntlayerClientProvider>
      );
      

      配下の全コンポーネントは引き続き useTranslation() をそのまま呼び出せます。

      vite.config.ts
      import { defineConfig } from "vite";
      import react from "@vitejs/plugin-react";
      import { reactI18nextVitePlugin } from "@intlayer/react-i18next/plugin";
      
      export default defineConfig({
        plugins: [react(), reactI18nextVitePlugin()],
      });
      

      reactI18nextVitePlugin()vite-intlayer をラップし、react-i18nexti18next のエイリアスを設定します。React以外のプロジェクトでは、@intlayer/i18next/plugini18nextVitePlugin()i18next 単体をエイリアス化します。

    移行後に削除できるコード

    ファイル / パターン削除できる理由
    resources: { en, fr, ... } および関連JSONインポートアダプターによって無視されます。ここに68 KB分の原因がありました
    i18next-http-backend, i18next-resources-to-backend実行時に取得するものがなくなります
    i18next-browser-languagedetectorロケール検出はIntlayerのルーティング設定(URLプレフィックス、Cookie、ヘッダー)に統合されます
    getStaticProps 内の serverSideTranslations()空のオブジェクトを返すだけとなり、削除しても動作に影響しません
    next-i18next.config.js読み込まれません。設定はすべて intlayer.config.ts で管理します
    ページごとの ns: [...] 定義リストコンパイラがコンポーネント単位で必要な名前空間を自動判別します

    容量削減以外のメリット

    • 型付けされたキー。 useTranslation("about") はコンパイルされた about 辞書に対して型付けされ、存在しないキー t("does.not.exist") は文字列ではなくTypeScriptのエラーとして即座に検出されます。
    • npx intlayer test で、いずれかの言語に欠落キーがある場合にCIを失敗させることができます。また npx intlayer fill を使えば、自身のAPIキー(OpenAI、Anthropic、Mistral、Geminiなど)で未翻訳キーを自動翻訳し、locales/{lng}/{ns}.json に書き戻せます。
    • ビジュアルエディターとCMS が同一のJSON上で動作するため、翻訳者がUI経由でテキストを編集するとGitリポジトリ上のファイルが直接更新されます。
    • .content.ts への段階的移行が可能。 コンポーネントごとに個別のコンテンツファイルを配置し、useTranslation("about") から useIntlayer("about") へ少しずつ切り替えることができます。JSONと .content.ts 辞書は完全に共存可能です。

    事前に把握しておくべき制限事項

    • バックエンドと検出器は不活性化します。 i18n.use(HttpBackend) はプラグインの init を呼ぶだけで動作を完了します。リクエスト時に外部CMSからリアルタイムで翻訳を取得していたワークフローは動作しなくなるため、IntlayerのCMSまたは intlayer pull / push コマンドをご利用ください。
    • resources はマージされず無視されます。 一部のアダプターと異なり、@intlayer/i18next はインラインの resources をフォールバックとして使用しません。すべてのキーは同期された辞書内に存在する必要があり、これは intlayer test で検証できます。
    • App RouterではProviderの変更が必要です。 上述の通り1ファイルのみ差し替えます。Pages Routerで appWithTranslation を使用している場合は変更不要です。
    • next-i18next.config.js は無視されます。 localePathfallbackLngreloadOnPrerender などの設定は機能しません。ロケールおよびフォールバックは intlayer.config.ts に記述してください。
    • アダプター自体のオーバーヘッド。 ネイティブの next-intlayer と比較して、ランタイムで9.4 KB、ページあたり+9.4 KBのコストが発生します。すべてのコンポーネントが useIntlayer へ移行完了した後は、アダプターをアンインストールすることをおすすめします。

    選択の指針

    • i18next を維持すべきケース: ランタイム時の動的バックエンド(リクエスト時に外部CMSから配信される翻訳)、固有のプラグイン群、またはアダプターがサポートしていない非React環境に強く依存している場合。
    • @intlayer/* を採用すべきケース: react-i18nextnext-i18next を利用中で、コードを書き換えることなく68 KBの削減、8倍軽量なコンポーネント、リーク率0%、型付きキー、CI検証を即座に導入したい場合。既存の i18next コードベースにとって最も手軽な選択肢です。
    • ネイティブ(next-intlayer / react-intlayer)へ移行すべきケース: 新規プロジェクト、またはアダプターによる移行が安定した段階。3つの中で最も軽量(5.5 KB、ページあたり+0.3 KB)であり、同期Server Componentsやコンポーネント共配置の .content.ts が利用できます。

    関連する比較記事

    まとめ

    i18next は本ベンチマークの中で最も重いランタイムですが、互換アダプターを導入すればAPIを一切変更することなくその大部分を削ぎ落とせます。同一のNext.jsアプリにおいて、設定ファイルとProviderの軽微な変更だけで、初期構成比でページあたり68 KBの削減、最高度に手動最適化された構成比でも12.7 KBの削減コンポーネントサイズ8分の1リーク率0%ハイドレーション速度4 ms向上が達成されます。

    すべての測定データ、検証用アプリ、再現スクリプトは Benchmark Bloom リポジトリ に公開されています。

    詳細は なぜIntlayerなのか? ドキュメントをご覧ください。

    コメント

    まだコメントはありません。最初のコメントを共有しましょう。

    関連記事

    最新の投稿