@intlayer/docs 6.1.4 → 6.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +1135 -75
  2. package/blog/ar/nextjs-multilingual-seo-comparison.md +364 -0
  3. package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +1139 -72
  4. package/blog/de/nextjs-multilingual-seo-comparison.md +362 -0
  5. package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +224 -240
  6. package/blog/en/nextjs-multilingual-seo-comparison.md +360 -0
  7. package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +1134 -37
  8. package/blog/en-GB/nextjs-multilingual-seo-comparison.md +360 -0
  9. package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +1122 -64
  10. package/blog/es/nextjs-multilingual-seo-comparison.md +363 -0
  11. package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +1132 -75
  12. package/blog/fr/nextjs-multilingual-seo-comparison.md +362 -0
  13. package/blog/hi/nextjs-multilingual-seo-comparison.md +363 -0
  14. package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +1120 -55
  15. package/blog/it/nextjs-multilingual-seo-comparison.md +363 -0
  16. package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +1140 -76
  17. package/blog/ja/nextjs-multilingual-seo-comparison.md +362 -0
  18. package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +1129 -73
  19. package/blog/ko/nextjs-multilingual-seo-comparison.md +362 -0
  20. package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +1133 -76
  21. package/blog/pt/nextjs-multilingual-seo-comparison.md +362 -0
  22. package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +1142 -74
  23. package/blog/ru/nextjs-multilingual-seo-comparison.md +370 -0
  24. package/blog/tr/nextjs-multilingual-seo-comparison.md +362 -0
  25. package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +1142 -75
  26. package/blog/zh/nextjs-multilingual-seo-comparison.md +394 -0
  27. package/dist/cjs/generated/blog.entry.cjs +16 -0
  28. package/dist/cjs/generated/blog.entry.cjs.map +1 -1
  29. package/dist/esm/generated/blog.entry.mjs +16 -0
  30. package/dist/esm/generated/blog.entry.mjs.map +1 -1
  31. package/dist/types/generated/blog.entry.d.ts +1 -0
  32. package/dist/types/generated/blog.entry.d.ts.map +1 -1
  33. package/docs/en/interest_of_intlayer.md +2 -2
  34. package/package.json +10 -10
  35. package/src/generated/blog.entry.ts +16 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  createdAt: 2025-08-23
3
- updatedAt: 2025-08-23
3
+ updatedAt: 2025-09-29
4
4
  title: next-i18next vs next-intl vs Intlayer
5
5
  description: Next.jsアプリの国際化(i18n)におけるnext-i18next、next-intl、Intlayerの比較
6
6
  keywords:
@@ -19,144 +19,1208 @@ slugs:
19
19
 
20
20
  # next-i18next VS next-intl VS intlayer | Next.jsの国際化(i18n)
21
21
 
22
- 本ガイドでは、**Next.js**で広く使われている3つのi18nオプション、**next-intl**、**next-i18next**、および**Intlayer**を比較します。
23
- 対象は**Next.js 13+のApp Router**(**React Server Components**対応)で、以下の点を評価します:
22
+ Next.js向けの3つのi18nオプション、next-i18next、next-intl、Intlayerの類似点と相違点を見ていきましょう。
23
+
24
+ これは完全なチュートリアルではなく、選択の参考となる比較です。
25
+
26
+ 私たちは**Next.js 13+のApp Router**(**React Server Components**対応)に焦点を当て、以下を評価します:
24
27
 
25
28
  1. **アーキテクチャとコンテンツの構成**
26
29
  2. **TypeScriptと安全性**
27
- 3. **翻訳欠落の取り扱い**
30
+ 3. **翻訳欠落の処理**
28
31
  4. **ルーティングとミドルウェア**
29
32
  5. **パフォーマンスと読み込み挙動**
30
33
  6. **開発者体験(DX)、ツールとメンテナンス**
31
- 7. **SEOと大規模プロジェクトのスケーラビリティ**
34
+ 7. **SEOと大規模プロジェクトの拡張性**
35
+
36
+ > **要約**: 3つのいずれもNext.jsアプリのローカライズが可能です。もし**コンポーネント単位のコンテンツ管理**、**厳格なTypeScript型**、**ビルド時の欠落キー検出**、**ツリーシェイク可能な辞書**、そして**一流のApp Router + SEOヘルパー**を求めるなら、**Intlayer**が最も完全でモダンな選択肢です。
32
37
 
33
- > **要約**: いずれのツールもNext.jsアプリのローカライズが可能です。もし**コンポーネント単位のコンテンツ管理**、**厳格なTypeScript型**、**ビルド時の欠落キー検出**、**ツリーシェイク可能な辞書**、そして**一流のApp RouterとSEOヘルパー**を求めるなら、**Intlayer**が最も包括的でモダンな選択肢です。
38
+ > 開発者がよく混同しがちなのは、`next-intl`が`react-intl`のNext.js版だと思うことです。そうではありません。`next-intl`は[Amann](https://github.com/amannn)によってメンテナンスされており、`react-intl`は[FormatJS](https://github.com/formatjs/formatjs)によってメンテナンスされています。
34
39
 
35
40
  ---
36
41
 
37
- ## 高レベルのポジショニング
42
+ ## 簡単に言うと
38
43
 
39
- - **next-intl** - 軽量でシンプルなメッセージフォーマットを提供し、Next.jsとの親和性が高いです。カタログは中央集権的に管理されることが多く、開発者体験はシンプルですが、安全性や大規模なメンテナンスは主にユーザーの責任となります。
40
- - **next-i18next** - Next.js向けにラップされたi18next。成熟したエコシステムとプラグイン(例:ICU)による機能を備えていますが、設定が冗長になりがちで、プロジェクトが大きくなるにつれてカタログは中央集権化しやすいです。
41
- - **Intlayer** - Next.js向けのコンポーネント中心のコンテンツモデル、**厳格なTS型付け**、**ビルド時チェック**、**ツリーシェイク**、**組み込みのミドルウェアとSEOヘルパー**、オプションの**ビジュアルエディター/CMS**、および**AI支援翻訳**を提供します。
44
+ - **next-intl** - 軽量でシンプルなメッセージフォーマットを提供し、Next.jsのサポートがしっかりしています。カタログは中央集権的であることが多く、開発者体験(DX)はシンプルですが、安全性や大規模なメンテナンスは主にあなたの責任となります。
45
+ - **next-i18next** - Next.js向けにラップされたi18nextです。成熟したエコシステムとプラグイン(例:ICU)による機能を持ちますが、設定が冗長になりがちで、プロジェクトが大きくなるにつれてカタログは中央集権化しやすいです。
46
+ - **Intlayer** - Next.js向けのコンポーネント中心のコンテンツモデル、**厳格なTS型付け**、**ビルド時チェック**、**ツリーシェイキング**、**組み込みのミドルウェア&SEOヘルパー**、オプションの**ビジュアルエディター/CMS**、および**AI支援翻訳**。
42
47
 
43
48
  ---
44
49
 
45
- ## 機能の比較(Next.jsに特化)
50
+ | Library | GitHub Stars | Total Commits | Last Commit | First Version | NPM Version | NPM Downloads |
51
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
52
+ | `aymericzip/intlayer` | [![GitHub Repo stars](https://img.shields.io/github/stars/aymericzip/intlayer?style=for-the-badge&label=%E2%AD%90%20stars)](https://github.com/aymericzip/intlayer/stargazers) | [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/aymericzip/intlayer?style=for-the-badge&label=commits)](https://github.com/aymericzip/intlayer/commits) | [![Last Commit](https://img.shields.io/github/last-commit/aymericzip/intlayer?style=for-the-badge)](https://github.com/aymericzip/intlayer/commits) | April 2024 | [![npm](https://img.shields.io/npm/v/intlayer?style=for-the-badge)](https://www.npmjs.com/package/intlayer) | [![npm downloads](https://img.shields.io/npm/dm/intlayer?style=for-the-badge)](https://www.npmjs.com/package/intlayer) |
53
+ | `amannn/next-intl` | [![GitHub Repo stars](https://img.shields.io/github/stars/amannn/next-intl?style=for-the-badge&label=%E2%AD%90%20stars)](https://github.com/amannn/next-intl/stargazers) | [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/amannn/next-intl?style=for-the-badge&label=commits)](https://github.com/amannn/next-intl/commits) | [![Last Commit](https://img.shields.io/github/last-commit/amannn/next-intl?style=for-the-badge)](https://github.com/amannn/next-intl/commits) | Nov 2020 | [![npm](https://img.shields.io/npm/v/next-intl?style=for-the-badge)](https://www.npmjs.com/package/next-intl) | [![npm downloads](https://img.shields.io/npm/dm/next-intl?style=for-the-badge)](https://www.npmjs.com/package/next-intl) |
54
+ | `i18next/i18next` | [![GitHub Repo stars](https://img.shields.io/github/stars/i18next/i18next?style=for-the-badge&label=%E2%AD%90%20stars)](https://github.com/i18next/i18next/stargazers) | [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/i18next/i18next?style=for-the-badge&label=commits)](https://github.com/i18next/i18next/commits) | [![Last Commit](https://img.shields.io/github/last-commit/i18next/i18next?style=for-the-badge)](https://github.com/i18next/i18next/commits) | Jan 2012 | [![npm](https://img.shields.io/npm/v/i18next?style=for-the-badge)](https://www.npmjs.com/package/i18next) | [![npm downloads](https://img.shields.io/npm/dm/i18next?style=for-the-badge)](https://www.npmjs.com/package/i18next) |
55
+ | `i18next/next-i18next` | [![GitHub Repo stars](https://img.shields.io/github/stars/i18next/next-i18next?style=for-the-badge&label=%E2%AD%90%20stars)](https://github.com/i18next/next-i18next/stargazers) | [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/i18next/next-i18next?style=for-the-badge&label=commits)](https://github.com/i18next/next-i18next/commits) | [![Last Commit](https://img.shields.io/github/last-commit/i18next/next-i18next?style=for-the-badge)](https://github.com/i18next/next-i18next/commits) | Nov 2018 | [![npm](https://img.shields.io/npm/v/next-i18next?style=for-the-badge)](https://www.npmjs.com/package/next-i18next) | [![npm downloads](https://img.shields.io/npm/dm/next-i18next?style=for-the-badge)](https://www.npmjs.com/package/next-i18next) |
46
56
 
47
- | 機能 | `next-intlayer` (Intlayer) | `next-intl` | `next-i18next` |
48
- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
49
- | **コンポーネント近くの翻訳** | ✅ はい、各コンポーネントにコンテンツが配置されています | ❌ いいえ | ❌ いいえ |
50
- | **TypeScript統合** | ✅ 高度、厳密な型が自動生成されます | ✅ 良好 | ⚠️ 基本的 |
51
- | **翻訳漏れ検出** | ✅ TypeScriptのエラー強調表示およびビルド時のエラー/警告 | ⚠️ 実行時フォールバック | ⚠️ 実行時フォールバック |
52
- | **リッチコンテンツ(JSX/Markdown/コンポーネント)** | ✅ 直接サポート | ❌ リッチノード向けに設計されていません | ⚠️ 制限あり |
53
- | **AI搭載翻訳** | ✅ はい、複数のAIプロバイダーをサポート。独自のAPIキーを使用可能。アプリケーションとコンテンツの範囲のコンテキストを考慮します。 | ❌ いいえ | ❌ いいえ |
54
- | **ビジュアルエディター** | ✅ はい、ローカルのビジュアルエディター+オプションのCMS;コードベースのコンテンツを外部化可能;埋め込み可能 | ❌ いいえ / 外部のローカリゼーションプラットフォーム経由で利用可能 | ❌ いいえ / 外部のローカリゼーションプラットフォーム経由で利用可能 |
55
- | **ローカライズされたルーティング** | ✅ はい、ローカライズされたパスを標準でサポート(Next.js & Viteで動作) | ✅ 組み込み、App Routerは`[locale]`セグメントをサポート | ✅ 組み込み |
56
- | **動的ルート生成** | ✅ はい | ✅ はい | ✅ はい |
57
- | **複数形処理** | ✅ 列挙ベースのパターン | ✅ 良好 | ✅ 良好 |
58
- | **フォーマット(日時、数値、通貨)** | ✅ 最適化されたフォーマッター(内部でIntlを使用) | ✅ 良好(Intlヘルパー) | ✅ 良好(Intlヘルパー) |
59
- | **コンテンツフォーマット** | ✅ .tsx, .ts, .js, .json, .md, .txt, (.yaml 作業中) | ✅ .json, .js, .ts | ⚠️ .json |
60
- | **ICUサポート** | ⚠️ 作業中 | ✅ あり | ⚠️ プラグイン経由(`i18next-icu`) |
61
- | **SEOヘルパー(hreflang、サイトマップ)** | ✅ 組み込みツール:サイトマップ、robots.txt、メタデータのヘルパー | ✅ 良好 | ✅ 良好 |
62
- | **エコシステム / コミュニティ** | ⚠️ 小規模だが急速に成長し、反応が良い | ✅ 中規模、Next.jsに特化 | ✅ 中規模、Next.jsに特化 |
63
- | **サーバーサイドレンダリング&サーバーコンポーネント** | ✅ はい、SSR / Reactサーバーコンポーネント向けに最適化されています | ⚠️ ページレベルでサポートされていますが、子のサーバーコンポーネントに対してt関数をコンポーネントツリーに渡す必要があります | ⚠️ ページレベルでサポートされていますが、子のサーバーコンポーネントに対してt関数をコンポーネントツリーに渡す必要があります |
64
- | **ツリーシェイキング(使用されるコンテンツのみ読み込み)** | ✅ はい、Babel/SWCプラグインを使用してビルド時にコンポーネント単位で実施されます | ⚠️ 部分的に対応 | ⚠️ 部分的に対応 |
65
- | **遅延読み込み** | ✅ はい、ロケールごと / 辞書ごと | ✅ はい(ルートごと / ロケールごと)、名前空間管理が必要 | ✅ はい(ルートごと / ロケールごと)、名前空間管理が必要 |
66
- | **未使用コンテンツの削除** | ✅ はい、ビルド時に辞書ごと | ❌ いいえ、名前空間管理で手動管理可能 | ❌ いいえ、名前空間管理で手動管理可能 |
67
- | **大規模プロジェクトの管理** | ✅ モジュール化を推奨し、デザインシステムに適している | ✅ セットアップによるモジュール化 | ✅ セットアップによるモジュール化 |
57
+ > バッジは自動的に更新されます。スナップショットは時間とともに変動します。
68
58
 
69
59
  ---
70
60
 
71
- ## 詳細比較
61
+ ## 並列機能比較(Next.jsに特化)
72
62
 
73
- ### 1) アーキテクチャとスケーラビリティ
63
+ | 機能 | `next-intlayer` (Intlayer) | `next-intl` | `next-i18next` |
74
64
 
75
- - **next-intl / next-i18next**: ロケールごとの**集中型カタログ**(i18nextでは**ネームスペース**も)をデフォルトとする。初期段階では問題なく機能するが、結合度が高まりキーの変更が頻繁になるにつれて、大きな共有領域となることが多い。
76
- - **Intlayer**: コードと共に配置された **コンポーネント単位**(または機能単位)の辞書を推奨します。これにより認知負荷が軽減され、UIパーツの複製や移行が容易になり、チーム間の競合も減少します。未使用のコンテンツも自然に見つけやすく、削除しやすくなります。
65
+ > バッジは自動的に更新されます。スナップショットは時間とともに変化します。
66
+
67
+ ---
77
68
 
78
- **重要な理由:** 大規模なコードベースやデザインシステムのセットアップでは、**モジュール化されたコンテンツ**の方がモノリシックなカタログよりもスケールしやすいです。
69
+ ## 並列機能比較(Next.jsに特化)
70
+
71
+ | 機能 | `next-intlayer` (Intlayer) | `next-intl` | `next-i18next` |
72
+ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
73
+ | **コンポーネント近くの翻訳** | ✅ はい、各コンポーネントにコンテンツが配置されています | ❌ いいえ | ❌ いいえ |
74
+ | **TypeScript 統合** | ✅ 高度、自動生成された厳密な型 | ✅ 良好 | ⚠️ 基本 |
75
+ | **翻訳漏れ検出** | ✅ TypeScript エラーのハイライトおよびビルド時のエラー/警告 | ⚠️ ランタイムフォールバック | ⚠️ ランタイムフォールバック |
76
+ | **リッチコンテンツ(JSX/Markdown/コンポーネント)** | ✅ 直接サポート | ❌ リッチノード向けに設計されていません | ⚠️ 制限あり |
77
+ | **AI搭載翻訳** | ✅ はい、複数のAIプロバイダーをサポート。独自のAPIキーを使用可能。アプリケーションのコンテキストとコンテンツの範囲を考慮します | ❌ いいえ | ❌ いいえ |
78
+ | **ビジュアルエディター** | ✅ はい、ローカルのビジュアルエディター+オプションのCMS;コードベースのコンテンツを外部化可能;埋め込み可能 | ❌ いいえ/外部のローカリゼーションプラットフォーム経由で利用可能 | ❌ いいえ/外部のローカリゼーションプラットフォーム経由で利用可能 |
79
+ | **ローカライズされたルーティング** | ✅ はい、標準でローカライズされたパスをサポート(Next.js & Viteで動作) | ✅ 組み込み、App Routerは`[locale]`セグメントをサポート | ✅ 組み込み |
80
+ | **動的ルート生成** | ✅ はい | ✅ はい | ✅ はい |
81
+ | **複数形対応** | ✅ 列挙ベースのパターン | ✅ 良好 | ✅ 良好 |
82
+ | **フォーマット(日時、数値、通貨)** | ✅ 最適化されたフォーマッター(内部でIntlを使用) | ✅ 良好(Intlヘルパー) | ✅ 良好(Intlヘルパー) |
83
+ | **コンテンツフォーマット** | ✅ .tsx、.ts、.js、.json、.md、.txt、(.yaml 開発中) | ✅ .json、.js、.ts | ⚠️ .json |
84
+ | **ICUサポート** | ⚠️ 作業中 | ✅ あり | ⚠️ プラグイン経由(`i18next-icu`) |
85
+ | **SEOヘルパー(hreflang、サイトマップ)** | ✅ 組み込みツール:サイトマップ、robots.txt、メタデータのヘルパー | ✅ 良好 | ✅ 良好 |
86
+ | **エコシステム / コミュニティ** | ⚠️ 小規模だが急速に成長し、反応が良い | ✅ 良好 | ✅ 良好 |
87
+ | **サーバーサイドレンダリング & サーバーコンポーネント** | ✅ はい、SSR / Reactサーバーコンポーネント向けに最適化 | ⚠️ ページレベルでサポートされているが、子のサーバーコンポーネントに対してt関数をコンポーネントツリーに渡す必要がある | ⚠️ ページレベルでサポートされているが、子のサーバーコンポーネントに対してt関数をコンポーネントツリーに渡す必要がある |
88
+ | **ツリーシェイキング(使用されたコンテンツのみを読み込む)** | ✅ はい、Babel/SWCプラグインを使用したビルド時のコンポーネント単位で対応 | ⚠️ 部分的に対応 | ⚠️ 部分的に対応 |
89
+ | **遅延読み込み** | ✅ はい、ロケール単位 / 辞書単位で対応 | ✅ はい(ルート単位/ロケール単位)、名前空間管理が必要 | ✅ はい(ルート単位/ロケール単位)、名前空間管理が必要 |
90
+ | **未使用コンテンツの削除** | ✅ はい、ビルド時に辞書単位で対応 | ❌ いいえ、名前空間管理で手動対応可能 | ❌ いいえ、名前空間管理で手動対応可能 |
91
+ | **大規模プロジェクトの管理** | ✅ モジュール化を推奨し、デザインシステムに適している | ✅ セットアップによるモジュール化対応 | ✅ セットアップによるモジュール化対応 |
92
+ | **翻訳漏れのテスト(CLI/CI)** | ✅ CLI: `npx intlayer content test`(CIに適した監査) | ⚠️ 組み込みではない;ドキュメントでは `npx @lingual/i18n-check` を推奨 | ⚠️ 組み込みではない;i18nextツールやランタイムの `saveMissing` に依存 |
79
93
 
80
94
  ---
81
95
 
82
- ### 2) TypeScript と安全性
96
+ ## はじめに
83
97
 
84
- - **next-intl**: 安定した TypeScript サポートがありますが、**キーはデフォルトで厳密に型付けされていません**。安全性のパターンは手動で維持する必要があります。
85
- - **next-i18next**: フックの基本的な型定義がありますが、**厳密なキーの型付けには追加のツールや設定が必要です**。
86
- - **Intlayer**: コンテンツから**厳密な型を生成**します。**IDEの自動補完**や**コンパイル時のエラー**により、デプロイ前にタイプミスやキーの欠落を検出します。
98
+ Next.jsは国際化されたルーティング(例:ロケールセグメント)を組み込みでサポートしています。しかし、その機能だけでは翻訳は行われません。ユーザーにローカライズされたコンテンツを表示するには、別途ライブラリが必要です。
87
99
 
88
- **なぜ重要か:** 強い型付けにより、失敗を**右(ランタイム)**ではなく**左(CI/ビルド時)**にシフトさせます。
100
+ 多くのi18nライブラリが存在しますが、Next.jsの世界では現在、next-i18next、next-intl、そしてIntlayerの3つが注目されています。
89
101
 
90
102
  ---
91
103
 
92
- ### 3) 翻訳欠落の取り扱い
104
+ ## アーキテクチャとスケーラビリティ
93
105
 
94
- - **next-intl / next-i18next**: **ランタイムのフォールバック**に依存(例:キーやデフォルトロケールを表示)。ビルドは失敗しません。
95
- - **Intlayer**: **ビルド時検出**により、ロケールやキーの欠落に対して**警告/エラー**を出します。
106
+ - **next-intl / next-i18next**: ロケールごとに **集中管理されたカタログ**(および i18next の場合は **ネームスペース**)をデフォルトとします。初期段階では問題なく機能しますが、結合度が高まりキーの変更が頻繁になると、大きな共有領域となってしまいます。
107
+ - **Intlayer**: サービスするコードと **共置** された **コンポーネント単位**(または機能単位)の辞書を推奨します。これにより認知負荷が軽減され、UIパーツの重複や移行が容易になり、チーム間の競合も減少します。未使用のコンテンツも自然に見つけやすく削除しやすくなります。
96
108
 
97
- **なぜ重要か:** ビルド時にギャップを検出することで、本番環境での「謎の文字列」発生を防ぎ、厳格なリリースゲートに適合します。
109
+ **なぜ重要か:** 大規模なコードベースやデザインシステムのセットアップでは、**モジュール化されたコンテンツ**の方がモノリシックなカタログよりもスケールしやすいです。
98
110
 
99
111
  ---
100
112
 
101
- ### 4) ルーティング、ミドルウェア、URL戦略
113
+ ## バンドルサイズと依存関係
114
+
115
+ アプリケーションをビルドした後、バンドルとはブラウザがページをレンダリングするために読み込むJavaScriptのことです。したがって、バンドルサイズはアプリケーションのパフォーマンスにとって重要です。
116
+
117
+ 多言語アプリケーションのバンドルにおいて重要な2つの要素は以下の通りです:
118
+
119
+ - アプリケーションコード
120
+ - ブラウザによって読み込まれるコンテンツ
121
+
122
+ ## アプリケーションコード
123
+
124
+ この場合、アプリケーションコードの重要性は最小限です。3つのソリューションすべてがツリーシェイカブルであり、未使用のコード部分はバンドルに含まれません。
125
+
126
+ 以下は、3つのソリューションを用いた多言語アプリケーションでブラウザが読み込むJavaScriptバンドルサイズの比較です。
127
+
128
+ アプリケーション内でフォーマッターを必要としない場合、ツリーシェイキング後にエクスポートされる関数のリストは以下のようになります:
129
+
130
+ - **next-intlayer**: `useIntlayer`, `useLocale`, `NextIntlClientProvider`、(バンドルサイズは180.6 kB -> 78.6 kB(gzip))
131
+ - **next-intl**: `useTranslations`, `useLocale`, `NextIntlClientProvider`、(バンドルサイズは101.3 kB -> 31.4 kB(gzip))
132
+ - **next-i18next**: `useTranslation`, `useI18n`, `I18nextProvider`、(バンドルサイズは80.7 kB -> 25.5 kB(gzip))
133
+
134
+ これらの関数はReactのコンテキスト/ステートのラッパーに過ぎないため、i18nライブラリがバンドルサイズに与える影響は最小限です。
135
+
136
+ > Intlayerは、`useIntlayer`関数により多くのロジックを含んでいるため、`next-intl`や`next-i18next`よりわずかに大きくなっています。これはマークダウンや`intlayer-editor`の統合に関連しています。
137
+
138
+ ## コンテンツと翻訳
139
+
140
+ この部分は開発者によってしばしば無視されますが、10ページで構成され、10言語に対応したアプリケーションの場合を考えてみましょう。計算を簡単にするために、各ページが100%ユニークなコンテンツを統合していると仮定します(実際には、ページタイトル、ヘッダー、フッターなど、ページ間で重複するコンテンツが多くあります)。
102
141
 
103
- - 3つとも、App Router上の**Next.jsのローカライズルーティング**に対応しています。
104
- - **Intlayer** はさらに進んで、**i18n ミドルウェア**(ヘッダーやクッキーによるロケール検出)や、ローカライズされた URL や `<link rel="alternate" hreflang="…">` タグを生成するための **ヘルパー** を提供します。
142
+ `/fr/about` ページを訪れたいユーザーは、特定の言語で1ページ分のコンテンツを読み込みます。コンテンツの最適化を無視すると、アプリケーションのコンテンツの8,200% `((1 + (((10ページ - 1) × (10言語 - 1)))) × 100)` を不必要に読み込むことになります。この問題がわかりますか?このコンテンツがテキストのままであっても、おそらくサイトの画像の最適化を考える方が多いでしょうが、無駄なコンテンツを世界中に送信し、ユーザーのコンピューターに無意味な処理をさせているのです。
105
143
 
106
- **重要な理由:** カスタムの接着層が減り、**一貫した UX** と **クリーンな SEO** をロケール間で実現します。
144
+ 2つの重要な問題:
145
+
146
+ - **ルートによる分割:**
147
+
148
+ > `/about` ページにいる場合、`/home` ページのコンテンツを読み込みたくない
149
+
150
+ - **ロケールによる分割:**
151
+
152
+ > `/fr/about` ページにいる場合、`/en/about` ページのコンテンツを読み込みたくない
153
+
154
+ 改めて、これら3つのソリューションはこれらの問題を認識しており、これらの最適化を管理することができます。3つのソリューションの違いはDX(開発者体験)にあります。
155
+
156
+ `next-intl` と `next-i18next` は翻訳を管理するために集中管理型のアプローチを使用しており、ロケールやサブファイルごとにJSONを分割することが可能です。`next-i18next` ではJSONファイルを「ネームスペース」と呼び、`next-intl` ではメッセージを宣言することができます。`intlayer` ではJSONファイルを「辞書」と呼びます。
157
+
158
+ - `next-intl`の場合は、`next-i18next`と同様に、コンテンツはページやレイアウトレベルで読み込まれ、その後このコンテンツがコンテキストプロバイダーに読み込まれます。つまり、開発者は各ページで読み込まれるJSONファイルを手動で管理する必要があります。
159
+
160
+ > 実際には、開発者はこの最適化を省略し、単純さのためにページのコンテキストプロバイダーにすべてのコンテンツを読み込むことを好むことが多いです。
161
+
162
+ - `intlayer`の場合は、すべてのコンテンツがアプリケーション内で読み込まれます。その後、プラグイン(`@intlayer/babel` / `@intlayer/swc`)がバンドルを最適化し、ページで使用されるコンテンツのみを読み込みます。したがって、開発者は読み込まれる辞書を手動で管理する必要がありません。これにより、より良い最適化、より良い保守性、そして開発時間の短縮が可能になります。
163
+
164
+ アプリケーションが成長するにつれて(特に複数の開発者がアプリケーションに関わっている場合)、JSONファイルからもはや使用されていないコンテンツを削除し忘れることがよくあります。
165
+
166
+ > すべてのJSONはすべての場合に読み込まれることに注意してください(next-intl、next-i18next、intlayer)。
167
+
168
+ これがIntlayerのアプローチがよりパフォーマンスに優れている理由です。コンポーネントがもはや使用されていない場合、その辞書はバンドルに読み込まれません。
169
+
170
+ ライブラリがフォールバックをどのように処理するかも重要です。アプリケーションがデフォルトで英語であり、ユーザーが`/fr/about`ページを訪れたとします。フランス語の翻訳が欠けている場合、英語のフォールバックが考慮されます。
171
+
172
+ `next-intl` と `next-i18next` の場合、ライブラリは現在のロケールに関連する JSON に加えて、フォールバックロケールの JSON も読み込む必要があります。したがって、すべてのコンテンツが翻訳されていると仮定すると、各ページは 100% 不要なコンテンツを読み込むことになります。**これに対して、`intlayer` は辞書のビルド時にフォールバックを処理します。したがって、各ページは使用されるコンテンツのみを読み込みます。**
173
+
174
+ 以下は、vite + react アプリケーションで `intlayer` を使用したバンドルサイズ最適化の影響の例です:
175
+
176
+ | 最適化されたバンドル | 最適化されていないバンドル |
177
+ | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
178
+ | ![最適化されたバンドル](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png) | ![最適化されていないバンドル](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png) |
107
179
 
108
180
  ---
109
181
 
110
- ### 5) サーバーコンポーネント(RSC)との整合性
182
+ ## TypeScript と安全性
183
+
184
+ <Columns>
185
+ <Column>
111
186
 
112
- - **すべて**が Next.js 13+ をサポートしています。
113
- - **Intlayer** は一貫した API と RSC 向けに設計されたプロバイダーで、**サーバー/クライアントの境界** をスムーズにし、フォーマッターや t 関数をコンポーネントツリー間で渡す必要をなくします。
187
+ **next-intl**
114
188
 
115
- **重要な理由:** より明確なメンタルモデルと、ハイブリッドツリーにおけるエッジケースの減少を実現します。
189
+ - 安定した TypeScript サポートを提供しますが、**キーはデフォルトで厳密に型付けされていません**。安全性のパターンは手動で維持する必要があります。
190
+
191
+ </Column>
192
+ <Column>
193
+
194
+ **next-i18next**
195
+
196
+ - フックの基本的な型定義がありますが、**厳密なキーの型付けには追加のツールや設定が必要です**。
197
+
198
+ </Column>
199
+ <Column>
200
+
201
+ **intlayer**
202
+
203
+ - **コンテンツから厳密な型を生成**します。**IDEのオートコンプリート**や**コンパイル時エラー**により、デプロイ前にタイプミスやキーの欠落を検出します。
204
+
205
+ </Column>
206
+ </Columns>
207
+
208
+ **なぜ重要か:** 強い型付けにより、失敗を**右(実行時)**ではなく**左(CI/ビルド時)**にシフトさせます。
116
209
 
117
210
  ---
118
211
 
119
- ### 6) パフォーマンスと読み込み挙動
212
+ ## 翻訳欠落の取り扱い
213
+
214
+ **next-intl**
120
215
 
121
- - **next-intl / next-i18next**: **namespaces**や**ルートレベルの分割**による部分的な制御が可能ですが、管理が甘いと未使用の文字列がバンドルされるリスクがあります。
122
- - **Intlayer**: ビルド時に**ツリーシェイク**を行い、辞書やロケールごとに**遅延ロード**します。未使用のコンテンツは配信されません。
216
+ - **実行時のフォールバック**に依存(例:キーやデフォルトロケールを表示)。ビルドは失敗しません。
123
217
 
124
- **なぜ重要か:** 特に多言語サイトで、バンドルサイズが小さくなり、起動が速くなります。
218
+ **next-i18next**
219
+
220
+ - **実行時のフォールバック**に依存(例:キーやデフォルトロケールを表示)。ビルドは失敗しません。
221
+
222
+ **intlayer**
223
+
224
+ - **ビルド時検出**により、ロケールやキーの欠落に対して**警告/エラー**を出します。
225
+
226
+ **なぜ重要か:** ビルド時に欠落を検出することで、本番環境での「謎の文字列」発生を防ぎ、厳格なリリースゲートに適合します。
125
227
 
126
228
  ---
127
229
 
128
- ### 7) DX(開発者体験)、ツール、メンテナンス
230
+ ## ルーティング、ミドルウェア & URL戦略
231
+
232
+ <Columns>
233
+ <Column>
234
+
235
+ **next-intl**
236
+
237
+ - App Router上の**Next.jsのローカライズされたルーティング**に対応。
238
+
239
+ </Column>
240
+ <Column>
129
241
 
130
- - **next-intl / next-i18next**: 通常、翻訳や編集ワークフローのために外部プラットフォームと連携します。
131
- - **Intlayer**: **無料のビジュアルエディター**と**オプションのCMS**(Git対応または外部化)を提供します。さらに、コンテンツ作成用の**VSCode拡張機能**や、独自のプロバイダーキーを使った**AI支援翻訳**も備えています。
242
+ **next-i18next**
132
243
 
133
- **なぜ重要か:** 運用コストを削減し、開発者とコンテンツ作成者間のフィードバックループを短縮します。
244
+ - App Router上の**Next.jsのローカライズされたルーティング**に対応。
245
+
246
+ </Column>
247
+ <Column>
248
+
249
+ **intlayer**
250
+
251
+ - 上記すべてに加え、**i18nミドルウェア**(ヘッダーやクッキーによるロケール検出)およびローカライズされたURLや`<link rel="alternate" hreflang="…">`タグを生成する**ヘルパー**を提供。
252
+
253
+ </Column>
254
+ </Columns>
255
+
256
+ **重要な理由:** カスタムの接着層が減り、**一貫したUX**と**クリーンなSEO**をロケール間で実現。
134
257
 
135
258
  ---
136
259
 
137
- ## どのツールを選ぶべきか?
260
+ ## サーバーコンポーネント(RSC)対応
261
+
262
+ <Columns>
263
+ <Column>
264
+
265
+ **next-intl**
266
+
267
+ - Next.js 13+をサポート。ハイブリッド構成では、t関数やフォーマッターをコンポーネントツリーに渡すことが多い。
268
+
269
+ </Column>
270
+ <Column>
138
271
 
139
- - **next-intl** を選ぶのは、**最小限の**ソリューションを求めていて、集中管理されたカタログに慣れており、アプリが**小規模から中規模**の場合です。
140
- - **next-i18next** を選ぶのは、**i18nextのプラグインエコシステム**(例:プラグインによる高度なICUルール)が必要で、チームがすでにi18nextを知っており、柔軟性のために**より多くの設定**を受け入れられる場合です。
141
- - **Intlayer** を選ぶのは、**コンポーネント単位のコンテンツ管理**、**厳格なTypeScript**、**ビルド時の保証**、**ツリーシェイキング**、および**ルーティング/SEO/エディタツールが標準搭載**されていることを重視し、特に**Next.js App Router**や**大規模でモジュール化されたコードベース**に適している場合です。
272
+ **next-i18next**
273
+
274
+ - Next.js 13+ をサポート。翻訳ユーティリティを境界を越えて渡す際に類似の制約があります。
275
+
276
+ </Column>
277
+ <Column>
278
+
279
+ **intlayer**
280
+
281
+ - Next.js 13+ をサポートし、一貫した API と RSC 指向のプロバイダーで **サーバー/クライアントの境界** をスムーズにし、フォーマッターや t-関数のやり取りを回避します。
282
+
283
+ </Column>
284
+ </Columns>
285
+
286
+ **重要な理由:** ハイブリッドツリーにおけるメンタルモデルがより明確になり、エッジケースが減少します。
142
287
 
143
288
  ---
144
289
 
145
- ## 実践的な移行ノート(next-intl / next-i18next → Intlayer)
290
+ ## DX、ツール&メンテナンス
291
+
292
+ <Columns>
293
+ <Column>
294
+
295
+ **next-intl**
296
+
297
+ - 外部のローカリゼーションプラットフォームや編集ワークフローと組み合わせて使われることが多いです。
298
+
299
+ </Column>
300
+ <Column>
301
+
302
+ **next-i18next**
303
+
304
+ - 外部のローカリゼーションプラットフォームや編集ワークフローと組み合わせて使われることが多いです。
305
+
306
+ </Column>
307
+ <Column>
308
+
309
+ **intlayer**
310
+
311
+ - 無料の**ビジュアルエディター**と**オプションのCMS**(Git対応または外部化可能)を提供し、さらに**VSCode拡張機能**と、独自のプロバイダーキーを使用した**AI支援翻訳**も備えています。
312
+
313
+ </Column>
314
+ </Columns>
315
+
316
+ **重要な理由:** 運用コストを削減し、開発者とコンテンツ作成者間のフィードバックループを短縮します。
317
+
318
+ ## ローカリゼーションプラットフォーム(TMS)との統合
319
+
320
+ 大規模な組織では、**Crowdin**、**Phrase**、**Lokalise**、**Localizely**、**Localazy**などの翻訳管理システム(TMS)に依存することが多いです。
321
+
322
+ - **企業が重視する理由**
323
+ - **協力と役割分担**:複数の関係者が関与します。開発者、プロダクトマネージャー、翻訳者、レビュアー、マーケティングチームなど。
324
+ - **規模と効率性**:継続的なローカリゼーション、コンテキスト内レビュー。
325
+
326
+ - **next-intl / next-i18next**
327
+ - 通常は**集中管理されたJSONカタログ**を使用するため、TMSとのエクスポート/インポートが簡単です。
328
+ - 上記プラットフォーム向けの成熟したエコシステムや例/統合があります。
329
+
330
+ - **Intlayer**
331
+ - **分散型のコンポーネントごとの辞書**を推奨し、**TypeScript/TSX/JS/JSON/MD**コンテンツをサポートします。
332
+ - これによりコードのモジュール性が向上しますが、ツールが集中管理されたフラットなJSONファイルを期待する場合、プラグアンドプレイのTMS統合が難しくなることがあります。
333
+ - Intlayerは代替手段を提供します:**AI支援翻訳**(ご自身のプロバイダーキーを使用)、**ビジュアルエディター/CMS**、およびギャップを検出して事前入力するための**CLI/CI**ワークフロー。
334
+
335
+ > 注意: `next-intl` と `i18next` は TypeScript カタログも受け入れます。もしチームがメッセージを `.ts` ファイルに保存したり、機能ごとに分散管理している場合、同様の TMS の摩擦に直面することがあります。しかし、多くの `next-intl` のセットアップは `locales/` フォルダに集中しており、TMS 用に JSON にリファクタリングするのが少し容易です。
336
+
337
+ ## 開発者体験
338
+
339
+ この部分では、3つのソリューションを深く比較します。各ソリューションの「はじめに」ドキュメントに記載されている単純なケースを考慮するのではなく、より実際のプロジェクトに近い実用的なユースケースを考えます。
340
+
341
+ ### アプリ構造
342
+
343
+ アプリの構造は、コードベースの良好な保守性を確保するために重要です。
344
+
345
+ <Tab defaultTab="next-intl" group='techno'>
346
+
347
+ <TabItem label="next-i18next" value="next-i18next">
348
+
349
+ ```bash
350
+ .
351
+ ├── public
352
+ │ └── locales
353
+ │ ├── en
354
+ │ │ ├── home.json
355
+ │ │ └── navbar.json
356
+ │ ├── fr
357
+ │ │ ├── home.json
358
+ │ │ └── navbar.json
359
+ │ └── es
360
+ │ ├── home.json
361
+ │ └── navbar.json
362
+ ├── next-i18next.config.js
363
+ └── src
364
+ ├── middleware.ts
365
+ ├── app
366
+ │ └── home.tsx
367
+ └── components
368
+ └── Navbar
369
+ └── index.tsx
370
+ ```
371
+
372
+ </TabItem>
373
+ <TabItem label="next-intl" value="next-intl">
374
+
375
+ ```bash
376
+ .
377
+ ├── locales
378
+ │ ├── en
379
+ │ │ ├── home.json
380
+ │ │ └── navbar.json
381
+ │ ├── fr
382
+ │ │ ├── home.json
383
+ │ │ └── navbar.json
384
+ │ └── es
385
+ │ ├── home.json
386
+ │ └── navbar.json
387
+ ├── i18n.ts
388
+ └── src
389
+ ├── middleware.ts
390
+ ├── app
391
+ │ └── home.tsx
392
+ └── components
393
+ └── Navbar
394
+ └── index.tsx
395
+ ```
396
+
397
+ </TabItem>
398
+ <TabItem label="intlayer" value="intlayer">
399
+
400
+ ```bash
401
+ .
402
+ ├── intlayer.config.ts
403
+ └── src
404
+ ├── middleware.ts
405
+ ├── app
406
+ │ └── home
407
+ │ └── index.tsx
408
+ │ └── index.content.ts
409
+ └── components
410
+ └── Navbar
411
+ ├── index.tsx
412
+ └── index.content.ts
413
+ ```
414
+
415
+ </TabItem>
416
+ </Tab>
417
+
418
+ #### 比較
419
+
420
+ - **next-intl / next-i18next**: 集中管理されたカタログ(JSON; 名前空間/メッセージ)。構造が明確で翻訳プラットフォームとよく統合されるが、アプリが大きくなるとファイル間の編集が増える可能性がある。
421
+ - **Intlayer**: コンポーネントごとに `.content.{ts|js|json}` 辞書がコンポーネントと同じ場所に配置されている。コンポーネントの再利用や局所的な理解が容易になるが、ファイルが増え、ビルド時のツールに依存する。
422
+
423
+ #### セットアップとコンテンツの読み込み
424
+
425
+ 前述のように、各JSONファイルのインポート方法を最適化する必要があります。
426
+ ライブラリがコンテンツの読み込みをどのように処理するかが重要です。
427
+
428
+ <Tab defaultTab="next-intl" group='techno'>
429
+ <TabItem label="next-i18next" value="next-i18next">
430
+
431
+ ```tsx fileName="next-i18next.config.js"
432
+ module.exports = {
433
+ i18n: {
434
+ locales: ["en", "fr", "es"],
435
+ defaultLocale: "en",
436
+ },
437
+ };
438
+ ```
439
+
440
+ ```tsx fileName="src/app/_app.tsx"
441
+ import { appWithTranslation } from "next-i18next";
442
+
443
+ const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;
444
+
445
+ export default appWithTranslation(MyApp);
446
+ ```
447
+
448
+ ```tsx fileName="src/app/[locale]/about/page.tsx"
449
+ import type { GetStaticProps } from "next";
450
+ import { serverSideTranslations } from "next-i18next/serverSideTranslations";
451
+ import { useTranslation } from "next-i18next";
452
+ import { I18nextProvider, initReactI18next } from "react-i18next";
453
+ import { createInstance } from "i18next";
454
+ import { ClientComponent, ServerComponent } from "@components";
455
+
456
+ export default function HomePage({ locale }: { locale: string }) {
457
+ // このコンポーネントで使用する名前空間を明示的に宣言します
458
+ const resources = await loadMessagesFor(locale); // あなたのローダー(JSONなど)
459
+
460
+ const i18n = createInstance();
461
+ i18n.use(initReactI18next).init({
462
+ lng: locale,
463
+ fallbackLng: "en",
464
+ resources,
465
+ ns: ["common", "about"],
466
+ defaultNS: "common",
467
+ interpolation: { escapeValue: false },
468
+ });
469
+
470
+ const { t } = useTranslation("about");
471
+
472
+ return (
473
+ <I18nextProvider i18n={i18n}>
474
+ <main>
475
+ <h1>{t("title")}</h1>
476
+ <ClientComponent />
477
+ <ServerComponent />
478
+ </main>
479
+ </I18nextProvider>
480
+ );
481
+ }
482
+
483
+ export const getStaticProps: GetStaticProps = async ({ locale }) => {
484
+ // このページに必要な名前空間のみをプリロードします
485
+ return {
486
+ props: {
487
+ ...(await serverSideTranslations(locale ?? "en", ["common", "about"])),
488
+ },
489
+ };
490
+ };
491
+ ```
492
+
493
+ </TabItem>
494
+ <TabItem label="next-intl" value="next-intl">
495
+
496
+ ```tsx fileName="i18n.ts"
497
+ import { getRequestConfig } from "next-intl/server";
498
+ import { notFound } from "next/navigation";
499
+
500
+ // 共有設定からインポート可能
501
+ const locales = ["en", "fr", "es"];
502
+
503
+ export default getRequestConfig(async ({ locale }) => {
504
+ // 受け取った `locale` パラメータが有効か検証します
505
+ if (!locales.includes(locale as any)) notFound();
506
+
507
+ return {
508
+ messages: (await import(`../messages/${locale}.json`)).default,
509
+ };
510
+ });
511
+ ```
512
+
513
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
514
+ import { NextIntlClientProvider } from "next-intl";
515
+ import { getMessages, unstable_setRequestLocale } from "next-intl/server";
516
+ import pick from "lodash/pick";
517
+
518
+ export default async function LocaleLayout({
519
+ children,
520
+ params,
521
+ }: {
522
+ children: React.ReactNode;
523
+ params: { locale: string };
524
+ }) {
525
+ const { locale } = params;
526
+
527
+ // このサーバーレンダリング(RSC)用にアクティブなリクエストロケールを設定します
528
+ unstable_setRequestLocale(locale);
529
+
530
+ // メッセージは src/i18n/request.ts 経由でサーバー側で読み込まれます
531
+ // (next-intl のドキュメント参照)。ここではクライアントコンポーネントに必要な
532
+ // サブセットのみをクライアントに渡します(ペイロード最適化)。
533
+ const messages = await getMessages();
534
+ const clientMessages = pick(messages, ["common", "about"]);
535
+
536
+ return (
537
+ <html lang={locale}>
538
+ <body>
539
+ <NextIntlClientProvider locale={locale} messages={clientMessages}>
540
+ {children}
541
+ </NextIntlClientProvider>
542
+ </body>
543
+ </html>
544
+ );
545
+ }
546
+ ```
547
+
548
+ ```tsx fileName="src/app/[locale]/about/page.tsx"
549
+ import { getTranslations } from "next-intl/server";
550
+ import { ClientComponent, ServerComponent } from "@components";
551
+
552
+ export default async function LandingPage({
553
+ params,
554
+ }: {
555
+ params: { locale: string };
556
+ }) {
557
+ // 完全にサーバー側での読み込み(クライアント側でのハイドレーションなし)
558
+ const t = await getTranslations("about");
559
+
560
+ return (
561
+ <main>
562
+ <h1>{t("title")}</h1>
563
+ <ClientComponent />
564
+ <ServerComponent />
565
+ </main>
566
+ );
567
+ }
568
+ ```
569
+
570
+ </TabItem>
571
+ <TabItem label="intlayer" value="intlayer">
572
+
573
+ ```tsx fileName="intlayer.config.ts"
574
+ export default {
575
+ internationalization: {
576
+ locales: ["en", "fr", "es"],
577
+ defaultLocale: "en",
578
+ },
579
+ };
580
+ ```
581
+
582
+ ```tsx fileName="src/app/[locale]/layout.tsx"
583
+ import { getHTMLTextDir } from "intlayer";
584
+ import {
585
+ IntlayerClientProvider,
586
+ generateStaticParams,
587
+ type NextLayoutIntlayer,
588
+ } from "next-intlayer";
589
+
590
+ export const dynamic = "force-static";
591
+
592
+ const LandingLayout: NextLayoutIntlayer = async ({ children, params }) => {
593
+ const { locale } = await params;
594
+
595
+ return (
596
+ <html lang={locale} dir={getHTMLTextDir(locale)}>
597
+ <IntlayerClientProvider locale={locale}>
598
+ {children}
599
+ </IntlayerClientProvider>
600
+ </html>
601
+ );
602
+ };
603
+
604
+ export default LandingLayout;
605
+ ```
606
+
607
+ ```tsx fileName="src/app/[locale]/about/page.tsx"
608
+ import { PageContent } from "@components/PageContent";
609
+ import type { NextPageIntlayer } from "next-intlayer";
610
+ import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
611
+ import { ClientComponent, ServerComponent } from "@components";
612
+
613
+ const LandingPage: NextPageIntlayer = async ({ params }) => {
614
+ const { locale } = await params;
615
+ const { title } = useIntlayer("about", locale);
616
+
617
+ return (
618
+ <IntlayerServerProvider locale={locale}>
619
+ <main>
620
+ <h1>{title}</h1>
621
+ <ClientComponent />
622
+ <ServerComponent />
623
+ </main>
624
+ </IntlayerServerProvider>
625
+ );
626
+ };
627
+
628
+ export default LandingPage;
629
+ ```
630
+
631
+ </TabItem>
632
+ </Tab>
633
+
634
+ #### 比較
635
+
636
+ 3つすべてがロケールごとのコンテンツ読み込みとプロバイダーをサポートしています。
637
+
638
+ - **next-intl/next-i18next** では、通常、ルートごとに選択されたメッセージや名前空間を読み込み、必要な場所にプロバイダーを配置します。
639
+
640
+ - **Intlayer** では、ビルド時の解析を追加して使用状況を推測し、手動の配線を減らし、単一のルートプロバイダーを許可する場合があります。
641
+
642
+ チームの好みに応じて、明示的な制御と自動化のどちらかを選択してください。
643
+
644
+ ### クライアントコンポーネントでの使用例
645
+
646
+ カウンターをレンダリングするクライアントコンポーネントの例を見てみましょう。
647
+
648
+ <Tab defaultTab="next-intl" group='techno'>
649
+ <TabItem label="next-i18next" value="next-i18next">
650
+
651
+ **翻訳(`public/locales/...` に実際のJSONファイルが必要です)**
652
+
653
+ ```json fileName="public/locales/en/about.json"
654
+ {
655
+ "counter": {
656
+ "label": "Counter",
657
+ "increment": "Increment"
658
+ }
659
+ }
660
+ ```
661
+
662
+ ```json fileName="public/locales/fr/about.json"
663
+ {
664
+ "counter": {
665
+ "label": "Compteur",
666
+ "increment": "Incrémenter"
667
+ }
668
+ }
669
+ ```
670
+
671
+ **クライアントコンポーネント**
672
+
673
+ ```tsx fileName="src/components/ClientComponentExample.tsx"
674
+ "use client";
675
+
676
+ import React, { useMemo, useState } from "react";
677
+ import { useTranslation } from "next-i18next";
678
+
679
+ const ClientComponentExample = () => {
680
+ const { t, i18n } = useTranslation("about");
681
+ const [count, setCount] = useState(0);
682
+
683
+ // next-i18nextはuseNumberを公開していないため、Intl.NumberFormatを使用
684
+ const numberFormat = new Intl.NumberFormat(i18n.language);
685
+
686
+ return (
687
+ <div>
688
+ <p>{numberFormat.format(count)}</p>
689
+ <button
690
+ aria-label={t("counter.label")}
691
+ onClick={() => setCount((count) => count + 1)}
692
+ >
693
+ {t("counter.increment")}
694
+ </button>
695
+ </div>
696
+ );
697
+ };
698
+ ```
699
+
700
+ > ページの serverSideTranslations に "about" ネームスペースを追加するのを忘れないでください
701
+ > ここでは React 19.x.x のバージョンを使用していますが、より低いバージョンの場合は、重い関数であるためフォーマッターのインスタンスを保持するために useMemo を使用する必要があります
702
+
703
+ </TabItem>
704
+ <TabItem label="next-intl" value="next-intl">
705
+
706
+ **翻訳(形状は再利用可能;お好みで next-intl のメッセージにロードしてください)**
707
+
708
+ ```json fileName="locales/en/about.json"
709
+ {
710
+ "counter": {
711
+ "label": "Counter",
712
+ "increment": "Increment"
713
+ }
714
+ }
715
+ ```
716
+
717
+ ```json fileName="locales/fr/about.json"
718
+ {
719
+ "counter": {
720
+ "label": "Compteur",
721
+ "increment": "Incrémenter"
722
+ }
723
+ }
724
+ ```
725
+
726
+ **クライアントコンポーネント**
727
+
728
+ ```tsx fileName="src/components/ClientComponentExample.tsx"
729
+ "use client";
730
+
731
+ import React, { useState } from "react";
732
+ import { useTranslations, useFormatter } from "next-intl";
733
+
734
+ const ClientComponentExample = () => {
735
+ // ネストされたオブジェクトに直接スコープを設定
736
+ const t = useTranslations("about.counter");
737
+ const format = useFormatter();
738
+ const [count, setCount] = useState(0);
739
+
740
+ return (
741
+ <div>
742
+ <p>{format.number(count)}</p>
743
+ <button
744
+ aria-label={t("label")}
745
+ onClick={() => setCount((count) => count + 1)}
746
+ >
747
+ {t("increment")}
748
+ </button>
749
+ </div>
750
+ );
751
+ };
752
+ ```
753
+
754
+ > ページのクライアントメッセージに "about" メッセージを追加するのを忘れないでください
755
+
756
+ </TabItem>
757
+ <TabItem label="intlayer" value="intlayer">
758
+
759
+ **コンテンツ**
760
+
761
+ ```ts fileName="src/components/ClientComponentExample/index.content.ts"
762
+ import { t, type Dictionary } from "intlayer";
763
+
764
+ const counterContent = {
765
+ key: "counter",
766
+ content: {
767
+ label: t({ ja: "カウンター", en: "Counter", fr: "Compteur" }),
768
+ increment: t({ ja: "インクリメント", en: "Increment", fr: "Incrémenter" }),
769
+ },
770
+ } satisfies Dictionary;
771
+
772
+ export default counterContent;
773
+ ```
774
+
775
+ **クライアントコンポーネント**
776
+
777
+ ```tsx fileName="src/components/ClientComponentExample/index.tsx"
778
+ "use client";
779
+
780
+ import React, { useState } from "react";
781
+ import { useNumber, useIntlayer } from "next-intlayer";
782
+
783
+ const ClientComponentExample = () => {
784
+ const [count, setCount] = useState(0);
785
+ const { label, increment } = useIntlayer("counter"); // 文字列を返す
786
+ const { number } = useNumber();
787
+
788
+ return (
789
+ <div>
790
+ <p>{number(count)}</p>
791
+ <button aria-label={label} onClick={() => setCount((count) => count + 1)}>
792
+ {increment}
793
+ </button>
794
+ </div>
795
+ );
796
+ };
797
+ ```
798
+
799
+ </TabItem>
800
+ </Tab>
801
+
802
+ #### 比較
803
+
804
+ - **数値フォーマット**
805
+ - **next-i18next**: `useNumber` はなく、`Intl.NumberFormat`(または i18next-icu)を使用。
806
+ - **next-intl**: `useFormatter().number(value)` を使用。
807
+ - **Intlayer**: 組み込みの `useNumber()` を使用。
808
+
809
+ - **キー**
810
+ - ネストされた構造(`about.counter.label`)を維持し、フックのスコープを適切に設定する(`useTranslation("about")` + `t("counter.label")` または `useTranslations("about.counter")` + `t("label")`)。
811
+
812
+ - **ファイルの場所**
813
+ - **next-i18next** は `public/locales/{lng}/{ns}.json` に JSON を期待。
814
+ - **next-intl** は柔軟で、設定に応じてメッセージをロード可能。
815
+ - **Intlayer** は TS/JS の辞書にコンテンツを格納し、キーで解決。
816
+
817
+ ---
818
+
819
+ ### サーバーコンポーネントでの使用
820
+
821
+ UIコンポーネントの場合を考えます。このコンポーネントはサーバーコンポーネントであり、クライアントコンポーネントの子として挿入できる必要があります。(ページ(サーバーコンポーネント) -> クライアントコンポーネント -> サーバーコンポーネント)。このコンポーネントはクライアントコンポーネントの子として挿入できるため、非同期にはできません。
822
+
823
+ <Tab defaultTab="next-intl" group='techno'>
824
+ <TabItem label="next-i18next" value="next-i18next">
825
+
826
+ ```tsx fileName="src/pages/about.tsx"
827
+ import type { GetStaticProps } from "next";
828
+ import { useTranslation } from "next-i18next";
829
+
830
+ type ServerComponentProps = {
831
+ count: number;
832
+ };
833
+
834
+ const ServerComponent = ({ count }: ServerComponentProps) => {
835
+ const { t, i18n } = useTranslation("about");
836
+ const formatted = new Intl.NumberFormat(i18n.language).format(count);
837
+
838
+ return (
839
+ <div>
840
+ <p>{formatted}</p>
841
+ <button aria-label={t("counter.label")}>{t("counter.increment")}</button>
842
+ </div>
843
+ );
844
+ };
845
+ ```
846
+
847
+ </TabItem>
848
+ <TabItem label="next-intl" value="next-intl">
849
+
850
+ ```tsx fileName="src/components/ServerComponent.tsx"
851
+ type ServerComponentProps = {
852
+ count: number;
853
+ t: (key: string) => string;
854
+ };
855
+
856
+ const ServerComponent = ({ t, count }: ServerComponentProps) => {
857
+ const formatted = new Intl.NumberFormat(i18n.language).format(count);
858
+
859
+ return (
860
+ <div>
861
+ <p>{formatted}</p>
862
+ <button aria-label={t("label")}>{t("increment")}</button>
863
+ </div>
864
+ );
865
+ };
866
+ ```
867
+
868
+ > サーバーコンポーネントは非同期にできないため、翻訳関数とフォーマッター関数をプロパティとして渡す必要があります。
869
+ >
870
+ > - `const t = await getTranslations("about.counter");`
871
+ > - `const format = await getFormatter();`
872
+
873
+ </TabItem>
874
+ <TabItem label="intlayer" value="intlayer">
875
+
876
+ ```tsx fileName="src/components/ServerComponent.tsx"
877
+ import { useIntlayer, useNumber } from "next-intlayer/server";
878
+
879
+ const ServerComponent = ({ count }: { count: number }) => {
880
+ const { label, increment } = useIntlayer("counter");
881
+ const { number } = useNumber();
882
+
883
+ return (
884
+ <div>
885
+ <p>{number(count)}</p>
886
+ <button aria-label={label}>{increment}</button>
887
+ </div>
888
+ );
889
+ };
890
+ ```
891
+
892
+ </TabItem>
893
+ </Tab>
894
+
895
+ > Intlayerは`next-intlayer/server`を通じて**サーバーセーフ**なフックを提供します。動作するために、`useIntlayer`と`useNumber`はクライアントフックに似たフックのような構文を使用しますが、内部的にはサーバーコンテキスト(`IntlayerServerProvider`)に依存しています。
896
+
897
+ ### メタデータ / サイトマップ / ロボット
898
+
899
+ コンテンツの翻訳は素晴らしいことです。しかし、多くの人は国際化の主な目的があなたのウェブサイトを世界により見えるようにすることだということを忘れがちです。I18nはあなたのウェブサイトの可視性を向上させるための非常に強力な手段です。
900
+
901
+ 以下は多言語SEOに関するベストプラクティスのリストです。
902
+
903
+ - `<head>`タグ内にhreflangメタタグを設定する
904
+ > これは検索エンジンがページで利用可能な言語を理解するのに役立ちます
905
+ - sitemap.xml にすべてのページの翻訳を `http://www.w3.org/1999/xhtml` XML スキーマを使ってリストアップする
906
+ >
907
+ - robots.txt からプレフィックス付きページを除外するのを忘れない(例:`/dashboard`、および `/fr/dashboard`、`/es/dashboard`)
908
+ >
909
+ - カスタム Link コンポーネントを使って最もローカライズされたページへリダイレクトする(例:フランス語では `<a href="/fr/about">A propos</a>`)
910
+ >
911
+
912
+ 開発者はしばしばロケール間でページを適切に参照することを忘れがちです。
913
+
914
+ <Tab defaultTab="next-intl" group='techno'>
915
+
916
+ <TabItem label="next-i18next" value="next-i18next">
917
+
918
+ ```ts fileName="i18n.config.ts"
919
+ export const locales = ["en", "fr"] as const;
920
+ export type Locale = (typeof locales)[number];
921
+ export const defaultLocale: Locale = "en";
922
+
923
+ export function localizedPath(locale: string, path: string) {
924
+ return locale === defaultLocale ? path : "/" + locale + path;
925
+ }
926
+
927
+ const ORIGIN = "https://example.com";
928
+ export function abs(locale: string, path: string) {
929
+ return ORIGIN + localizedPath(locale, path);
930
+ }
931
+ ```
932
+
933
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
934
+ import type { Metadata } from "next";
935
+ import { locales, defaultLocale, localizedPath } from "@/i18n.config";
936
+
937
+ export async function generateMetadata({
938
+ params,
939
+ }: {
940
+ params: { locale: string };
941
+ }): Promise<Metadata> {
942
+ const { locale } = params;
943
+
944
+ // 正しいJSONファイルを動的にインポートする
945
+ const messages = (
946
+ await import("@/../public/locales/" + locale + "/about.json")
947
+ ).default;
948
+
949
+ const languages = Object.fromEntries(
950
+ locales.map((locale) => [locale, localizedPath(locale, "/about")])
951
+ );
952
+
953
+ return {
954
+ title: messages.title,
955
+ description: messages.description,
956
+ alternates: {
957
+ canonical: localizedPath(locale, "/about"),
958
+ languages: { ...languages, "x-default": "/about" },
959
+ },
960
+ };
961
+ }
962
+
963
+ export default async function AboutPage() {
964
+ return <h1>概要</h1>; // 「About」の日本語訳
965
+ }
966
+ ```
967
+
968
+ ```ts fileName="src/app/sitemap.ts"
969
+ import type { MetadataRoute } from "next";
970
+ import { locales, defaultLocale, abs } from "@/i18n.config";
971
+
972
+ export default function sitemap(): MetadataRoute.Sitemap {
973
+ const languages = Object.fromEntries(
974
+ locales.map((locale) => [locale, abs(locale, "/about")])
975
+ );
976
+ return [
977
+ {
978
+ url: abs(defaultLocale, "/about"),
979
+ lastModified: new Date(),
980
+ changeFrequency: "monthly", // 更新頻度:毎月
981
+ priority: 0.7, // 優先度
982
+ alternates: { languages },
983
+ },
984
+ ];
985
+ }
986
+ ```
987
+
988
+ ```ts fileName="src/app/robots.ts"
989
+ import type { MetadataRoute } from "next";
990
+ import { locales, defaultLocale, localizedPath } from "@/i18n.config";
991
+
992
+ const ORIGIN = "https://example.com";
993
+
994
+ const expandAllLocales = (path: string) => [
995
+ localizedPath(defaultLocale, path),
996
+ ...locales
997
+ .filter((locale) => locale !== defaultLocale)
998
+ .map((locale) => localizedPath(locale, path)),
999
+ ];
1000
+
1001
+ export default function robots(): MetadataRoute.Robots {
1002
+ const disallow = [
1003
+ ...expandAllLocales("/dashboard"),
1004
+ ...expandAllLocales("/admin"),
1005
+ ];
1006
+
1007
+ return {
1008
+ rules: { userAgent: "*", allow: ["/"], disallow },
1009
+ host: ORIGIN,
1010
+ sitemap: ORIGIN + "/sitemap.xml",
1011
+ };
1012
+ }
1013
+ ```
1014
+
1015
+ </TabItem>
1016
+ <TabItem label="next-intl" value="next-intl">
1017
+
1018
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
1019
+ import type { Metadata } from "next";
1020
+ import { locales, defaultLocale } from "@/i18n";
1021
+ import { getTranslations } from "next-intl/server";
1022
+
1023
+ function localizedPath(locale: string, path: string) {
1024
+ return locale === defaultLocale ? path : "/" + locale + path;
1025
+ }
1026
+
1027
+ export async function generateMetadata({
1028
+ params,
1029
+ }: {
1030
+ params: { locale: string };
1031
+ }): Promise<Metadata> {
1032
+ const { locale } = params;
1033
+ const t = await getTranslations({ locale, namespace: "about" });
1034
+
1035
+ const url = "/about";
1036
+ const languages = Object.fromEntries(
1037
+ locales.map((locale) => [locale, localizedPath(locale, url)])
1038
+ );
1039
+
1040
+ return {
1041
+ title: t("title"),
1042
+ description: t("description"),
1043
+ alternates: {
1044
+ canonical: localizedPath(locale, url),
1045
+ languages: { ...languages, "x-default": url },
1046
+ },
1047
+ };
1048
+ }
1049
+
1050
+ // ... ページの残りのコード
1051
+ ```
1052
+
1053
+ ```tsx fileName="src/app/sitemap.ts"
1054
+ import type { MetadataRoute } from "next";
1055
+ import { locales, defaultLocale } from "@/i18n";
1056
+
1057
+ const origin = "https://example.com";
1058
+
1059
+ const formatterLocalizedPath = (locale: string, path: string) =>
1060
+ locale === defaultLocale ? origin + path : origin + "/" + locale + path;
1061
+
1062
+ export default function sitemap(): MetadataRoute.Sitemap {
1063
+ const aboutLanguages = Object.fromEntries(
1064
+ locales.map((l) => [l, formatterLocalizedPath(l, "/about")])
1065
+ );
1066
+
1067
+ return [
1068
+ {
1069
+ url: formatterLocalizedPath(defaultLocale, "/about"),
1070
+ lastModified: new Date(),
1071
+ changeFrequency: "monthly",
1072
+ priority: 0.7,
1073
+ alternates: { languages: aboutLanguages },
1074
+ },
1075
+ ];
1076
+ }
1077
+ ```
1078
+
1079
+ ```tsx fileName="src/app/robots.ts"
1080
+ import type { MetadataRoute } from "next";
1081
+ import { locales, defaultLocale } from "@/i18n";
1082
+
1083
+ const origin = "https://example.com";
1084
+ const withAllLocales = (path: string) => [
1085
+ path,
1086
+ ...locales
1087
+ .filter((locale) => locale !== defaultLocale)
1088
+ .map((locale) => "/" + locale + path),
1089
+ ];
1090
+
1091
+ export default function robots(): MetadataRoute.Robots {
1092
+ const disallow = [
1093
+ ...withAllLocales("/dashboard"),
1094
+ ...withAllLocales("/admin"),
1095
+ ];
1096
+
1097
+ return {
1098
+ rules: { userAgent: "*", allow: ["/"], disallow },
1099
+ host: origin,
1100
+ sitemap: origin + "/sitemap.xml",
1101
+ };
1102
+ }
1103
+ ```
1104
+
1105
+ </TabItem>
1106
+ <TabItem label="intlayer" value="intlayer">
1107
+
1108
+ ```typescript fileName="src/app/[locale]/about/layout.tsx"
1109
+ import { getIntlayer, getMultilingualUrls } from "intlayer";
1110
+ import type { Metadata } from "next";
1111
+ import type { LocalPromiseParams } from "next-intlayer";
1112
+
1113
+ export const generateMetadata = async ({
1114
+ params,
1115
+ }: LocalPromiseParams): Promise<Metadata> => {
1116
+ const { locale } = await params;
1117
+
1118
+ const metadata = getIntlayer("page-metadata", locale);
1119
+
1120
+ const multilingualUrls = getMultilingualUrls("/about");
1121
+
1122
+ return {
1123
+ ...metadata,
1124
+ alternates: {
1125
+ canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
1126
+ languages: { ...multilingualUrls, "x-default": "/about" },
1127
+ },
1128
+ };
1129
+ };
1130
+
1131
+ // ... ページの残りのコード
1132
+ ```
1133
+
1134
+ ```tsx fileName="src/app/sitemap.ts"
1135
+ import { getMultilingualUrls } from "intlayer";
1136
+ import type { MetadataRoute } from "next";
1137
+
1138
+ const sitemap = (): MetadataRoute.Sitemap => [
1139
+ {
1140
+ url: "https://example.com/about",
1141
+ alternates: {
1142
+ languages: { ...getMultilingualUrls("https://example.com/about") },
1143
+ },
1144
+ },
1145
+ ];
1146
+ ```
1147
+
1148
+ ```tsx fileName="src/app/robots.ts"
1149
+ import { getMultilingualUrls } from "intlayer";
1150
+ import type { MetadataRoute } from "next";
1151
+
1152
+ const getAllMultilingualUrls = (urls: string[]) =>
1153
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
1154
+
1155
+ const robots = (): MetadataRoute.Robots => ({
1156
+ rules: {
1157
+ userAgent: "*",
1158
+ allow: ["/"],
1159
+ disallow: getAllMultilingualUrls(["/dashboard"]),
1160
+ },
1161
+ host: "https://example.com",
1162
+ sitemap: "https://example.com/sitemap.xml",
1163
+ });
1164
+
1165
+ export default robots;
1166
+ ```
1167
+
1168
+ </TabItem>
1169
+ </Tab>
1170
+
1171
+ > Intlayerは、サイトマップ用の多言語URLを生成するための`getMultilingualUrls`関数を提供しています。
1172
+
1173
+ ---
1174
+
1175
+ ---
1176
+
1177
+ ## そして勝者は…
1178
+
1179
+ 簡単ではありません。各オプションにはトレードオフがあります。私の見解は以下の通りです:
1180
+
1181
+ <Columns>
1182
+ <Column>
1183
+
1184
+ **next-intl**
1185
+
1186
+ - 最もシンプルで軽量、強制される決定が少ないです。**最小限**のソリューションを求めていて、集中管理されたカタログに慣れており、アプリが**小規模から中規模**の場合に適しています。
1187
+
1188
+ </Column>
1189
+ <Column>
1190
+
1191
+ **next-i18next**
1192
+
1193
+ - 成熟しており、機能が豊富でコミュニティプラグインも多いですが、セットアップコストは高めです。**i18nextのプラグインエコシステム**(例:プラグイン経由の高度なICUルール)が必要で、チームがすでにi18nextを知っていて、柔軟性のために**より多くの設定**を受け入れられる場合に適しています。
1194
+
1195
+ </Column>
1196
+ <Column>
1197
+
1198
+ **Intlayer**
1199
+
1200
+ - モダンな Next.js 向けに構築されており、モジュラーコンテンツ、型安全性、ツール群、そしてボイラープレートの削減を実現しています。**コンポーネント単位のコンテンツ管理**、**厳格な TypeScript**、**ビルド時の保証**、**ツリーシェイキング**、そして**ルーティング/SEO/エディターツールがバッテリー込み**で提供されることを重視する場合、特に **Next.js App Router**、デザインシステム、そして**大規模でモジュラーなコードベース**に最適です。
1201
+
1202
+ </Column>
1203
+ </Columns>
1204
+
1205
+ セットアップを最小限に抑え、多少の手動設定を許容できるなら next-intl が良い選択です。すべての機能が必要で複雑さを気にしないなら next-i18next が適しています。しかし、モダンでスケーラブル、モジュラーなソリューションをビルトインツールと共に求めるなら、Intlayer はそれをすぐに提供することを目指しています。
1206
+
1207
+ > **エンタープライズチーム向けの代替案**: **Crowdin**、**Phrase**、またはその他のプロフェッショナルな翻訳管理システムのような確立されたローカリゼーションプラットフォームと完全に連携する、実績のあるソリューションが必要な場合は、成熟したエコシステムと実証済みの統合を持つ **next-intl** または **next-i18next** を検討してください。
1208
+
1209
+ > **今後のロードマップ**: Intlayer は、**i18next** および **next-intl** ソリューションの上に動作するプラグインの開発も計画しています。これにより、Intlayer の自動化、構文、およびコンテンツ管理の利点を享受しつつ、これらの確立されたソリューションがアプリケーションコードに提供するセキュリティと安定性を維持できます。
1210
+
1211
+ ## GitHub STARs
1212
+
1213
+ GitHubのスターは、プロジェクトの人気、コミュニティの信頼、そして長期的な関連性を示す強力な指標です。技術的な品質の直接的な尺度ではありませんが、どれだけ多くの開発者がそのプロジェクトを有用と感じ、進捗を追い、採用する可能性が高いかを反映しています。プロジェクトの価値を評価する際、スターは代替案間のトラクションを比較し、エコシステムの成長に関する洞察を提供するのに役立ちます。
146
1214
 
147
- - **機能ごとに開始**:ルートやコンポーネントを一度に一つずつ**ローカル辞書**に移行します。
148
- - **旧カタログを並行して維持**:移行中の橋渡しとして使用し、一斉移行は避けます。
149
- - **厳密なチェックを有効化**:ビルド時の検出で早期にギャップを明らかにします。
150
- - **ミドルウェアとヘルパーを採用**:サイト全体でロケール検出とSEOタグを標準化します。
151
- - **バンドルサイズを測定**:未使用のコンテンツが削除されるため、**バンドルサイズの削減**が期待できます。
1215
+ [![スター履歴チャート](https://api.star-history.com/svg?repos=i18next/next-i18next&repos=amannn/next-intl&repos=aymericzip/intlayer&type=Date)](https://www.star-history.com/#i18next/next-i18next&amannn/next-intl&aymericzip/intlayer)
152
1216
 
153
1217
  ---
154
1218
 
155
1219
  ## 結論
156
1220
 
157
- 3つのライブラリはすべてコアなローカリゼーションに成功しています。違いは、**モダンな Next.js で堅牢でスケーラブルなセットアップを実現するためにどれだけの作業が必要か**です:
1221
+ 3つのライブラリはすべてコアなローカリゼーションに成功しています。違いは、**モダンな Next.js** で堅牢でスケーラブルなセットアップを実現するために、**どれだけの作業が必要か**という点です。
158
1222
 
159
- - **Intlayer**では、**モジュラーコンテンツ**、**厳格なTS**、**ビルド時の安全性**、**ツリーシェイクされたバンドル**、そして**一流のApp Router + SEOツール**が**デフォルト**であり、手間ではありません。
160
- - チームが多言語対応のコンポーネント駆動型アプリにおいて**保守性と速度**を重視するなら、Intlayerは今日最も**完全な**体験を提供します。
1223
+ - **Intlayer** では、**モジュラーコンテンツ**、**厳格なTS**、**ビルド時の安全性**、**ツリーシェイクされたバンドル**、および **一流のApp Router + SEOツール** が **デフォルト** であり、手間ではありません。
1224
+ - チームが多言語対応のコンポーネント駆動型アプリにおいて、**保守性と速度**を重視するなら、Intlayerは今日最も**完全な**体験を提供します。
161
1225
 
162
- 詳細は[『なぜIntlayerか?』ドキュメント](https://intlayer.org/doc/why)を参照してください。
1226
+ 詳細は ['Why Intlayer?' ドキュメント](https://intlayer.org/doc/why) を参照してください。