@intlayer/docs 7.5.12 → 7.5.14

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 (229) hide show
  1. package/blog/ar/per-component_vs_centralized_i18n.md +248 -0
  2. package/blog/de/per-component_vs_centralized_i18n.md +248 -0
  3. package/blog/en/_per-component_vs_centralized_i18n.md +252 -0
  4. package/blog/en/per-component_vs_centralized_i18n.md +248 -0
  5. package/blog/en-GB/per-component_vs_centralized_i18n.md +247 -0
  6. package/blog/es/per-component_vs_centralized_i18n.md +245 -0
  7. package/blog/fr/per-component_vs_centralized_i18n.md +245 -0
  8. package/blog/hi/per-component_vs_centralized_i18n.md +249 -0
  9. package/blog/id/per-component_vs_centralized_i18n.md +248 -0
  10. package/blog/it/per-component_vs_centralized_i18n.md +247 -0
  11. package/blog/ja/per-component_vs_centralized_i18n.md +247 -0
  12. package/blog/ko/per-component_vs_centralized_i18n.md +246 -0
  13. package/blog/pl/per-component_vs_centralized_i18n.md +247 -0
  14. package/blog/pt/per-component_vs_centralized_i18n.md +246 -0
  15. package/blog/ru/per-component_vs_centralized_i18n.md +251 -0
  16. package/blog/tr/per-component_vs_centralized_i18n.md +244 -0
  17. package/blog/uk/compiler_vs_declarative_i18n.md +224 -0
  18. package/blog/uk/i18n_using_next-i18next.md +1086 -0
  19. package/blog/uk/i18n_using_next-intl.md +760 -0
  20. package/blog/uk/index.md +69 -0
  21. package/blog/uk/internationalization_and_SEO.md +273 -0
  22. package/blog/uk/intlayer_with_i18next.md +211 -0
  23. package/blog/uk/intlayer_with_next-i18next.md +202 -0
  24. package/blog/uk/intlayer_with_next-intl.md +203 -0
  25. package/blog/uk/intlayer_with_react-i18next.md +200 -0
  26. package/blog/uk/intlayer_with_react-intl.md +202 -0
  27. package/blog/uk/intlayer_with_vue-i18n.md +206 -0
  28. package/blog/uk/l10n_platform_alternative/Lokalise.md +80 -0
  29. package/blog/uk/l10n_platform_alternative/crowdin.md +80 -0
  30. package/blog/uk/l10n_platform_alternative/phrase.md +78 -0
  31. package/blog/uk/list_i18n_technologies/CMS/drupal.md +143 -0
  32. package/blog/uk/list_i18n_technologies/CMS/wix.md +167 -0
  33. package/blog/uk/list_i18n_technologies/CMS/wordpress.md +189 -0
  34. package/blog/uk/list_i18n_technologies/frameworks/angular.md +125 -0
  35. package/blog/uk/list_i18n_technologies/frameworks/flutter.md +128 -0
  36. package/blog/uk/list_i18n_technologies/frameworks/react-native.md +217 -0
  37. package/blog/uk/list_i18n_technologies/frameworks/react.md +155 -0
  38. package/blog/uk/list_i18n_technologies/frameworks/svelte.md +145 -0
  39. package/blog/uk/list_i18n_technologies/frameworks/vue.md +144 -0
  40. package/blog/uk/next-i18next_vs_next-intl_vs_intlayer.md +1499 -0
  41. package/blog/uk/nextjs-multilingual-seo-comparison.md +360 -0
  42. package/blog/uk/per-component_vs_centralized_i18n.md +248 -0
  43. package/blog/uk/rag_powered_documentation_assistant.md +288 -0
  44. package/blog/uk/react-i18next_vs_react-intl_vs_intlayer.md +164 -0
  45. package/blog/uk/vue-i18n_vs_intlayer.md +279 -0
  46. package/blog/uk/what_is_internationalization.md +167 -0
  47. package/blog/vi/per-component_vs_centralized_i18n.md +246 -0
  48. package/blog/zh/per-component_vs_centralized_i18n.md +248 -0
  49. package/dist/cjs/common.cjs.map +1 -1
  50. package/dist/cjs/generated/blog.entry.cjs +20 -0
  51. package/dist/cjs/generated/blog.entry.cjs.map +1 -1
  52. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  53. package/dist/cjs/generated/frequentQuestions.entry.cjs +20 -0
  54. package/dist/cjs/generated/frequentQuestions.entry.cjs.map +1 -1
  55. package/dist/cjs/generated/legal.entry.cjs.map +1 -1
  56. package/dist/esm/common.mjs.map +1 -1
  57. package/dist/esm/generated/blog.entry.mjs +20 -0
  58. package/dist/esm/generated/blog.entry.mjs.map +1 -1
  59. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  60. package/dist/esm/generated/frequentQuestions.entry.mjs +20 -0
  61. package/dist/esm/generated/frequentQuestions.entry.mjs.map +1 -1
  62. package/dist/esm/generated/legal.entry.mjs.map +1 -1
  63. package/dist/types/generated/blog.entry.d.ts +1 -0
  64. package/dist/types/generated/blog.entry.d.ts.map +1 -1
  65. package/dist/types/generated/frequentQuestions.entry.d.ts +1 -0
  66. package/dist/types/generated/frequentQuestions.entry.d.ts.map +1 -1
  67. package/docs/ar/configuration.md +6 -1
  68. package/docs/ar/dictionary/content_file.md +6 -1
  69. package/docs/de/configuration.md +6 -1
  70. package/docs/de/dictionary/content_file.md +6 -1
  71. package/docs/en/configuration.md +6 -1
  72. package/docs/en/dictionary/content_file.md +6 -1
  73. package/docs/en-GB/configuration.md +6 -1
  74. package/docs/en-GB/dictionary/content_file.md +3 -1
  75. package/docs/es/configuration.md +6 -1
  76. package/docs/es/dictionary/content_file.md +6 -1
  77. package/docs/fr/configuration.md +6 -1
  78. package/docs/fr/dictionary/content_file.md +3 -1
  79. package/docs/hi/configuration.md +6 -1
  80. package/docs/hi/dictionary/content_file.md +3 -1
  81. package/docs/id/configuration.md +6 -1
  82. package/docs/id/dictionary/content_file.md +3 -1
  83. package/docs/it/configuration.md +6 -1
  84. package/docs/it/dictionary/content_file.md +3 -1
  85. package/docs/ja/configuration.md +6 -1
  86. package/docs/ja/dictionary/content_file.md +3 -1
  87. package/docs/ko/configuration.md +6 -1
  88. package/docs/ko/dictionary/content_file.md +3 -1
  89. package/docs/pl/configuration.md +3 -1
  90. package/docs/pl/dictionary/content_file.md +3 -1
  91. package/docs/pt/configuration.md +6 -1
  92. package/docs/pt/dictionary/content_file.md +3 -1
  93. package/docs/ru/configuration.md +6 -1
  94. package/docs/ru/dictionary/content_file.md +6 -1
  95. package/docs/tr/configuration.md +6 -1
  96. package/docs/tr/dictionary/content_file.md +3 -1
  97. package/docs/uk/CI_CD.md +198 -0
  98. package/docs/uk/autoFill.md +307 -0
  99. package/docs/uk/bundle_optimization.md +185 -0
  100. package/docs/uk/cli/build.md +64 -0
  101. package/docs/uk/cli/ci.md +137 -0
  102. package/docs/uk/cli/configuration.md +63 -0
  103. package/docs/uk/cli/debug.md +46 -0
  104. package/docs/uk/cli/doc-review.md +43 -0
  105. package/docs/uk/cli/doc-translate.md +132 -0
  106. package/docs/uk/cli/editor.md +28 -0
  107. package/docs/uk/cli/fill.md +130 -0
  108. package/docs/uk/cli/index.md +190 -0
  109. package/docs/uk/cli/init.md +84 -0
  110. package/docs/uk/cli/list.md +90 -0
  111. package/docs/uk/cli/list_projects.md +128 -0
  112. package/docs/uk/cli/live.md +41 -0
  113. package/docs/uk/cli/login.md +157 -0
  114. package/docs/uk/cli/pull.md +78 -0
  115. package/docs/uk/cli/push.md +98 -0
  116. package/docs/uk/cli/sdk.md +71 -0
  117. package/docs/uk/cli/test.md +76 -0
  118. package/docs/uk/cli/transform.md +65 -0
  119. package/docs/uk/cli/version.md +24 -0
  120. package/docs/uk/cli/watch.md +37 -0
  121. package/docs/uk/configuration.md +742 -0
  122. package/docs/uk/dictionary/condition.md +237 -0
  123. package/docs/uk/dictionary/content_file.md +1134 -0
  124. package/docs/uk/dictionary/enumeration.md +245 -0
  125. package/docs/uk/dictionary/file.md +232 -0
  126. package/docs/uk/dictionary/function_fetching.md +212 -0
  127. package/docs/uk/dictionary/gender.md +273 -0
  128. package/docs/uk/dictionary/insertion.md +187 -0
  129. package/docs/uk/dictionary/markdown.md +383 -0
  130. package/docs/uk/dictionary/nesting.md +273 -0
  131. package/docs/uk/dictionary/translation.md +332 -0
  132. package/docs/uk/formatters.md +595 -0
  133. package/docs/uk/how_works_intlayer.md +256 -0
  134. package/docs/uk/index.md +175 -0
  135. package/docs/uk/interest_of_intlayer.md +297 -0
  136. package/docs/uk/intlayer_CMS.md +569 -0
  137. package/docs/uk/intlayer_visual_editor.md +292 -0
  138. package/docs/uk/intlayer_with_angular.md +710 -0
  139. package/docs/uk/intlayer_with_astro.md +256 -0
  140. package/docs/uk/intlayer_with_create_react_app.md +1258 -0
  141. package/docs/uk/intlayer_with_express.md +429 -0
  142. package/docs/uk/intlayer_with_fastify.md +446 -0
  143. package/docs/uk/intlayer_with_lynx+react.md +548 -0
  144. package/docs/uk/intlayer_with_nestjs.md +283 -0
  145. package/docs/uk/intlayer_with_next-i18next.md +640 -0
  146. package/docs/uk/intlayer_with_next-intl.md +456 -0
  147. package/docs/uk/intlayer_with_nextjs_page_router.md +1541 -0
  148. package/docs/uk/intlayer_with_nuxt.md +711 -0
  149. package/docs/uk/intlayer_with_react_router_v7.md +600 -0
  150. package/docs/uk/intlayer_with_react_router_v7_fs_routes.md +669 -0
  151. package/docs/uk/intlayer_with_svelte_kit.md +579 -0
  152. package/docs/uk/intlayer_with_tanstack.md +818 -0
  153. package/docs/uk/intlayer_with_vite+preact.md +1748 -0
  154. package/docs/uk/intlayer_with_vite+react.md +1449 -0
  155. package/docs/uk/intlayer_with_vite+solid.md +302 -0
  156. package/docs/uk/intlayer_with_vite+svelte.md +520 -0
  157. package/docs/uk/intlayer_with_vite+vue.md +1113 -0
  158. package/docs/uk/introduction.md +222 -0
  159. package/docs/uk/locale_mapper.md +242 -0
  160. package/docs/uk/mcp_server.md +211 -0
  161. package/docs/uk/packages/express-intlayer/t.md +465 -0
  162. package/docs/uk/packages/intlayer/getEnumeration.md +159 -0
  163. package/docs/uk/packages/intlayer/getHTMLTextDir.md +121 -0
  164. package/docs/uk/packages/intlayer/getLocaleLang.md +81 -0
  165. package/docs/uk/packages/intlayer/getLocaleName.md +135 -0
  166. package/docs/uk/packages/intlayer/getLocalizedUrl.md +338 -0
  167. package/docs/uk/packages/intlayer/getMultilingualUrls.md +359 -0
  168. package/docs/uk/packages/intlayer/getPathWithoutLocale.md +75 -0
  169. package/docs/uk/packages/intlayer/getPrefix.md +213 -0
  170. package/docs/uk/packages/intlayer/getTranslation.md +190 -0
  171. package/docs/uk/packages/intlayer/getTranslationContent.md +189 -0
  172. package/docs/uk/packages/next-intlayer/t.md +365 -0
  173. package/docs/uk/packages/next-intlayer/useDictionary.md +276 -0
  174. package/docs/uk/packages/next-intlayer/useIntlayer.md +263 -0
  175. package/docs/uk/packages/next-intlayer/useLocale.md +166 -0
  176. package/docs/uk/packages/react-intlayer/t.md +311 -0
  177. package/docs/uk/packages/react-intlayer/useDictionary.md +295 -0
  178. package/docs/uk/packages/react-intlayer/useI18n.md +250 -0
  179. package/docs/uk/packages/react-intlayer/useIntlayer.md +251 -0
  180. package/docs/uk/packages/react-intlayer/useLocale.md +210 -0
  181. package/docs/uk/per_locale_file.md +345 -0
  182. package/docs/uk/plugins/sync-json.md +398 -0
  183. package/docs/uk/readme.md +265 -0
  184. package/docs/uk/releases/v6.md +305 -0
  185. package/docs/uk/releases/v7.md +624 -0
  186. package/docs/uk/roadmap.md +346 -0
  187. package/docs/uk/testing.md +204 -0
  188. package/docs/vi/configuration.md +6 -1
  189. package/docs/vi/dictionary/content_file.md +6 -1
  190. package/docs/zh/configuration.md +6 -1
  191. package/docs/zh/dictionary/content_file.md +6 -1
  192. package/frequent_questions/ar/error-vite-env-only.md +77 -0
  193. package/frequent_questions/de/error-vite-env-only.md +77 -0
  194. package/frequent_questions/en/error-vite-env-only.md +77 -0
  195. package/frequent_questions/en-GB/error-vite-env-only.md +77 -0
  196. package/frequent_questions/es/error-vite-env-only.md +76 -0
  197. package/frequent_questions/fr/error-vite-env-only.md +77 -0
  198. package/frequent_questions/hi/error-vite-env-only.md +77 -0
  199. package/frequent_questions/id/error-vite-env-only.md +77 -0
  200. package/frequent_questions/it/error-vite-env-only.md +77 -0
  201. package/frequent_questions/ja/error-vite-env-only.md +77 -0
  202. package/frequent_questions/ko/error-vite-env-only.md +77 -0
  203. package/frequent_questions/pl/error-vite-env-only.md +77 -0
  204. package/frequent_questions/pt/error-vite-env-only.md +77 -0
  205. package/frequent_questions/ru/error-vite-env-only.md +77 -0
  206. package/frequent_questions/tr/error-vite-env-only.md +77 -0
  207. package/frequent_questions/uk/SSR_Next_no_[locale].md +104 -0
  208. package/frequent_questions/uk/array_as_content_declaration.md +72 -0
  209. package/frequent_questions/uk/build_dictionaries.md +58 -0
  210. package/frequent_questions/uk/build_error_CI_CD.md +74 -0
  211. package/frequent_questions/uk/bun_set_up.md +53 -0
  212. package/frequent_questions/uk/customized_locale_list.md +64 -0
  213. package/frequent_questions/uk/domain_routing.md +113 -0
  214. package/frequent_questions/uk/error-vite-env-only.md +77 -0
  215. package/frequent_questions/uk/esbuild_error.md +29 -0
  216. package/frequent_questions/uk/get_locale_cookie.md +142 -0
  217. package/frequent_questions/uk/intlayer_command_undefined.md +155 -0
  218. package/frequent_questions/uk/locale_incorect_in_url.md +73 -0
  219. package/frequent_questions/uk/package_version_error.md +181 -0
  220. package/frequent_questions/uk/static_rendering.md +44 -0
  221. package/frequent_questions/uk/translated_path_url.md +55 -0
  222. package/frequent_questions/uk/unknown_command.md +97 -0
  223. package/frequent_questions/vi/error-vite-env-only.md +77 -0
  224. package/frequent_questions/zh/error-vite-env-only.md +77 -0
  225. package/legal/uk/privacy_notice.md +83 -0
  226. package/legal/uk/terms_of_service.md +55 -0
  227. package/package.json +9 -9
  228. package/src/generated/blog.entry.ts +20 -0
  229. package/src/generated/frequentQuestions.entry.ts +20 -0
@@ -0,0 +1,247 @@
1
+ ---
2
+ createdAt: 2025-09-10
3
+ updatedAt: 2025-09-10
4
+ title: コンポーネント単位 vs 集中型 i18n: Intlayer を用いた新しいアプローチ
5
+ description: React の国際化戦略を深掘りし、集中型、キー単位(per-key)、コンポーネント単位の各アプローチを比較し、Intlayer を紹介します。
6
+ keywords:
7
+ - i18n
8
+ - React
9
+ - 国際化
10
+ - Intlayer
11
+ - 最適化
12
+ - バンドルサイズ
13
+ slugs:
14
+ - blog
15
+ - per-component-vs-centralized-i18n
16
+ ---
17
+
18
+ # コンポーネント単位 vs 集中型 i18n
19
+
20
+ コンポーネント単位のアプローチは新しい概念ではありません。例えば、Vue エコシステムでは、`vue-i18n` が [SFC i18n(Single File Component)](https://vue-i18n.intlify.dev/guide/advanced/sfc.html) をサポートしています。Nuxt も [コンポーネント単位の翻訳](https://i18n.nuxtjs.org/docs/guide/per-component-translations) を提供しており、Angular は [Feature Modules](https://v17.angular.io/guide/feature-modules) を通じて同様のパターンを採用しています。
21
+
22
+ Flutter アプリでも、しばしば次のようなパターンが見られます:
23
+
24
+ ```bash
25
+ lib/
26
+ └── features/
27
+ └── login/
28
+ ├── login_screen.dart
29
+ └── login_screen.i18n.dart # <- 翻訳はここに格納されます
30
+ ```
31
+
32
+ ```dart fileName='lib/features/login/login_screen.i18n.dart'
33
+ import 'package:i18n_extension/i18n_extension.dart';
34
+
35
+ extension Localization on String {
36
+ static var _t = Translations.byText("en") +
37
+ {
38
+ "Hello": {
39
+ "en": "Hello",
40
+ "fr": "Bonjour",
41
+ },
42
+ };
43
+
44
+ String get i18n => localize(this, _t);
45
+ }
46
+ ```
47
+
48
+ しかし、Reactの世界では、主に異なるアプローチが見られ、ここではそれらを3つのカテゴリに分類します:
49
+
50
+ <Columns>
51
+ <Column>
52
+
53
+ **集中型アプローチ** (i18next, next-intl, react-intl, lingui)
54
+
55
+ - (namespaces を使わない場合) はコンテンツを取得する単一のソースを想定します。デフォルトでは、アプリ起動時にすべてのページのコンテンツを読み込みます。
56
+
57
+ </Column>
58
+ <Column>
59
+
60
+ **細粒度アプローチ** (intlayer, inlang)
61
+
62
+ - キー単位、またはコンポーネント単位でコンテンツ取得を細分化する。
63
+
64
+ </Column>
65
+ </Columns>
66
+
67
+ > このブログでは、すでに解説したためコンパイラベースのソリューションには焦点を当てません: [コンパイラ vs 宣言型 i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/ja/compiler_vs_declarative_i18n.md).
68
+ > コンパイラベースの i18n(例: Lingui)は、コンテンツの抽出と読み込みを自動化するだけであることに注意してください。内部的には、しばしば他のアプローチと同じ制約を共有します。
69
+
70
+ > コンテンツ取得をより細かくすればするほど、コンポーネントに追加の状態やロジックを挿入してしまうリスクが高まることに注意してください。
71
+
72
+ グラニュラーなアプローチは集中型より柔軟ですが、多くの場合トレードオフになります。ライブラリが "tree shaking" を謳っていても、実際にはページごとにすべての言語を読み込むことが多いでしょう。
73
+
74
+ 大まかに言うと、判断は次のように分かれます:
75
+
76
+ - アプリケーションのページ数が言語数より多い場合は、グラニュラーなアプローチを優先すべきです。
77
+ - 言語数がページ数より多い場合は、集中型アプローチを選ぶべきです。
78
+
79
+ もちろん、ライブラリの作者はこれらの制約を認識しており、回避策を提供しています。
80
+ その中には:ネームスペースに分割すること、JSONファイルを動的に読み込むこと(`await import()`)、あるいはビルド時にコンテンツをパージすることなどがあります。
81
+
82
+ 同時に、コンテンツを動的に読み込むとサーバーへの追加リクエストが発生することを知っておくべきです。追加の `useState` や他のフックごとに、追加のサーバーリクエストが必要になります。
83
+
84
+ > この点を解決するために、Intlayer は複数のコンテンツ定義を同じキーの下にグループ化することを提案します。Intlayer はその後、それらのコンテンツをマージします。
85
+
86
+ しかし、これらの解決策を見ても、最も一般的に採用されているのは集中型のアプローチであることは明らかです。
87
+
88
+ ### では、なぜ集中型アプローチはこれほど人気があるのか?
89
+
90
+ - まず、i18next は広く使われるようになった最初のソリューションで、PHP や Java のアーキテクチャ(MVC)に触発された哲学に従っており、関心の厳格な分離(コンテンツをコードから切り離す)を前提としています。i18next は 2011 年に登場し、コンポーネントベースのアーキテクチャ(例: React)への大きな移行が起こる前にその基準を確立しました。
91
+ - 次に、一度ライブラリが広く採用されると、エコシステムを他のパターンに移行させるのは難しくなります。
92
+ - 中央集権的なアプローチは、Crowdin、Phrase、Localized のような翻訳管理システムでも扱いやすくなります。
93
+ - コンポーネント単位のアプローチのロジックは中央集権的なものよりも複雑で、特にコンテンツがどこにあるかを特定するといった問題を解決する必要がある場合、開発に余分な時間がかかります。
94
+
95
+ ### では、なぜ中央集約型アプローチに固執しないのか?
96
+
97
+ 問題になり得る理由を説明します:
98
+
99
+ - **未使用データ:**
100
+ ページが読み込まれると、しばしば他のすべてのページのコンテンツも読み込まれます。(10ページのアプリなら、読み込まれるコンテンツの90%が未使用です。)モーダルを遅延読み込みしても?i18nライブラリは気にせず、いずれにせよ文字列を先に読み込みます。
101
+ - **パフォーマンス:**
102
+ 再レンダリングごとに、すべてのコンポーネントが巨大なJSONペイロードでハイドレートされ、アプリが成長するにつれてリアクティビティに悪影響を与えます。
103
+ - **保守性:**
104
+ 大きなJSONファイルの保守は辛いです。翻訳を追加するためにファイル間を行き来し、翻訳漏れがないことや**孤立したキー(orphan keys)**が残っていないことを確認する必要があります。
105
+ - **デザインシステム:**
106
+ それはデザインシステム(例: `LoginForm` コンポーネント)との非互換性を生み、異なるアプリ間でのコンポーネントの重複利用を制約します。
107
+
108
+ **"でも私たちは Namespaces を発明した!"**
109
+
110
+ 確かに、それは大きな前進です。Vite + React + React Router v7 + Intlayer のセットアップにおけるメインバンドルサイズの比較を見てみましょう。20ページのアプリケーションをシミュレートしました。
111
+
112
+ 最初の例はロケールごとの遅延読み込み(lazy-loaded)翻訳や namespace 分割を含んでいません。2番目の例はコンテンツのパージ(不要な翻訳の削除)と翻訳の動的ロードを含みます。
113
+
114
+ | 最適化されたバンドル | 最適化されていないバンドル |
115
+ | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
116
+ | ![最適化されていないバンドル](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png?raw=true) | ![最適化されたバンドル](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) |
117
+
118
+ 名前空間のおかげで、次のような構成から移行しました:
119
+
120
+ ```bash
121
+ locale/
122
+ ├── en.json
123
+ ├── fr.json
124
+ └── es.json
125
+ ```
126
+
127
+ To this one:
128
+
129
+ ```bash
130
+ locale/
131
+ ├── en/
132
+ │ ├── common.json
133
+ │ ├── navbar.json
134
+ │ ├── footer.json
135
+ │ ├── home.json
136
+ │ └── about.json
137
+ ├── fr/
138
+ │ └── ...
139
+ └── es/
140
+ └── ...
141
+
142
+ ```
143
+
144
+ ここでは、アプリのどのコンテンツをどこで読み込むかを細かく管理する必要があります。結論として、その複雑さのために大多数のプロジェクトはこの部分を省略してしまいます(例えば、良いプラクティスに従うだけでも直面する課題を確認するには [next-i18next ガイド](https://github.com/aymericzip/intlayer/blob/main/docs/blog/ja/i18n_using_next-i18next.md) を参照してください)。その結果、これらのプロジェクトは前述した大量の JSON 読み込み問題に陥ります。
145
+
146
+ > この問題は i18next 固有のものではなく、上で挙げたすべての集中型アプローチに共通する問題であることに注意してください。
147
+
148
+ ただし、すべての粒度を細かくしたアプローチ(granular approaches)がこの問題を解決するわけではないことを覚えておいてください。例えば、`vue-i18n SFC` や `inlang` のアプローチはロケールごとに翻訳を遅延読み込み(lazy load)する仕組みを本質的には持たないため、バンドルサイズの問題を別の問題に置き換えているに過ぎません。
149
+
150
+ さらに、関心の分離(separation of concerns)が適切に行われていないと、翻訳を抽出して翻訳者にレビュー用として渡すことがはるかに難しくなります。
151
+
152
+ ### Intlayerのコンポーネント単位アプローチがこれをどのように解決するか
153
+
154
+ Intlayerは以下の手順で進めます:
155
+
156
+ 1. **宣言(Declaration):** `*.content.{ts|jsx|cjs|json|json5|...}` ファイルを使用して、コードベースの任意の場所にコンテンツを宣言します。これにより、コンテンツをコロケート(colocated)したまま関心の分離を確保できます。コンテンツファイルはロケールごとのものでも多言語対応でも構いません。
157
+ 2. **処理:** Intlayer はビルドステップを実行して、JS ロジックの処理、欠落した翻訳のフォールバック対応、TypeScript 型の生成、重複コンテンツの管理、CMS からのコンテンツ取得などを行います。
158
+ 3. **パージ:** アプリをビルドすると、Intlayer は未使用のコンテンツをパージ(Tailwind がクラスを管理する方法に少し似ています)し、コンテンツを次のように置き換えます:
159
+
160
+ **宣言:**
161
+
162
+ ```tsx
163
+ // src/MyComponent.tsx
164
+ export const MyComponent = () => {
165
+ const content = useIntlayer("my-key");
166
+ return <h1>{content.title}</h1>;
167
+ };
168
+ ```
169
+
170
+ ```tsx
171
+ // src/myComponent.content.ts
172
+ export const {
173
+ key: "my-key",
174
+ content: t({
175
+ ja: { title: "私のタイトル" },
176
+ en: { title: "My title" },
177
+ fr: { title: "Mon titre" }
178
+ })
179
+ }
180
+
181
+ ```
182
+
183
+ **処理:** Intlayer は `.content` ファイルに基づいて辞書を構築し、次を生成します:
184
+
185
+ ```json5
186
+ // ファイル: .intlayer/dynamic_dictionary/en/my-key.json
187
+ {
188
+ "key": "my-key",
189
+ "content": { "title": "My title" },
190
+ }
191
+ ```
192
+
193
+ **置換:** Intlayerはアプリケーションのビルド中にコンポーネントを変換します。
194
+
195
+ **- 静的インポートモード:**
196
+
197
+ ```tsx
198
+ // JSXライクな構文でのコンポーネント表現
199
+ export const MyComponent = () => {
200
+ const content = useDictionary({
201
+ key: "my-key",
202
+ content: {
203
+ nodeType: "translation",
204
+ translation: {
205
+ en: { title: "My title" },
206
+ fr: { title: "Mon titre" },
207
+ },
208
+ },
209
+ });
210
+
211
+ return <h1>{content.title}</h1>;
212
+ };
213
+ ```
214
+
215
+ **- 動的インポートモード:**
216
+
217
+ ```tsx
218
+ // JSXライクな構文でのコンポーネント表現
219
+ export const MyComponent = () => {
220
+ const content = useDictionaryAsync({
221
+ en: () =>
222
+ import(".intlayer/dynamic_dictionary/en/my-key.json", {
223
+ with: { type: "json" },
224
+ }).then((mod) => mod.default),
225
+ // 他の言語も同様
226
+ });
227
+
228
+ return <h1>{content.title}</h1>;
229
+ };
230
+ ```
231
+
232
+ > `useDictionaryAsync` は、必要なときにのみローカライズされた JSON を読み込む Suspense のような仕組みを使用します。
233
+
234
+ **このコンポーネント単位のアプローチの主な利点:**
235
+
236
+ - コンテンツ宣言をコンポーネントに近くに置くことで、保守性が向上します(例:コンポーネントを別のアプリやデザインシステムに移動する場合など)。コンポーネントのフォルダを削除すれば関連するコンテンツも削除されます — これはおそらく `.test` や `.stories` に対して既に行っているのと同じです。
237
+
238
+ - コンポーネントごとのアプローチにより、AIエージェントがすべての異なるファイルをまたいで参照する必要がなくなります。翻訳を一箇所で扱うため、タスクの複雑さと使用するトークン量を抑えられます。
239
+
240
+ ### 制限事項
241
+
242
+ もちろん、このアプローチにはトレードオフがあります:
243
+
244
+ - 他の l10n システムや追加ツールとの接続が難しくなります。
245
+ - ロックインされやすくなります(これは特定の構文を持つ任意の i18n ソリューションで基本的に既に起こっていることです)。
246
+
247
+ そのため Intlayer は、独自の AI プロバイダーと API キーを使った AI 翻訳を含む、i18n のための包括的なツールセット(100% 無料かつ OSS)を提供しようとしています。Intlayer はまた、JSON を同期するためのツールチェーンを提供しており、ICU / vue-i18n / i18next のメッセージフォーマッターのように機能して、コンテンツを各フォーマットにマッピングします。
@@ -0,0 +1,246 @@
1
+ ---
2
+ createdAt: 2025-09-10
3
+ updatedAt: 2025-09-10
4
+ title: 컴포넌트별 vs 중앙집중식 i18n: Intlayer를 통한 새로운 접근
5
+ description: React에서의 국제화 전략을 심층적으로 탐구하며 중앙집중식, 키별, 컴포넌트별 접근법을 비교하고 Intlayer를 소개합니다.
6
+ keywords:
7
+ - i18n
8
+ - React
9
+ - Internationalization
10
+ - Intlayer
11
+ - 최적화
12
+ - 번들 크기
13
+ slugs:
14
+ - blog
15
+ - per-component-vs-centralized-i18n
16
+ ---
17
+
18
+ # 컴포넌트별 i18n vs 중앙집중식 i18n
19
+
20
+ 컴포넌트별 접근법은 새로운 개념이 아닙니다. 예를 들어, Vue 생태계에서 `vue-i18n`은 [SFC i18n (Single File Component)](https://vue-i18n.intlify.dev/guide/advanced/sfc.html)을 지원합니다. Nuxt도 [컴포넌트별 번역](https://i18n.nuxtjs.org/docs/guide/per-component-translations)을 제공하며, Angular는 [Feature Modules](https://v17.angular.io/guide/feature-modules)를 통해 유사한 패턴을 사용합니다.
21
+
22
+ Flutter 앱에서도 종종 다음과 같은 패턴을 볼 수 있습니다:
23
+
24
+ ```bash
25
+ lib/
26
+ └── features/
27
+ └── login/
28
+ ├── login_screen.dart
29
+ └── login_screen.i18n.dart # <- 번역은 여기 저장됩니다
30
+ ```
31
+
32
+ ```dart fileName='lib/features/login/login_screen.i18n.dart'
33
+ import 'package:i18n_extension/i18n_extension.dart';
34
+
35
+ extension Localization on String {
36
+ static var _t = Translations.byText("en") +
37
+ {
38
+ "Hello": {
39
+ "en": "Hello",
40
+ "fr": "Bonjour",
41
+ },
42
+ };
43
+
44
+ String get i18n => localize(this, _t);
45
+ }
46
+ ```
47
+
48
+ 하지만 React 환경에서는 주로 여러 다른 접근 방식을 보며, 저는 이를 세 가지 범주로 나누겠습니다:
49
+
50
+ <Columns>
51
+ <Column>
52
+
53
+ **중앙집중식 접근** (i18next, next-intl, react-intl, lingui)
54
+
55
+ - (네임스페이스 없이) 단일 소스에서 콘텐츠를 가져온다고 가정합니다. 기본적으로 앱이 로드될 때 모든 페이지의 콘텐츠를 로드합니다.
56
+
57
+ </Column>
58
+ <Column>
59
+
60
+ **세분화된 접근** (intlayer, inlang)
61
+
62
+ - 키 단위 또는 컴포넌트 단위로 콘텐츠 조회를 세분화합니다.
63
+
64
+ </Column>
65
+ </Columns>
66
+
67
+ > 이 블로그에서는 컴파일러 기반 솔루션에 초점을 맞추지 않겠습니다. 해당 내용은 이미 여기에서 다뤘습니다: [Compiler vs Declarative i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/ko/compiler_vs_declarative_i18n.md).
68
+ > 컴파일러 기반 i18n(예: Lingui)은 단순히 콘텐츠 추출과 로딩을 자동화할 뿐이라는 점을 유의하세요. 내부적으로는 다른 접근 방식들과 동일한 한계를 공유하는 경우가 많습니다.
69
+
70
+ > 콘텐츠를 가져오는 방식을 더 세분화할수록 컴포넌트에 추가적인 상태와 로직을 삽입할 위험이 커집니다.
71
+
72
+ 세분화된 접근 방식은 중앙집중식 접근보다 더 유연하지만, 종종 트레이드오프가 따릅니다. 해당 라이브러리들이 "tree shaking"을 홍보하더라도, 실제로는 종종 모든 언어에 대해 페이지를 로드하게 됩니다.
73
+
74
+ 요약하면, 결정은 대체로 다음과 같이 나뉩니다:
75
+
76
+ - 애플리케이션의 페이지 수가 언어 수보다 많은 경우에는 세분화된 접근 방식을 선호해야 합니다.
77
+ - 언어 수가 페이지 수보다 많은 경우에는 중앙집중식 접근을 택하는 것이 좋습니다.
78
+
79
+ 물론 라이브러리 저자들은 이러한 한계를 인지하고 우회 방법을 제공합니다. 그중에는 네임스페이스로 분리하기, JSON 파일을 동적으로 로드하기 (`await import()`), 또는 빌드 시 콘텐츠를 정리(purge)하는 방법 등이 있습니다.
80
+
81
+ 동시에, 콘텐츠를 동적으로 로드하면 서버에 대한 추가 요청이 발생한다는 점을 알아두어야 합니다. 추가적인 `useState`나 hook 하나마다 서버 요청이 하나 더 발생합니다.
82
+
83
+ > 이 문제를 해결하기 위해, Intlayer는 여러 콘텐츠 정의를 동일한 키 아래에 그룹화할 것을 제안합니다. Intlayer는 그런 다음 해당 콘텐츠를 병합합니다.
84
+
85
+ 하지만 이러한 모든 해결책을 종합해보면, 가장 널리 사용되는 접근 방식은 중앙집중식 방식이라는 점이 분명합니다.
86
+
87
+ ### 그렇다면 중앙집중식 접근 방식이 왜 이렇게 인기 있을까요?
88
+
89
+ - 먼저, i18next는 널리 사용된 최초의 솔루션 중 하나로, PHP와 Java 아키텍처(MVC)에서 영감을 받은 철학(관심사의 엄격한 분리 — 콘텐츠를 코드와 분리해서 유지)을 따랐습니다. 2011년에 등장하여 React와 같은 컴포넌트 기반 아키텍처로의 대대적인 전환보다도 먼저 그 표준을 확립했습니다.
90
+ - 한 번 라이브러리가 널리 채택되면 에코시스템을 다른 패턴으로 전환하기가 어려워집니다.
91
+ - 중앙집중식 접근 방식은 Crowdin, Phrase 또는 Localized와 같은 번역 관리 시스템(Translation Management Systems)에서도 작업을 더 수월하게 만듭니다.
92
+ - 컴포넌트별 접근 방식의 로직은 중앙집중식 방식보다 더 복잡하고 개발에 추가 시간이 필요합니다. 특히 콘텐츠가 어디에 위치하는지 식별하는 문제를 해결해야 할 때 그렇습니다.
93
+
94
+ ### 알겠지만, 중앙집중식 접근 방식을 고수하면 안 되는 이유는 무엇인가요?
95
+
96
+ 다음은 귀하의 앱에 문제가 될 수 있는 이유입니다:
97
+
98
+ - **미사용 데이터:**
99
+ 페이지가 로드될 때 종종 다른 모든 페이지의 콘텐츠를 함께 로드합니다. (10페이지 앱이라면 로드된 콘텐츠의 90%가 사용되지 않음). 모달을 지연 로드(lazy load)하나요? i18n 라이브러리는 상관하지 않고 어쨌든 문자열을 먼저 로드합니다.
100
+ - **성능:**
101
+ 각 리렌더링마다 모든 컴포넌트가 거대한 JSON 페이로드로 하이드레이션되어, 앱이 커질수록 반응성에 영향을 줍니다.
102
+ - **유지보수:**
103
+ 큰 JSON 파일을 관리하는 것은 고통스럽습니다. 번역을 추가하려면 파일을 이곳저곳 이동해야 하고, 누락된 번역이 없는지, **orphan keys**가 남아있지 않은지 확인해야 합니다.
104
+ - **디자인 시스템:**
105
+ 이는 디자인 시스템(예: `LoginForm` 컴포넌트)과의 비호환성을 초래하며, 서로 다른 앱들 간의 컴포넌트 복제를 제한합니다.
106
+
107
+ **"하지만 우리는 Namespaces를 발명했잖아!"**
108
+
109
+ 물론이고, 이는 엄청난 진전입니다. 이제 Vite + React + React Router v7 + Intlayer 설정에서 메인 번들 크기를 비교해 보겠습니다. 20페이지짜리 애플리케이션을 시뮬레이션했습니다.
110
+
111
+ 첫 번째 예시는 locale별로 lazy-loaded 번역을 포함하지 않았으며 네임스페이스 분할도 없습니다. 두 번째는 content purging + 번역의 동적 로딩을 포함합니다.
112
+
113
+ | 최적화된 번들 | 최적화되지 않은 번들 |
114
+ | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
115
+ | ![최적화되지 않은 번들](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png?raw=true) | ![최적화된 번들](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) |
116
+
117
+ 네임스페이스 덕분에, 우리는 다음 구조에서:
118
+
119
+ ```bash
120
+ locale/
121
+ ├── en.json
122
+ ├── fr.json
123
+ └── es.json
124
+ ```
125
+
126
+ 다음 구조로:
127
+
128
+ ```bash
129
+ locale/
130
+ ├── en/
131
+ │ ├── common.json
132
+ │ ├── navbar.json
133
+ │ ├── footer.json
134
+ │ ├── home.json
135
+ │ └── about.json
136
+ ├── fr/
137
+ │ └── ...
138
+ └── es/
139
+ └── ...
140
+
141
+ ```
142
+
143
+ 이제 앱의 어떤 콘텐츠를 언제 어디서 로드할지 세밀하게 관리해야 합니다. 결론적으로, 복잡성 때문에 대다수의 프로젝트는 이 부분을 건너뛰게 됩니다(예: 좋은 관행을 따르는 것만으로 발생하는 어려움을 보려면 [next-i18next 가이드](https://github.com/aymericzip/intlayer/blob/main/docs/blog/ko/i18n_using_next-i18next.md)를 참조하세요).
144
+ 그 결과, 이러한 프로젝트들은 앞서 설명한 대규모 JSON 로딩 문제에 직면하게 됩니다.
145
+
146
+ > 참고: 이 문제는 i18next에만 국한된 것이 아니라 위에 열거한 모든 중앙집중식 접근 방식에 공통적으로 발생합니다.
147
+
148
+ 하지만 모든 세분화된 접근 방식이 이 문제를 해결하는 것은 아니라는 점을 상기시키고 싶습니다. 예를 들어, `vue-i18n SFC`나 `inlang`와 같은 접근 방식은 본질적으로 로케일별 번역을 지연 로드(lazy load)하지 않으므로 번들 크기 문제를 단지 다른 문제로 바꾸는 것에 불과합니다.
149
+
150
+ 또한, 관심사의 분리가 제대로 이루어지지 않으면 번역가에게 검토용으로 번역을 추출하고 제공하는 작업이 훨씬 더 어려워집니다.
151
+
152
+ ### Intlayer의 컴포넌트별 접근 방식이 이를 해결하는 방법
153
+
154
+ Intlayer는 다음과 같은 여러 단계를 거칩니다:
155
+
156
+ 1. **선언(Declaration):** `*.content.{ts|jsx|cjs|json|json5|...}` 파일을 사용하여 코드베이스 어디에서나 콘텐츠를 선언하세요. 이렇게 하면 콘텐츠를 코드와 동일 위치에 두면서 관심사의 분리를 보장할 수 있습니다. 콘텐츠 파일은 로케일별일 수도 있고 다국어(multilingual)일 수도 있습니다.
157
+ 2. **처리:** Intlayer는 빌드 단계에서 JS 로직을 처리하고, 누락된 번역 폴백을 처리하며, TypeScript 타입을 생성하고, 중복된 콘텐츠를 관리하고, CMS에서 콘텐츠를 가져오는 등 다양한 작업을 수행합니다.
158
+ 3. **정리(Purging):** 앱을 빌드할 때 Intlayer는 사용되지 않는 콘텐츠를 정리합니다(약간 Tailwind가 클래스 관리를 하는 방식과 유사). 다음과 같이 콘텐츠를 대체합니다:
159
+
160
+ **선언:**
161
+
162
+ ```tsx
163
+ // src/MyComponent.tsx
164
+ export const MyComponent = () => {
165
+ const content = useIntlayer("my-key");
166
+ return <h1>{content.title}</h1>;
167
+ };
168
+ ```
169
+
170
+ ```tsx
171
+ // src/myComponent.content.ts
172
+ export const {
173
+ key: "my-key",
174
+ content: t({
175
+ en: { title: "My title" },
176
+ fr: { title: "Mon titre" }
177
+ })
178
+ }
179
+
180
+ ```
181
+
182
+ **처리:** Intlayer는 `.content` 파일을 기반으로 사전(dictionary)을 빌드하고 다음을 생성합니다:
183
+
184
+ ```json5
185
+ // .intlayer/dynamic_dictionary/ko/my-key.json
186
+ {
187
+ "key": "my-key",
188
+ "content": { "title": "내 제목" },
189
+ }
190
+ ```
191
+
192
+ **대체:** Intlayer는 애플리케이션 빌드 중에 컴포넌트를 변환합니다.
193
+
194
+ **- 정적 임포트 모드:**
195
+
196
+ ```tsx
197
+ // JSX와 유사한 문법으로 표현한 컴포넌트
198
+ export const MyComponent = () => {
199
+ const content = useDictionary({
200
+ key: "my-key",
201
+ content: {
202
+ nodeType: "translation",
203
+ translation: {
204
+ en: { title: "내 제목" },
205
+ fr: { title: "내 제목" },
206
+ },
207
+ },
208
+ });
209
+
210
+ return <h1>{content.title}</h1>;
211
+ };
212
+ ```
213
+
214
+ **- 동적 임포트 모드:**
215
+
216
+ ```tsx
217
+ // JSX와 유사한 문법으로 표현한 컴포넌트
218
+ export const MyComponent = () => {
219
+ const content = useDictionaryAsync({
220
+ en: () =>
221
+ import(".intlayer/dynamic_dictionary/en/my-key.json", {
222
+ with: { type: "json" },
223
+ }).then((mod) => mod.default),
224
+ // 다른 언어도 동일합니다
225
+ });
226
+
227
+ return <h1>{content.title}</h1>;
228
+ };
229
+ ```
230
+
231
+ > `useDictionaryAsync`는 필요할 때만 로컬라이즈된 JSON을 로드하기 위해 Suspense 유사 메커니즘을 사용합니다.
232
+
233
+ **이 컴포넌트별 접근 방식의 주요 이점:**
234
+
235
+ - 컴포넌트 가까이에 콘텐츠 선언을 유지하면 유지보수가 더 쉬워집니다(예: 컴포넌트를 다른 앱이나 디자인 시스템으로 옮기는 경우). 컴포넌트 폴더를 삭제하면 관련 콘텐츠도 함께 제거되므로, 이미 `.test`, `.stories`에 대해 하시는 것과 동일한 방식을 적용할 수 있습니다)
236
+
237
+ - 컴포넌트별 접근 방식은 AI 에이전트가 여러 파일을 이리저리 찾아다니지 않아도 되게 합니다. 모든 번역을 한 곳에서 처리하므로 작업의 복잡성과 사용되는 토큰 수를 줄여줍니다.
238
+
239
+ ### 제한 사항
240
+
241
+ 물론, 이 접근 방식에는 트레이드오프가 있습니다:
242
+
243
+ - 다른 l10n 시스템이나 추가 툴링과 연동하기가 더 어렵습니다.
244
+ - 락인(lock-in)이 발생할 수 있습니다(특정 문법 때문에 사실상 대부분의 i18n 솔루션에서 이미 그러합니다).
245
+
246
+ 바로 이런 이유로 Intlayer는 i18n을 위한 완전한 툴셋(100% 무료 및 OSS)을 제공하려고 합니다. 여기에는 자체 AI Provider와 API keys를 사용하는 AI 번역 기능도 포함됩니다. Intlayer는 또한 JSON을 동기화하는 툴링을 제공하는데, 이는 ICU / vue-i18n / i18next 메시지 포매터처럼 동작하여 콘텐츠를 해당 포맷에 맞게 매핑합니다.