---
createdAt: 2025-11-24
updatedAt: 2025-11-24
title: コンパイラー vs 宣言的 i18n
description: 「魔法の」コンパイラー ベースの国際化と明示的な宣言的コンテンツ管理の間のアーキテクチャ上のトレードオフを探る。
keywords:
  - Intlayer
  - 国際化
  - ブログ
  - Next.js
  - JavaScript
  - React
  - i18n
  - コンパイラー
  - 宣言的
slugs:
  - blog
  - compiler-vs-declarative-i18n
---

# コンパイラー ベースの i18n に賛成と反対の理由

10年以上ウェブアプリケーションを開発しているなら、国際化（i18n）が常に摩擦点であったことをご存知でしょう。文字列の抽出、JSON ファイルの管理、複数形ルールの心配など、誰もやりたがらないタスクであることが多いです。

最近、新たな波の**「コンパイラー ベース」i18nツール**が登場し、この面倒を解消すると約束しています。魅力的な提案はこうです：**コンポーネントにテキストを書くだけで、ビルドツールが残りを処理してくれる。** キーもインポートも不要、ただの魔法のように。

しかし、ソフトウェア工学におけるすべての抽象化と同様に、魔法には代償があります。

このブログ記事では、宣言的ライブラリからコンパイラー ベースのアプローチへの移行、それらがもたらす隠れたアーキテクチャ的負債、そしてなぜ「退屈な」方法がプロフェッショナルなアプリケーションにとって依然として最良の方法である可能性があるのかを探ります。

## 目次

<TOC/>

## 国際化の簡単な歴史

今の状況を理解するためには、まず出発点を振り返る必要があります。

2011年から2012年頃、JavaScriptの状況は大きく異なっていました。現在私たちが知っているようなバンドラー（WebpackやVite）は存在しなかったか、まだ初期段階にありました。私たちはブラウザ上でスクリプトをつなぎ合わせていました。この時代に、**i18next**のようなライブラリが誕生しました。

彼らは当時可能な唯一の方法で問題を解決しました：**ランタイム辞書**です。巨大なJSONオブジェクトをメモリに読み込み、関数がその場でキーを検索しました。それは信頼性が高く、明示的で、どこでも動作しました。

そして今日に至ります。私たちはミリ秒単位で抽象構文木（AST）を解析できる強力なコンパイラ（SWCやRustベースのバンドラー）を持っています。この力が新しいアイデアを生み出しました：_なぜ私たちは手動でキーを管理しているのか？なぜコンパイラが「Hello World」というテキストを見て、それを置き換えてくれないのか？_

こうして、コンパイラベースのi18nが誕生しました。

> **コンパイラベースのi18nの例:**
>
> - Paraglide（各メッセージを小さなESM関数にコンパイルするツリーシェイク可能なモジュール。これにより、バンドラーは未使用のロケールやキーを自動的に削除できます。文字列キーのルックアップの代わりに、関数としてメッセージをインポートします。）
> - LinguiJS（ビルド時に`<Trans>`のようなメッセージマクロをプレーンなJS関数呼び出しに書き換えるマクロから関数へのコンパイラ。ICU/MessageFormat構文を非常に小さなランタイムフットプリントで利用可能です。）
> - Lingo.dev（Reactアプリケーションのビルド中に翻訳済みコンテンツを直接注入することでローカリゼーションパイプラインの自動化に注力。AIを使った翻訳の自動生成やCI/CDへの直接統合も可能です。）
> - Wuchale（Svelteを最優先としたプリプロセッサで、.svelteファイル内のインラインテキストを抽出し、ラッパーなしの翻訳関数にコンパイルします。文字列キーを避け、コンテンツ抽出ロジックをメインアプリケーションのランタイムから完全に分離します。）
> - Intlayer（コンパイラ / 抽出CLIで、コンポーネントを解析し、型付き辞書を生成し、必要に応じてコードを書き換えて明示的なIntlayerコンテンツを使用できます。目的は、宣言的でフレームワークに依存しないコアを維持しつつ、コンパイラによる高速化を実現することです。）

> **宣言的i18nの例：**
>
> - i18next / react-i18next / next-i18next（ランタイムJSON辞書と豊富なプラグインエコシステムを使用する成熟した業界標準）
> - react-intl（FormatJSライブラリの一部で、標準的なICUメッセージ構文と厳密なデータフォーマットに焦点を当てています）
> - next-intl（Next.js向けに最適化されており、App RouterおよびReact Server Componentsとの統合を提供します）
> - vue-i18n / @nuxt/i18n（コンポーネントレベルの翻訳ブロックと密接なリアクティビティ統合を提供する標準的なVueエコシステムソリューション）
> - svelte-i18n（リアクティブなランタイム翻訳のためのSvelteストアの軽量ラッパー）
> - angular-translate（ビルド時のマージではなく、ランタイムのキー検索に依存するレガシーな動的翻訳ライブラリ）
> - angular-i18n（ビルド時にXLIFFファイルをテンプレートに直接マージするAngularのネイティブな先行処理アプローチ）
> - Tolgee（宣言型コードとインコンテキストSDKを組み合わせ、UI上での「クリックして翻訳」編集を可能にする）
> - Intlayer（コンポーネント単位のアプローチで、コンテンツ宣言ファイルを使用し、ネイティブのツリーシェイキングとTypeScript検証を実現）

## Intlayerコンパイラ

**Intlayer**は基本的にコンテンツに対して**宣言的アプローチ**を推奨するソリューションですが、開発の高速化や迅速なプロトタイピングを支援するためにコンパイラも含まれています。

Intlayerコンパイラは、React、Vue、Svelteコンポーネントおよびその他のJavaScript/TypeScriptファイルのAST（抽象構文木）を走査します。その役割は、ハードコードされた文字列を検出し、それらを専用の`.content`宣言に抽出することです。

> 詳細については、ドキュメントをご覧ください: [Intlayer Compiler Docs](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/compiler.md)

## コンパイラーの魅力（「マジック」アプローチ）

この新しいアプローチがトレンドになっている理由があります。開発者にとって、その体験は驚くべきものです。

### 1. スピードと「フロー」

集中しているときに、意味のある変数名（`home_hero_title_v2`）を考えるために立ち止まると、フローが途切れてしまいます。コンパイラーアプローチでは、`<p>Welcome back</p>` とタイプしてそのまま進めます。摩擦はゼロです。

### 2. レガシー救出ミッション

5,000のコンポーネントを持ち、翻訳がまったくない巨大なコードベースを引き継ぐことを想像してください。これを手動のキー ベースのシステムに後付けするのは、数か月に及ぶ悪夢のような作業です。コンパイラベースのツールは救助戦略として機能し、1つのファイルにも手を触れずに何千もの文字列を即座に抽出します。

### 3. AI時代

これは見逃せない現代的な利点です。AIコーディングアシスタント（CopilotやChatGPTなど）は、標準的なJSX/HTMLを自然に生成します。彼らはあなたの特定の翻訳キーのスキーマを知りません。

- **宣言的:** AIの出力をテキストからキーに置き換えるために書き直す必要があります。
- **コンパイラ:** AIのコードをコピー＆ペーストするだけで、そのまま動作します。

## 現実の確認：「マジック」が危険な理由

「マジック」は魅力的ですが、抽象化の漏れが発生します。ビルドツールに人間の意図を理解させることに依存すると、アーキテクチャの脆弱性が生じます。

### ヒューリスティックな脆弱性（推測ゲーム）

コンパイラは何がコンテンツで何がコードかを推測しなければなりません。これにより、ツールと「戦う」ことになるエッジケースが発生します。

以下のシナリオを考えてみてください：

- `<span className="active"></span>` は抽出されますか？（文字列ですが、おそらくクラス名です）。
- `<span status="pending"></span>` は抽出されますか？（これはプロップの値です）。
- `<span>{"Hello World"}</span>` は抽出されますか？（これはJSの式です）。
- `<span>Hello {name}. How are you?</span>` は抽出されますか？（補間が複雑です）。
- `<span aria-label="Image of cat"></span>` は抽出されますか？（アクセシビリティ属性は翻訳が必要です）。
- `<span data-testid="my-element"></span>` は抽出されますか？（テストIDは翻訳すべきではありません）。
- `<MyComponent errorMessage="An error occurred" />` は抽出されますか？
- `<p>This is a paragraph{" "}\n containing multiple lines</p>` は抽出されますか？
- `<p>{getStatusMessage()}</p>` の関数結果は抽出されますか？
- `<div>{isLoading ? "The page is loading" : <MyComponent/>} </div>` は抽出されますか？
- `<span>AX-99</span>` のような製品IDは抽出されますか？

最終的には、アプリケーションのロジックを壊さないように、`// ignore-translation` のような特定のコメントや、`data-compiler-ignore="true"` のような特定のプロパティを追加することになります。

### Intlayerはこの複雑さをどのように処理するのか？

Intlayerは、フィールドが翻訳のために抽出されるべきかを検出するために混合アプローチを使用しており、誤検出を最小限に抑えようとしています。

1.  **AST解析:** 要素のタイプをチェックします（例：`reactNode`、`label`、または`title`プロパティの区別など）。
2.  **パターン認識:** 文字列が大文字で始まっているか、スペースを含んでいるかを検出し、それがコード識別子ではなく人間が読めるテキストである可能性が高いことを示唆します。

### 動的データのハードリミット

コンパイラの抽出は**静的解析**に依存しています。安定したIDを生成するために、コード内のリテラル文字列を認識する必要があります。
もしAPIが `server_error` のようなエラーコード文字列を返す場合、コンパイラはビルド時にその文字列が存在することを認識できないため、コンパイラで翻訳することはできません。動的データのために、別途「ランタイム専用」のシステムを構築する必要があります。

### チャンク分割の欠如

特定のコンパイラはページごとに翻訳をチャンク分割しません。もしコンパイラが言語ごとに大きなJSONファイル（例：`./lang/en.json`、`./lang/fr.json`など）を生成する場合、単一の訪問ページのためにすべてのページのコンテンツを読み込むことになりがちです。また、コンテンツを使用する各コンポーネントは必要以上に多くのコンテンツでハイドレートされる可能性があり、パフォーマンスの問題を引き起こすことがあります。

また、翻訳を動的に読み込む際には注意が必要です。これが適切に行われないと、現在の言語に加えてすべての言語のコンテンツが読み込まれてしまいます。

> 問題を説明するために、10ページと10言語（すべて100％ユニーク）を持つサイトを考えてみてください。すると、99ページ分の追加コンテンツ（10 × 10 - 1）を読み込むことになります。

### 「チャンク爆発」とネットワークウォーターフォール

チャンク問題を解決するために、一部のソリューションはコンポーネント単位、あるいはキー単位でのチャンク分割を提供しています。しかし、この問題は部分的にしか解決されていません。これらのソリューションのセールスポイントはしばしば「コンテンツがツリーシェイクされる」という点にあります。

確かに、コンテンツを静的に読み込む場合、ソリューションは未使用のコンテンツをツリーシェイクしますが、それでもすべての言語のコンテンツがアプリケーションと共に読み込まれてしまいます。

では、なぜ動的に読み込まないのでしょうか？確かにその場合、必要以上のコンテンツを読み込むことになりますが、それにはトレードオフがあります。

コンテンツを動的に読み込むと、各コンテンツがそれぞれ独立したチャンクに分離され、コンポーネントがレンダリングされるときにのみ読み込まれます。つまり、テキストブロックごとに1つのHTTPリクエストが発生します。ページに1,000のテキストブロックがある場合？ → サーバーに1,000回のHTTPリクエストが送信されます。そして、ダメージを最小限に抑え、アプリケーションの初回レンダリング時間を最適化するために、複数のSuspense境界やSkeleton Loaderを挿入する必要があります。

> 注意: Next.jsとSSRを使用していても、コンポーネントは読み込み後にハイドレートされるため、HTTPリクエストは依然として発生します。

解決策は？`i18next`、`next-intl`、または `intlayer` のように、スコープ付きのコンテンツ宣言を可能にするソリューションを採用することです。

> 注意: `i18next` と `next-intl` は、バンドルサイズを最適化するために、各ページごとに名前空間やメッセージのインポートを手動で管理する必要があります。`rollup-plugin-visualizer`（vite）、`@next/bundle-analyzer`（next.js）、または `webpack-bundle-analyzer`（React CRA / Angular / など）などのバンドルアナライザーを使用して、未使用の翻訳でバンドルが汚染されていないかを検出することを推奨します。

### ランタイムパフォーマンスのオーバーヘッド

翻訳をリアクティブにするため（言語を切り替えた際に即座に更新されるように）、コンパイラはしばしばすべてのコンポーネントに状態管理のフックを注入します。

- **コスト:** もし5,000件のアイテムのリストをレンダリングすると、テキストのためだけに5,000個の `useState` と `useEffect` フックを初期化することになります。Reactはすべての5,000のコンシューマーを同時に特定し、再レンダリングしなければなりません。これにより大規模な「メインスレッド」ブロックが発生し、切り替え時にUIがフリーズします。これは宣言的ライブラリ（通常は単一のContextプロバイダーを使用する）が節約するメモリとCPUサイクルを大量に消費します。

> React以外のフレームワークでも同様の問題があることに注意してください。

## 落とし穴：ベンダーロックイン

翻訳キーの抽出や移行を可能にするi18nソリューションを選ぶ際は注意してください。

宣言型ライブラリの場合、ソースコードには明示的に翻訳の意図が含まれています。これらがキーであり、あなたがそれを管理します。ライブラリを変更したい場合は、通常インポートを更新するだけで済みます。

コンパイラ方式では、ソースコードは単なる英語のテキストであり、翻訳ロジックの痕跡はありません。すべてはビルドツールの設定に隠されています。そのプラグインがメンテナンスされなくなったり、別のソリューションに変更したい場合、行き詰まる可能性があります。「イジェクト」する簡単な方法はなく、コード内に使えるキーが存在しないため、新しいライブラリ用にすべての翻訳を再生成する必要があるかもしれません。

一部のソリューションは翻訳生成サービスも提供しています。クレジットがなくなったら？翻訳もなくなります。

コンパイラはしばしばテキストをハッシュ化します（例：`"Hello World"` -> `x7f2a`）。あなたの翻訳ファイルは `{ "x7f2a": "Hola Mundo" }` のように見えます。罠は、もしライブラリを切り替えた場合、新しいライブラリは `"Hello World"` を見てそのキーを探しますが、翻訳ファイルはハッシュ（`x7f2a`）でいっぱいなので見つかりません。

### プラットフォーム依存

コンパイラベースのアプローチを選択することで、基盤となるプラットフォームに縛られることになります。例えば、特定のコンパイラはすべてのバンドラー（Vite、Turbopack、Metroなど）で利用できるわけではありません。これにより、将来の移行が困難になり、すべてのアプリケーションをカバーするために複数のソリューションを採用する必要があるかもしれません。

## もう一方の側面：宣言的アプローチのリスク

公平に言うと、従来の宣言的な方法も完璧ではありません。独自の「落とし穴」があります。

1.  **ネームスペース地獄:** どのJSONファイルを読み込むか（`common.json`、`dashboard.json`、`footer.json`など）を手動で管理する必要がよくあります。もし一つでも忘れると、ユーザーは生のキーを目にすることになります。
2.  **過剰フェッチ:** 注意深い設定をしないと、初回ロード時にすべてのページのすべての翻訳キーを誤って読み込んでしまい、バンドルサイズが膨れ上がることが非常に簡単に起こります。
3.  **同期のズレ:** コンポーネントが削除された後も、そのコンポーネントで使用されていたキーがJSONファイルに残り続けることがよくあります。翻訳ファイルは無限に増え続け、「ゾンビキー」で溢れてしまいます。

## Intlayerの中間的アプローチ

ここで、**Intlayer**のようなツールが革新を試みています。Intlayerは、コンパイラは強力である一方で、暗黙のマジックは危険であることを理解しています。

Intlayerは混合アプローチを提供し、宣言的なコンテンツ管理の利点と、開発時間を節約するためのコンパイラとの互換性の両方を享受できるようにしています。

そして、たとえIntlayerのコンパイラを使用しなくても、Intlayerは`transform`コマンド（VSCode拡張機能からもアクセス可能）を提供しています。隠れたビルドステップで魔法のように処理するのではなく、実際に**コンポーネントのコードを書き換える**ことができます。テキストをスキャンし、コードベース内で明示的なコンテンツ宣言に置き換えます。

これにより、両方の利点を享受できます：

1.  **粒度:** 翻訳をコンポーネントの近くに保持できるため（モジュール性とツリーシェイキングが向上します）。
2.  **安全性:** 翻訳が隠れたビルド時の魔法ではなく、明示的なコードになります。
3.  **ロックインなし:** コードがリポジトリ内で宣言的な構造に変換されるため、タブキーを押したり、IDEのCopilotを使ってコンテンツ宣言を簡単に生成できます。Webpackプラグインにロジックを隠すことはありません。

## 結論

では、どちらを選ぶべきでしょうか？

**MVPを構築している場合、または迅速に進めたい場合：**  
コンパイラベースのアプローチは有効な選択肢です。非常に速く進めることができます。ファイル構造やキーについて心配する必要はありません。ただ構築するだけです。技術的負債は「未来のあなた」の問題です。

**ジュニア開発者である場合、または最適化を気にしない場合：**  
手動管理を最小限にしたいなら、コンパイラベースのアプローチが最適でしょう。キーや翻訳ファイルを自分で扱う必要はなく、テキストを書くだけでコンパイラが残りを自動化します。これにより、セットアップの手間や手動ステップに伴う一般的なi18nのミスが減ります。

**すでに何千ものコンポーネントを含む既存プロジェクトを国際化する場合：**  
コンパイラベースのアプローチは、ここでは実用的な選択肢となり得ます。初期の抽出フェーズは、数週間から数ヶ月に及ぶ手作業を節約できます。ただし、Intlayerの`transform`コマンドのようなツールを使用することを検討してください。これは文字列を抽出し、明示的な宣言的コンテンツ宣言に変換することができます。これにより、自動化のスピードを得ながら、宣言的アプローチの安全性と移植性を維持できます。両方の利点を享受できるのです。すなわち、長期的なアーキテクチャの負債なしに迅速な初期移行が可能になります。

**プロフェッショナルでエンタープライズグレードのアプリケーションを構築している場合：**
マジックは一般的に悪い考えです。制御が必要です。

- バックエンドからの動的データを扱う必要があります。
- 低スペックデバイスでのパフォーマンスを確保する必要があります（フックの爆発を避けるため）。
- 特定のビルドツールに永遠に縛られないようにする必要があります。

プロフェッショナルなアプリケーションの場合、**宣言的コンテンツ管理**（Intlayerや確立されたライブラリのようなもの）が依然としてゴールドスタンダードです。これにより関心事が分離され、アーキテクチャがクリーンに保たれ、アプリケーションが複数言語に対応する能力が「ブラックボックス」コンパイラに意図を推測させることに依存しなくなります。
