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

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

    Svelteにはi18n用の機能が標準で用意されていません。$tも、ロケールプリミティブも、メッセージフォーマットもありません。すべての選択肢がサードパーティ製であり、Svelteエコシステムはコンパイル時i18nが最も進んでいる領域であるため、各候補の違いはReactやVue以上に大きくなります。

    本ガイドでは、まず確認すべき質問事項を整理し、それらに基づいてsvelte-i18n、Paraglide、typesafe-i18nwuchale、Intlayerを、Vite + SvelteおよびSvelteKitの両方の環境で比較・マッピングしていきます。

    Svelte i18n library ecosystem

    目次

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

    1. Vite SPAか、SvelteKitか? SPAであればモジュールレベルのストアで問題ありません。1つのタブ、1人のユーザー、1つのロケールで完結するからです。しかしSvelteKitでは、同じシングルトンがサーバー上の並行リクエスト間で共有されてしまい、リクエストBがリクエストAの言語でレンダリングされてしまう危険があります。ライブラリがリクエストごとの分離機構(コンテキストやlocals)を提供しているか、あるいは開発者自身で実装する必要があります。
    2. 誰が翻訳を作成するか? 開発者か、TMS(翻訳管理システム)か、ICU形式を納品する翻訳会社か、それともAIパイプラインか。svelte-i18nはICUに対応しています。Paraglideとtypesafe-i18nは独自の構文を使用します。翻訳フローに合ったフォーマットを選びましょう。
    3. ロケール数とページ数はどのくらいか? 2つのロケールと5ページ程度であれば、すべてをまとめて配信できます。10ロケールと40ルートがある場合はそれが不可能になり、ランタイムカタログとコンパイル済みメッセージの差が最大のコスト要因になります。
    4. キーの型安全性は必要か? svelte-i18nでは$_("cart.totl")のようなタイポは実行時エラー(またはフォールバック)になります。コンパイル時ライブラリは構造上これを型エラーとして検出します。
    5. Svelte 4のストアか、Svelte 5のRunesか? Runesはロケール状態の構文を変えるものであり、共有問題そのものを解決するわけではありません。ただし、.tsファイル内の$stateは単純な変数にコンパイルされるため、Svelte 5を使用している場合はライブラリのランタイムがRuneに対応している必要があります。
    6. リポジトリ内に生成ファイルが含まれても問題ないか? Paraglideとtypesafe-i18nは、ソースツリー内にJavaScriptまたはTypeScriptを生成します。これを許容できるチームもあれば、並行ブランチごとにマージコンフリクトが発生して困るチームもあります。

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

    1枚の図で見る全体像

    Svelteのi18nはReactやVueよりも後に登場したため、初期の段階をスキップして直接コンパイル時の波へと進みました。

    History of JavaScript i18n libraries

    JSONカタログを使用し、intl-messageformat経由でブラウザ内でICUをパースし、モジュールレベルのストア($locale, $_)でロケールを保持します。最も普及しておりドキュメントも豊富ですが、SSRの連携は自前で構築する必要があります。

    ジェネレーターがカタログを監視し、型付きアクセサ($LL.cart.total())を出力します。優れたモデルですが、リポジトリ内にファイルが生成され、最近はリポジトリの更新があまり活発ではありません。

    Paraglideは各メッセージをエクスポート関数にコンパイルし、ルートが呼び出さないメッセージをバンドラーがツリーシェイキングできるようにします。wuchaleはビルド時にマークアップから文字列を抽出します。Intlayerはコンポーネントごとにコンテンツを宣言し、型とコンポーネントごとの辞書を生成します。

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

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

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

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

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

    Theoretical content leakage by architecture

    svelte-i18nはデフォルトで左上に位置します。register("fr", () => import("./fr.json"))によりロケールごとの動的読み込みは可能ですが、ロケールカタログは1つのオブジェクトであるため、それを読み込むと全ページのテキストが読み込まれます。Paraglideは興味深いケースです。すべてのメッセージが個別のエクスポートとなるため、ツリーシェイキングによってページ軸の最適化が無償で得られます。Svelteベンチマークでも、Vite + Svelte環境で期待通りに機能することが確認されています(ReactやNext.jsのベンチマークでは機能しませんでした)。Intlayerはコンポーネントごとの宣言によって同じ領域に到達します。

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

    比較対象の候補

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

    ライブラリメッセージの配置場所ロケール状態キーの型定義メッセージフォーマットルートごとの分割ライブラリサイズ
    svelte-i18nロケールごとのJSONカタログモジュールレベルのSvelteストア手動Union型ICUなし約16.6 kB
    typesafe-i18n生成されたTSモジュールストアアダプター自動生成独自構文一部対応軽量
    Paraglideinlangプロジェクト、関数へコンパイルCookie、URL、Storageから呼び出しごとに取得自動生成独自構文あり(ツリーシェイキング経由)ほぼゼロ
    wuchaleビルド時にマークアップから抽出ストア該当なし(キーなし)独自構文あり軽量
    Intlayerコンポーネント隣の.content.tsコンテキスト+ストア、Rune対応自動生成、デフォルトで有効ヘルパーあり(コンポーネント単位)ベースライン
    数値はベンチマーク実施バージョンのスナップショットです。サイズだけで判断する前に、実際のアプリで計測してください。

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

    回答に合ったライブラリを選ぶ

    svelte-i18n。最もドキュメントが充実した選択肢であり、$_はマークアップ内で自然に読め、registerwaitLocale()によりロケールごとの遅延ロードがカバーされます。初回ペイントをisLoadingで制御しないと、生のキーが一瞬表示(フラッシュ)されてしまいます。将来的にサーバー(SSR)を導入する可能性がある場合は、モジュールストアに頼るのではなく、初日からロケールをSvelteコンテキストに配置してください。今ならコストはかからず、本番環境でのみ発生するバグを未然に防ぐことができます。

    共有問題が決定打となります。svelte-i18nはSvelteKitでも動作しますが、リクエストごとの連携(hooks.server.tslocalsload、そしてsetContext)は自前で記述する必要があり、微妙なミスが起きやすいです。Paraglideはルーティングを処理し呼び出しごとにロケールを読み取るSvelteKit統合を提供しており、シングルトンの問題を回避できます。Intlayerはloadデータからコンテキストへとロケールを設定します。SvelteKit i18nの記事では[[lang]]rerouteの選択について解説しています。ライブラリを選ぶ前に決めておきましょう。

    svelte-i18nintl-messageformatによるネイティブなICU対応を備えているため、多くのベンダーと直接連携できます。Paraglideとtypesafe-i18nは独自の構文を使用しているため変換が必要です。IntlayerのICUサポートは部分的なものにとどまるため、現在ICU文字列を受け取っている場合はブロッカー(採用を見送る理由)として扱う必要があります。

    コンパイル時ライブラリ。ParaglideのツリーシェイキングはVite + Svelteで効果的に機能し、ライブラリコストはほぼゼロです。Intlayerのコンポーネントごとの辞書も、リポジトリ内にファイルを生成することなく同様の結果をもたらします。svelte-i18nはICUパーサーとカタログ全体を同梱するため、コンテンツを含める前のベンチマーク段階でsvelte-intlayerの約4.5倍のサイズになります。

    素のsvelte-i18n以外のすべてです。svelte-i18nでの唯一の型付けは手書きのUnion型であり、JSONとすぐに乖離してしまいます。typesafe-i18n、Paraglide、Intlayerはいずれもコンテンツから型を自動生成します。コードベースをコミットする前にtypesafe-i18nのリポジトリのアクティビティを確認してください。不足している翻訳の検出の記事では、ビルド時にそれぞれが何を検知できるかを比較しています。

    Paraglideとtypesafe-i18nは候補から外れます。svelte-i18nとIntlayerは出力をnode_modulesまたはビルドディレクトリ内に保持します。Intlayerの場合、.content.tsファイルは手書きのソースコードであり、コンパイルされた辞書と型は.intlayer/内に配置されgitignoreされます。

    集中管理されたJSONを維持する理由はなくなります。コロケーションされたコンテンツと、不足しているロケールを補完するCLIを組み合わせるのが近道です。Intlayerのfillコマンドは自身のAPIキー(OpenAI、Anthropic、Mistral、Gemini)で動作し、変更された部分のみを再翻訳します。Paraglideのinlangエコシステムでも、独自のプランを持つホスト型同等機能が提供されています。

    各ライブラリの弱点・課題

    • svelte-i18n: 最も重く、キーの型がなく、ルートごとの分割がなく、コンテキストを自前で接続しない限りSvelteKit上でリクエストをまたいで共有ストアがリークする。
    • typesafe-i18n: ウォッチャープロセスが必要で、リポジトリ内にファイルが生成され、最近リポジトリの動きがあまりない。
    • Paraglide: リポジトリにコミットされプッシュごとに再生成される生成ファイル、並行ブランチでのマージコンフリクト、ストアからではなくメッセージ呼び出しごとにCookieやストレージからロケールを読み取るためロケール変更時の処理コストがかかる。
    • wuchale: マークアップ抽出という興味深いアイデアだが、まだ初期段階。Reactベンチマークではプロバイダーの再レンダリングを強制する必要があるリアクティビティの問題が発生し、ドキュメントも少ない。
    • Intlayer: 必須のビルドプラグイン、比較的小さなエコシステム、部分的なICUサポート、そして設計上コンテンツがコードベース全体に分散しているため翻訳者向けに1つのJSONをエクスポートするにはツールが必要。

    各選択肢のコード例

    タイトルと複数形を含む同じカート概要コンポーネントを、各候補で記述した例です。注目すべきはマークアップではなく、コンテンツがどこに配置され、ロケールがどのように保持され、型チェッカーが何を把握しているかです。

    src/locales/en.json
    {
      "cart": {
        "title": "Your cart",
        "items": "{count, plural, one {# item} other {# items}}"
      }
    }
    
    src/lib/CartSummary.svelte
    <script lang="ts">
      import { _ } from "svelte-i18n";
    
      let { count }: { count: number } = $props();
    </script>
    
    <section>
      <h2>{$_("cart.title")}</h2>
      <p>{$_("cart.items", { values: { count } })}</p>
    </section>
    

    intl-messageformatによるICU、モジュールレベルのストアで保持されるロケール。$_は任意の文字列を受け付けます。型付けは手書きのUnion型のみです。

    messages/en.json
    {
      "cart_title": "Your cart",
      "cart_items": "{count} items"
    }
    
    src/lib/CartSummary.svelte
    <script lang="ts">
      import { m } from "$lib/paraglide/messages.js";
    
      let { count }: { count: number } = $props();
    </script>
    
    <section>
      <h2>{m.cart_title()}</h2>
      <p>{m.cart_items({ count })}</p>
    </section>
    

    すべてのメッセージが生成された型付き関数であり、呼び出されなければツリーシェイキングされます。paraglide/フォルダーはリポジトリ内に生成され、ロケールはストアからではなく呼び出しごとに読み取られます。

    src/i18n/en/index.ts
    import type { BaseTranslation } from "../i18n-types";
    
    const en = {
      cart: {
        title: "Your cart",
        items: "{count} item{{s}}",
      },
    } satisfies BaseTranslation;
    
    export default en;
    
    src/lib/CartSummary.svelte
    <script lang="ts">
      import LL from "$i18n/i18n-svelte";
    
      let { count }: { count: number } = $props();
    </script>
    
    <section>
      <h2>{$LL.cart.title()}</h2>
      <p>{$LL.cart.items({ count })}</p>
    </section>
    

    ウォッチャープロセスによって生成される型付きアクセサ。モデルは堅牢ですが、生成ファイルがリポジトリ内に存在し、プロジェクトは最近静かです。

    src/lib/cartSummary.content.ts
    import { plural, t, type Dictionary } from "intlayer";
    
    const cartSummaryContent = {
      key: "cart-summary",
      content: {
        title: t({
          ja: "ショッピングカート",
          en: "Your cart",
          fr: "Votre panier",
          es: "Tu carrito",
        }),
        items: t({
          ja: plural({
            one: "{{count}}個のアイテム",
            other: "{{count}}個のアイテム",
          }),
          en: plural({ one: "{{count}} item", other: "{{count}} items" }),
          fr: plural({ one: "{{count}} article", other: "{{count}} articles" }),
          es: plural({ one: "{{count}} artículo", other: "{{count}} artículos" }),
        }),
      },
    } satisfies Dictionary;
    
    export default cartSummaryContent;
    
    src/lib/CartSummary.svelte
    <script lang="ts">
      import { useIntlayer } from "svelte-intlayer";
    
      let { count }: { count: number } = $props();
      const content = useIntlayer("cart-summary");
    </script>
    
    <section>
      <h2>{$content.title}</h2>
      <p>{$content.items(count)}</p>
    </section>
    

    全ロケールがコンポーネントの隣にある1つのファイルに記述されます。useIntlayerは読み取り可能なストアを返すため、$contentはお馴染みの自動購読(auto-subscription)となり、ロケールはモジュールシングルトンではなくコンテキスト(SSRセーフ)で保持されます。

    すでにsvelte-i18nを使用している場合、@intlayer/svelte-i18n互換アダプターによってバンドラーレベルでパッケージがエイリアスされるため、Intlayerがコンテンツを提供しながら$_$date$numberおよびフラットなキーを引き続き機能させることができます。

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

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

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

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

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

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

    Tier list of JavaScript i18n libraries

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

    svelte-i18nnext-intlvue-i18nと同様にCrowdinが支援しています。i18nextはLocizeが支援しています。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)の有無を確認してください。

    よくある質問

    カタログが小さいVite SPAであれば適切です。最もドキュメントが充実した選択肢であり、ICUの互換性は多くのチームにとって重要です。ただしSvelteKit上や数十ページを超える規模になると、そのコスト(型がない、スコープ分割がない、共有ストア)が顕在化し始めます。

    Vite + Svelteにおいては機能し、ベンチマークでもそれが確認されています。TanStack Startを用いたReactやNext.jsでは同じベンチマークで効果が見られませんでした。どちらの結果も盲信するのではなく、自身のスタックで検証してください。

    Runesはロケール状態の記述構文を変えるものであり、共有問題そのものを変えるわけではありません。重要なのは、Svelte 5においてライブラリのランタイムがRuneに対応しているかどうか、そしてモジュールストアではなくコンテキストを使用しているかどうかです。両方を確認してください。

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

    さらに詳しく

    コメント

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

    関連記事

    最新の投稿