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

    最適なSolid i18nライブラリを選ぶ方法

    Solidのリアクティビティモデルは、i18nライブラリに求められる役割を大きく変えます。コンポーネントは一度しか実行されないため、セットアップ時にconstに格納された翻訳文字列は固定された文字列(frozen string)になります。アクセサ(accessor)ではなく文字列を直接返すライブラリを使用すると、その書き方をした3つのコンポーネントだけ言語が切り替わらないページが出来上がってしまいます。Solid向けのライブラリ選びは、APIの扱いやすさだけでなく、そのようなミスを防ぎやすい設計になっているかどうかも重要な要素です。

    本ガイドでは、まず確認すべき質問事項を整理し、それらに基づいて@solid-primitives/i18nsolid-i18next、Paraglide、@lingui/solid、Intlayerを、Vite + SolidおよびSolidStartの両方の環境で比較・マッピングしていきます。

    Solid i18n library ecosystem

    目次

    ライブラリを比較する前に答えるべき6つの質問

    1. Vite SPAか、SolidStartか? SPAであれば、ロケールをシグナル(signal)内で保持するだけで完結します。SolidStartの場合、サーバー上でURLからロケールを解決する必要があり、クローラーがJavaScriptなしで読み取るべき要素(<html lang>hreflang)はentry-server.tsxに記述する必要があります。
    2. ロケール変更のリアクティビティはどの程度必要か? 言語切り替え時にページ全体をリロードする仕様で問題ないアプリもあります。そうでない場合、ライブラリが提供する値はシグナルまたはアクセサである必要があり、その値の読み取りはコピーではなく追跡(tracked)されなければなりません。
    3. 誰が翻訳を作成するか? 開発者か、TMS(翻訳管理システム)か、ICU形式を納品する翻訳会社か、それともAIパイプラインか。solid-i18nextはi18nextのフォーマットに対応しています。@solid-primitives/i18nは自作の辞書オブジェクトそのものです。翻訳を行う担当者やツールに合った形式を選びましょう。
    4. ロケール数とページ数はどのくらいか? 2つのロケールと5ページ程度であれば、すべてをまとめて配信できます。10ロケールと40ルートがある場合はそれが不可能になり、遅延ロード(lazy catalogs)とスコープ分割が最大のコスト要因になります。
    5. キーの型安全性は必要か? @solid-primitives/i18nは元の辞書オブジェクトから型を推論します。solid-i18nextは手動での型宣言が必要です。コンパイル時ライブラリは型を自動生成します。
    6. どの程度の機能セットが必要か? Cookie管理、ロケールプレフィックス付きルーティング、リダイレクト、フォーマッタなど。最も軽量な選択肢にはこれらの機能は一切含まれていません。要件が小さいうちはそれでも問題ありません。

    回答を書き出してみてください。以降の内容はすべてこれらの項目を前提に進めます。

    1枚の図で見る全体像

    Solidは比較的新しいエコシステムであり選択肢も少なめですが、大きく3つの波に分かれています。

    History of JavaScript i18n libraries

    Solid向けにラップされたi18next。ネームスペース、バックエンド、言語検出、そして10年以上にわたる豊富なプラグインを備えています。最も重い選択肢であり、Reactと同様にt("a.b")のオーバーヘッドが存在します。

    自身で管理するフラットな辞書、アクセサを返すtranslator()、元のオブジェクトから推論される型定義。非常に軽量ですが、スコープ分割、ルーティング、フォーマッタなどの機能はありません。コミュニティ標準の選択肢です。

    Paraglideはメッセージごとに1つの関数を生成します。Intlayerは.content.tsファイルでコンポーネントごとにコンテンツを宣言し、シグナル対応のノードを返します。2026年に登場したLinguiのSolidバインディングは、マクロベースの抽出機能を提供します。

    各世代の詳細については、JavaScript i18nの歴史で詳しく解説しています。

    最も重要な意思決定: コンテンツの配置場所と読み込みタイミング

    構成におけるバンドルサイズの差の大部分は、主に2つの構造的な選択によって決まります。

    • 集中管理か、コンポーネントごとのスコープ管理か。 アプリ全体で1つの辞書を持つか、コンポーネントごとに1つの宣言を持つか。
    • 静的インポートか、動的インポートか。 起動時にすべて読み込むか、アクティブなロケール(理想的にはアクティブなルートも)をオンデマンドで取得するか。

    以下のグラフは、1〜10ページ、1〜10ロケール、1ページあたり約30 KBのテキストを持つ理論上のアプリにおけるペイロードの推定値を示しています。

    Theoretical content leakage by architecture

    @solid-primitives/i18nはどちらの軸も自動では対応しません。ロケールごとに辞書をcreateResourceすることで動的ロードは実現できますが、それ以外の制御は自作する必要があります。solid-i18nextにはネームスペースと遅延バックエンドがありますが、マッピングが強制されないため、共通コンポーネントがcommonをインポートすると、それがすべてのルートの依存関係になってしまいます。Paraglideはツリーシェイキング(tree-shaking)によってページ単位の最適化を行いますが、Solidベンチマークの実装では効果が現れませんでした。Intlayerはコンポーネントごとの宣言によってこれを実現します。

    質問4の回答が「多くのページがある」だった場合は、APIの好みよりもこのセクションを重視してください。コンポーネント単位 vs 集中管理型 i18nの記事では、このトレードオフのメンテナンス面について解説しています。

    比較対象の候補

    ライブラリのサイズは、10ページ・10ロケールのアプリを対象にしたSolidベンチマーク(バンドル、ツリーシェイキング、minify後の空コンポーネントにおけるProvider+アクセサ)の数値です。コンテンツのサイズは個別に測定しています。

    ライブラリコンテンツモデルロケール変更時のリアクティビティキーの型定義スコープ管理と遅延ロードライブラリサイズ
    @solid-primitives/i18n自身で管理するフラットな辞書シグナル、translatorから返されるアクセサ元の辞書から推論組み込みなし極めて軽量
    solid-i18nexti18nextのカタログとネームスペースStore、Provider経由の再レンダリング手動宣言ネームスペース、遅延バックエンド約14.9 kB
    Paraglideinlangプロジェクト、生成された関数CookieまたはStorageから呼び出しごとに読み取り自動生成ツリーシェイキング(ベンチ外)ほぼゼロ
    @lingui/solidコード内のソーステキスト、コンパイル済みカタログシグナルベースコンパイラから生成カタログ単位軽量
    Intlayerコンポーネントごとに1つの.content.tsシグナル対応ノード、コンポーネント再実行なし自動生成、デフォルトで有効あり(コンポーネント単位)ベースライン
    数値はベンチマーク実施バージョンのスナップショットです。@lingui/solidはベンチマークに含まれていません。サイズだけで判断する前に、実際のアプリで計測してください。

    Paraglideのライブラリサイズがほぼゼロである理由は構造によるものです。ランタイムがリポジトリ内に直接生成されます。Intlayerはvite-intlayerを必要とするため、ビルドステップなしでは動作しません。

    回答に基づいたライブラリの選定

    @solid-primitives/i18nが適しています。フラットな辞書、アクセサを返すtranslator()、設定不要で推論される型定義が特徴です。小規模アプリには最適な選択肢であり、ソースコードも10分程度で読み通せます。ロケールの永続化、ルーティング、フォーマッタ、ルートごとのコード分割などは自分で実装する必要があります。これらの要件が増えてきたら、他のライブラリへの移行を検討する合図です。

    solid-i18nextを使用すれば、既存のカタログ、ネームスペース、バックエンド、言語検出処理をそのまま再利用できます。最も重い選択肢であり、react-i18nextと同様のコスト(手動での型宣言、可能だが手間の掛かる最適化、文字列を返すt()による翻訳フリーズの起きやすさなど)が伴います。読み取りはJSX内またはメモ(memo)内で行い、セットアップ時に変数へ保存しないようにしてください。

    クライアントとサーバーで言語設定を一致させるため、サーバー側のURLからロケールを取得する必要があります。クライアント側で検出していては遅すぎます。@solid-primitives/i18nsolid-i18nextでは、[[locale]]ルート、matchFilters、リダイレクト処理、entry-server.tsxのタグ設定をすべて自前で構築する必要があります。Paraglideにはルーティングを処理するViteプラグインが用意されています。Intlayerにはミドルウェアとルートヘルパーが同梱されています。どの選択肢を採用する場合でも、<html lang>hreflangentry-server.tsxに記述してください。SolidStart v2では@solidjs/metaがクライアント側でハイドレーション後に適用されるためです。詳細なセットアップ手順はSolid i18nの記事を参照してください。

    値がシグナルまたはアクセサであり、読み取りが追跡されるライブラリを選択してください。@solid-primitives/i18nのアクセサとIntlayerのノードは、コンポーネント全体を再実行することなく、それらを読み取っているDOMノードのみを更新します。solid-i18nextはProviderを介して再レンダリングを行います。Paraglideはシグナルではなくメッセージ呼び出しごとにCookieやStorageからロケールを読み取るため、動作はしますが不要な処理コストが発生します。

    ビルド時にコンパイルされるスコープ管理されたコンテンツが最適です。Intlayerはルートが描画するものだけを配信します。Paraglideはツリーシェイキングによって最適化されるはずですが、ベンチマーク環境では機能しなかったため実際の環境で検証してください。solid-i18nextを使用する場合は、初日からネームスペースと遅延読み込みの戦略を設計し、コードレビューで徹底する必要があります。

    @solid-primitives/i18nは追加設定なしで推論された型を提供します。これは多くのReactライブラリ以上の利点です。遅延ロードやルートごとのコード分割に対応した生成型の点では、Paraglide、@lingui/solid、Intlayerはいずれもコンテンツから型を自動生成します。不足している翻訳の検出の記事では、各ライブラリがビルド時に何をキャッチできるかを比較しています。

    集中管理型の辞書ファイルはもはや不要になります。コンポーネントと同じ場所に配置するコロケーション(colocated content)と、不足ロケールを補完するCLIを組み合わせるのが最も効率的です。Intlayerのfillコマンドは独自のAPIキー(OpenAI、Anthropic、Mistral、Gemini)を利用でき、変更された部分のみを再翻訳します。

    各ライブラリのデメリット・注意点

    • @solid-primitives/i18n: 自作しない限り遅延ロードやスコープ管理がなく、ルーティング、Cookie処理、フォーマッタも非搭載。小規模アプリには優れていますが、プロダクション規模では機能不足になりやすいです。
    • solid-i18next: 候補の中で最も重く、型の定義が手動であり、独自の複数形フォーマットを採用しています。またt()が文字列を返すためセットアップ時に保存すると翻訳がフリーズします。
    • Paraglide: 生成されたファイルをリポジトリにコミットしPush前に再生成する必要があり、Solidベンチマークではツリーシェイキングが有効に機能せず、シグナルではなくストレージから呼び出しごとにロケールを読み取ります。
    • @lingui/solid: 2026年に登場したばかりで本番実績がまだ少ないです。Lingui特有のextract / compileビルドステップと、複数の重複する構文を引き継いでいます。
    • Intlayer: ビルドプラグインが必須で、エコシステムが比較的小さく、ICUのサポートが一部に留まります。また設計上コンテンツがコードベース全体に分散するため、翻訳者向けに1つのJSONへエクスポートするにはツールが必要です。

    コードによる各選択肢の比較

    タイトルと複数形を含む同じカートサマリーコンポーネントを、各候補ライブラリで記述した例です。翻訳がどこで読み取られているかに注目してください。JSX内では追跡されますが、セットアップ関数内では固定された文字列になってしまいます。

    src/i18n/index.ts
    import * as i18n from "@solid-primitives/i18n";
    
    export const en = {
      cart: { title: "Your cart", items: "{{ count }} items" },
    };
    
    export const dictionary = () => i18n.flatten(en);
    export const t = i18n.translator(dictionary, i18n.resolveTemplate);
    
    src/components/CartSummary.tsx
    import type { Component } from "solid-js";
    import { t } from "../i18n";
    
    export const CartSummary: Component<{ count: number }> = (props) => (
      <section>
        <h2>{t("cart.title")}</h2>
        <p>{t("cart.items", { count: props.count })}</p>
      </section>
    );
    

    コード生成なしで英語オブジェクトからキーの型が推論されます。複数形ルール、遅延読み込み、ルーティングは含まれておらず、必要に応じて追加する必要があります。

    public/locales/en/cart.json
    {
      "title": "Your cart",
      "items_one": "{{count}} item",
      "items_other": "{{count}} items"
    }
    
    src/components/CartSummary.tsx
    import { useTransContext } from "@mbarzda/solid-i18next";
    import type { Component } from "solid-js";
    
    export const CartSummary: Component<{ count: number }> = (props) => {
      const [t] = useTransContext();
    
      return (
        <section>
          <h2>{t("cart:title")}</h2>
          <p>{t("cart:items", { count: props.count })}</p>
        </section>
      );
    };
    

    i18nextのカタログ、ネームスペース、プラグインをそのまま使用できます。tは文字列を返すため、セットアップ時にconst title = t("cart:title")と記述すると値が固定されてしまいます。呼び出しはJSX内で行うようにしてください。

    messages/en.json
    {
      "cart_title": "Your cart",
      "cart_items": "{count} items"
    }
    
    src/components/CartSummary.tsx
    import type { Component } from "solid-js";
    import { m } from "../paraglide/messages.js";
    
    export const CartSummary: Component<{ count: number }> = (props) => (
      <section>
        <h2>{m.cart_title()}</h2>
        <p>{m.cart_items({ count: props.count })}</p>
      </section>
    );
    

    すべてのメッセージが型付きの自動生成関数になります。ロケールはシグナルではなく呼び出しごとにCookieまたはStorageから読み取られるため、切り替え時のリアクティビティは自前で設定する必要があります。

    src/components/cartSummary.content.ts
    import { type Dictionary, plural, t } from "intlayer";
    
    const cartSummaryContent = {
      key: "cart-summary",
      content: {
        title: t({
          ja: "カート",
          en: "Your cart",
          fr: "Votre panier",
          es: "Tu carrito",
        }),
        items: plural({
          one: t({
            ja: "{{count}} 点の商品",
            en: "{{count}} item",
            fr: "{{count}} article",
          }),
          other: t({
            ja: "{{count}} 点の商品",
            en: "{{count}} items",
            fr: "{{count}} articles",
          }),
        }),
      },
    } satisfies Dictionary;
    
    export default cartSummaryContent;
    
    src/components/CartSummary.tsx
    import { useIntlayer } from "solid-intlayer";
    import type { Component } from "solid-js";
    
    export const CartSummary: Component<{ count: number }> = (props) => {
      const content = useIntlayer("cart-summary");
    
      return (
        <section>
          <h2>{content.title}</h2>
          <p>{content.items(props.count)}</p>
        </section>
      );
    };
    

    コンポーネントの隣にある1つのファイルに全ロケールを記述します。useIntlayerはシグナル対応ノードを返すため、ロケール変更時はそれらを読み取るDOMノードのみが更新されます。JSX内の{content.title}は追跡されますが、セットアップ関数内のcontent.title.valueは追跡されません。

    既存のi18nextコードベースに対しては、i18next互換アダプターによりバンドラーレベルでパッケージがエイリアスされ、Intlayerがコンテンツを提供しながらカタログとt()を引き続き動作させることができます。その他の詳細は移行ガイドを参照してください。

    採用を決める前のチェックポイント

    機能比較表は現在の機能を示しているに過ぎません。以下のポイントは、実際にそのライブラリを運用していく際の体験を左右します。

    リポジトリのアクティビティを確認する。

    コミット頻度、Issueへの返答時間、最新のマイナーリリースが今年行われているかを確認してください。メンテナーのいない優れた設計は、いずれ移行作業を迫られることになります。

    npmのダウンロード数だけで選ばない。

    最もインストールされているライブラリは、最初にリリースされたものであり、必ずしも2026年のSolidコードベースに適合しているわけではありません。ダウンロード数は歴史の長さを示すものであり、適合度を示すものではありません。

    Tier list of JavaScript i18n libraries

    メンテナーの収益モデルと販売対象を確認する。

    solid-i18nextの背後にあるi18nextはLocizeが支援しています。next-intlvue-i18nsvelte-i18n、LinguiはCrowdinが支援しています。Tolgee、Paraglide(inlang)、Intlayerはそれぞれ独自のプラットフォームを運営しています。ホスティング翻訳サービスを主な収益源とするベンダーは、開発ツールチェーン内での無料翻訳を促進する動機が薄くなりがちです。Intlayerはこの中で唯一、独自のAPIキーを使ったCLI経由のAI翻訳と、セルフホスト可能なCMSを提供しています。

    AIエージェントに対応しているか?

    AIエージェントは依然としてi18nの扱いに苦労することが多く、ロケールの不足、キーの捏造、メッセージ構文の混同などが起きがちです。エージェントがコンテンツのリストアップ、補完、テストを行えるように、ライブラリがAgent SkillsMCPサーバーを提供しているか確認してください。またコンテンツの読み込みがデフォルトで最適化されているか、あるいは四半期ごとにネームスペースや遅延インポートの見直しが必要になるかも重要です。

    設定なし(out of the box)での型安全性。

    「追加の設定を行えば型付けできる」ではなく、「新規インストール状態で存在しないキーを指定するとtscが失敗する」かどうかです。存在しないキーを指定した場合や、1つのロケールで翻訳が欠落している場合に何が起きるかを確認してください。

    未使用コンテンツの検出。

    カタログは増え続ける一方です。Intlayerのビルドは未使用のフィールドを削除しログを出力します(build.purge)。Paraglideは呼び出されないメッセージ関数がツリーシェイキングされる構造になっています。他のライブラリでは未使用キーの整理を自前で行う必要があります。

    開発者体験(Developer Experience)。

    最初の翻訳文字列を表示するまでのセットアップ時間、ホバー時に翻訳を表示し定義元へジャンプできるLSPVS Code拡張機能、補完・テスト・プッシュを行うためのCLI、そして開発者以外でもプルリクエストなしでコンテンツを編集できる手段(ビジュアルエディタCMS)の有無を確認してください。

    よくある質問

    小規模なアプリであれば十分であり、最も軽量な選択肢です。ただし、ルートごとの遅延カタログ、SolidStartでのロケールルーティング、Cookieによる永続化、フォーマッタなどが必要になった場合は、すべて自前で構築する必要があるため不足を感じるようになります。

    Solidのコンポーネントは一度しか実行されないためです。セットアップ時にconstに読み込まれた翻訳は単なる文字列であり、サブスクリプションではありません。JSX内、エフェクト内、またはメモ内で読み取るか、値がアクセサになっていて誤った記述をしにくいライブラリを選択してください。

    バンドルサイズ、自動生成された型、あるいはビルド時の不足キーチェックが実際に必要な要件である場合にのみ検討してください。コンパイラ vs 宣言型 i18nの記事では、コンパイラが提供する利点と注意点について解説しています。

    間接的に影響します。クローラーはルーティング、hreflang<html lang>、およびサーバーレンダリングされたHTML内にテキストが存在するかどうかを評価します。SolidStartにおいてはentry-server.tsxの設定が重要になります。hreflangガイドを参照してください。

    さらに詳しく

    コメント

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

    関連記事

    最新の投稿