@intlayer/docs 7.5.13 → 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 (35) 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/per-component_vs_centralized_i18n.md +248 -0
  18. package/blog/vi/per-component_vs_centralized_i18n.md +246 -0
  19. package/blog/zh/per-component_vs_centralized_i18n.md +248 -0
  20. package/dist/cjs/common.cjs.map +1 -1
  21. package/dist/cjs/generated/blog.entry.cjs +20 -0
  22. package/dist/cjs/generated/blog.entry.cjs.map +1 -1
  23. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  24. package/dist/cjs/generated/frequentQuestions.entry.cjs.map +1 -1
  25. package/dist/cjs/generated/legal.entry.cjs.map +1 -1
  26. package/dist/esm/common.mjs.map +1 -1
  27. package/dist/esm/generated/blog.entry.mjs +20 -0
  28. package/dist/esm/generated/blog.entry.mjs.map +1 -1
  29. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  30. package/dist/esm/generated/frequentQuestions.entry.mjs.map +1 -1
  31. package/dist/esm/generated/legal.entry.mjs.map +1 -1
  32. package/dist/types/generated/blog.entry.d.ts +1 -0
  33. package/dist/types/generated/blog.entry.d.ts.map +1 -1
  34. package/package.json +9 -9
  35. package/src/generated/blog.entry.ts +20 -0
@@ -0,0 +1,248 @@
1
+ ---
2
+ createdAt: 2025-09-10
3
+ updatedAt: 2025-09-10
4
+ title: التعريب لكل مكوّن مقابل التعريب المركزي: نهج جديد مع Intlayer
5
+ description: غوص عميق في استراتيجيات التعريب في React، مع مقارنة النهج المركزي، لكل-مفتاح، ولكل-مكوّن، وتقديم 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
+ # التعريب لكل مكوّن مقابل التعريب المركزي
19
+
20
+ نهج التعريب لكل مكوّن ليس مفهومًا جديدًا. على سبيل المثال، في بيئة Vue، تدعم مكتبة `vue-i18n` [i18n SFC (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 (compiler-based)، والتي قمت بتغطيتها سابقًا هنا: [Compiler vs Declarative i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/ar/compiler_vs_declarative_i18n.md).
68
+ > لاحظ أن i18n المعتمدة على الـ compiler (مثل Lingui) تقوم ببساطة بأتمتة استخراج وتحميل المحتوى. تحت الغطاء، غالبًا ما تشترك في نفس القيود التي تواجه النهج الأخرى.
69
+
70
+ > لاحظ أنه كلما زادت دقة تفصيل طريقة استرجاع المحتوى، زادت المخاطرة بإدخال حالة (state) ومنطق إضافي داخل مكوناتك.
71
+
72
+ النهج التفصيلي أكثر مرونة من النهج المركزي، لكنه غالبًا ما يكون مقايضة. حتى وإن كانت تلك المكتبات تروّج لخاصية "tree shaking"، ففي الممارسة العملية، غالبًا ما ستجد نفسك تقوم بتحميل الصفحة بكل لغة.
73
+
74
+ بشكل عام، يمكن تبسيط القرار كالتالي:
75
+
76
+ - إذا كان تطبيقك يحتوي على صفحات أكثر من عدد اللغات، فعليك تفضيل النهج التفصيلي.
77
+ - إذا كان عدد اللغات أكبر من عدد الصفحات، فعليك التوجه نحو النهج المركزي.
78
+
79
+ بالطبع، مؤلفو المكتبات على دراية بهذه القيود ويقدّمون حلولاً بديلة. من بينها: التقسيم إلى namespaces، التحميل الديناميكي لملفات JSON (`await import()`)، أو إزالة المحتوى أثناء عملية البناء.
80
+
81
+ في نفس الوقت، يجب أن تعلم أنه عندما تقوم بتحميل المحتوى بشكل ديناميكي، فإنك تُولد طلبات إضافية إلى الخادم. كل `useState` إضافي أو hook يعني طلب خادم إضافي.
82
+
83
+ > لإصلاح هذه النقطة، تقترح Intlayer تجميع تعريفات المحتوى المتعددة تحت مفتاح واحد، ثم تقوم Intlayer بدمج ذلك المحتوى.
84
+
85
+ ولكن من بين كل هذه الحلول، يتضح أن النهج الأكثر شعبية هو النهج المركزي.
86
+
87
+ ### فلماذا يُعد النهج المركزي شائعًا إلى هذا الحد؟
88
+
89
+ - أولاً، كانت i18next أول حل يصبح مستخدمًا على نطاق واسع، واتّبع فلسفة مستوحاة من معماريات PHP و Java (MVC)، التي تعتمد على فصل صارم للمسؤوليات (الحفاظ على فصل المحتوى عن الكود). وصلت في عام 2011، مما وضع معاييره حتى قبل التحول الكبير نحو Component-Based Architectures (مثل React).
90
+ - ثم، بمجرد اعتماد مكتبة على نطاق واسع، يصبح من الصعب نقل النظام البيئي إلى أنماط أخرى.
91
+ - يجعل استخدام النهج المركزي أيضًا الأمور أسهل في أنظمة إدارة الترجمة مثل Crowdin و Phrase و Localized.
92
+ - المنطق وراء نهج per-component أكثر تعقيدًا من النهج المركزي ويستغرق وقتًا إضافيًا للتطوير، خصوصًا عندما تضطر لحل مشكلات مثل تحديد مكان المحتوى.
93
+
94
+ ### حسنًا، لكن لماذا لا نلتزم فقط بالنهج المركزي؟
95
+
96
+ دعني أشرح لماذا قد يكون ذلك مشكلة لتطبيقك:
97
+
98
+ - **البيانات غير المستخدمة:**
99
+ عندما يتم تحميل صفحة، غالبًا ما تقوم بتحميل المحتوى الخاص بكل الصفحات الأخرى. (في تطبيق من 10 صفحات، هذا يعني تحميل 90% من المحتوى غير المستخدم). هل تقوم بتحميل نافذة منبثقة بشكل كسول؟ مكتبة i18n لا تهتم، فهي تحمّل السلاسل أولًا على أي حال.
100
+ - **الأداء:**
101
+ في كل عملية إعادة عرض، يتم تهيئة كل مكون من مكوناتك بحمولة JSON ضخمة، مما يؤثر على تفاعلية التطبيق مع نموه.
102
+ - **الصيانة:**
103
+ الحفاظ على ملفات JSON كبيرة أمر مؤلم. عليك التنقل بين الملفات لإضافة ترجمة، مع التأكد من عدم وجود ترجمات مفقودة ولا وجود **مفاتيح يتيمة** متروكة.
104
+ - **نظام التصميم:**
105
+ يُحدث ذلك عدم توافق مع أنظمة التصميم (مثل مكون `LoginForm`) ويقيد تكرار المكونات عبر تطبيقات مختلفة.
106
+
107
+ **"لكننا اخترعنا Namespaces!"**
108
+
109
+ بالتأكيد، وهذا تقدم كبير. لنلقِ نظرة على مقارنة حجم الحزمة الرئيسية لتكوين Vite + React + React Router v7 + Intlayer. قمنا بمحاكاة تطبيق مكوّن من 20 صفحة.
110
+
111
+ المثال الأول لا يتضمن ترجمات تُحمّل عند الطلب لكل لغة ولا تقسيمًا للـ namespaces. المثال الثاني يتضمن تنقية المحتوى (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
+ إذن، بفضل الـnamespaces، انتقلنا من هذا الهيكل:
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/ar/i18n_using_next-i18next.md).
144
+
145
+ وبالتالي، تنتهي تلك المشاريع بمشكلة تحميل ملفات JSON الضخمة التي شرحناها سابقًا.
146
+
147
+ > ملاحظة: هذه المشكلة ليست خاصة بـ i18next فقط، بل تنطبق على جميع النهج المركزية المذكورة أعلاه.
148
+
149
+ ومع ذلك، أود أن أذكّركم بأن ليست كل الأساليب الجزئية تحل هذه المشكلة. على سبيل المثال، نهج `vue-i18n SFC` أو `inlang` لا يقومان بشكل افتراضي بتحميل الترجمات لكل لغة بشكل كسول (lazy load)، لذا فإنك ببساطة تستبدل مشكلة حجم الحزمة بمشكلة أخرى.
150
+
151
+ علاوة على ذلك، دون فصل مناسب للمسؤوليات (separation of concerns)، يصبح استخراج الترجمات وتقديمها للمترجمين للمراجعة أكثر صعوبة بكثير.
152
+
153
+ ### كيف يحل نهج Intlayer القائم على كل مكوّن هذه المشكلة
154
+
155
+ يتبع Intlayer عدة خطوات:
156
+
157
+ 1. **الإعلان:** أعلن محتواك في أي مكان داخل قاعدة الشيفرة باستخدام ملفات `*.content.{ts|jsx|cjs|json|json5|...}`. هذا يضمن فصل المسؤوليات مع إبقاء المحتوى موضوعًا جنبًا إلى جنب مع الكود. يمكن أن يكون ملف المحتوى مخصصًا لكل لغة (per-locale) أو متعدد اللغات.
158
+ 2. **المعالجة:** تقوم Intlayer بتشغيل خطوة بناء لمعالجة منطق JS، والتعامل مع حالات السقوط الخاصة بالترجمات المفقودة، وتوليد أنواع TypeScript، وإدارة المحتوى المكرر، وجلب المحتوى من نظام إدارة المحتوى (CMS) الخاص بك، والمزيد.
159
+ 3. **التنقية:** عندما يُبنى تطبيقك، تقوم Intlayer بتنقية المحتوى غير المستخدم (مماثل إلى حد ما لكيفية إدارة Tailwind للفئات الخاصة بك) عن طريق استبدال المحتوى كما يلي:
160
+
161
+ **الإعلان:**
162
+
163
+ ```tsx
164
+ // src/MyComponent.tsx
165
+ export const MyComponent = () => {
166
+ const content = useIntlayer("my-key");
167
+ return <h1>{content.title}</h1>;
168
+ };
169
+ ```
170
+
171
+ ```tsx
172
+ // src/myComponent.content.ts
173
+ export const {
174
+ key: "my-key",
175
+ content: t({
176
+ ar: { title: "العنوان الخاص بي" },
177
+ en: { title: "My title" },
178
+ fr: { title: "Mon titre" }
179
+ })
180
+ }
181
+
182
+ ```
183
+
184
+ **المعالجة:** تقوم Intlayer ببناء القاموس بناءً على ملف `.content` وتولد:
185
+
186
+ ```json5
187
+ // مسار الملف: .intlayer/dynamic_dictionary/en/my-key.json
188
+ {
189
+ "key": "my-key",
190
+ "content": { "title": "My title" },
191
+ }
192
+ ```
193
+
194
+ **الاستبدال:** يقوم Intlayer بتحويل المكوّن الخاص بك أثناء عملية بناء التطبيق.
195
+
196
+ **- وضع الاستيراد الثابت:**
197
+
198
+ ```tsx
199
+ // تمثيل المكوّن بصيغة مشابهة لـ JSX
200
+ export const MyComponent = () => {
201
+ const content = useDictionary({
202
+ key: "my-key",
203
+ content: {
204
+ nodeType: "translation",
205
+ translation: {
206
+ en: { title: "My title" },
207
+ fr: { title: "Mon titre" },
208
+ },
209
+ },
210
+ });
211
+
212
+ return <h1>{content.title}</h1>;
213
+ };
214
+ ```
215
+
216
+ **- وضع الاستيراد الديناميكي:**
217
+
218
+ ```tsx
219
+ // تمثيل المكوّن بصيغة مشابهة لـ JSX
220
+ export const MyComponent = () => {
221
+ const content = useDictionaryAsync({
222
+ en: () =>
223
+ import(".intlayer/dynamic_dictionary/en/my-key.json", {
224
+ with: { type: "json" },
225
+ }).then((mod) => mod.default),
226
+ // نفس الشيء بالنسبة للغات الأخرى
227
+ });
228
+
229
+ return <h1>{content.title}</h1>;
230
+ };
231
+ ```
232
+
233
+ > `useDictionaryAsync` يستخدم آلية مشابهة لـ Suspense لتحميل JSON المحلي فقط عند الحاجة.
234
+
235
+ **الفوائد الرئيسية لهذا النهج per-component:**
236
+
237
+ - إبقاء إعلان المحتوى قرب مكوناتك يسمح بصيانة أفضل (مثلاً نقل مكون إلى تطبيق أو نظام تصميم آخر. حذف مجلد المكون يزيل المحتوى المرتبط أيضاً، كما تفعل على الأرجح بالفعل لملفات `.test` و`.stories`)
238
+
239
+ - نهج لكل-مكوّن يمنع وكلاء الذكاء الاصطناعي من الحاجة إلى التنقّل عبر كل ملفاتك المختلفة. فهو يعالج كل الترجمات في مكان واحد، مما يحدّ من تعقيد المهمة ومن عدد الرموز (tokens) المستخدمة.
240
+
241
+ ### القيود
242
+
243
+ بطبيعة الحال، هذا النهج يأتي بمقايضات:
244
+
245
+ - يصبح من الأصعب الربط مع أنظمة l10n الأخرى والأدوات الإضافية.
246
+ - قد تُصبح مقيدًا (vendor lock-in)، وهو ما يحدث بالفعل مع أي حل i18n بسبب الـ syntax الخاص به.
247
+
248
+ لهذا السبب تحاول Intlayer توفير مجموعة أدوات كاملة لـ i18n (مفتوحة المصدر ومجانية 100%)، تتضمن ترجمة باستخدام AI بواسطة مزوّد AI ومفاتيح API الخاصة بك. كما توفر Intlayer أدوات لمزامنة JSON الخاص بك، وتعمل مثل محولات رسائل ICU / vue-i18n / i18next لربط المحتوى بصيغها الخاصة.
@@ -0,0 +1,248 @@
1
+ ---
2
+ createdAt: 2025-09-10
3
+ updatedAt: 2025-09-10
4
+ title: Per-Komponente vs. Zentralisiertes i18n: Ein neuer Ansatz mit Intlayer
5
+ description: Eine eingehende Analyse der Internationalisierungsstrategien in React, die zentralisierte, per-key- und per-component-Ansätze vergleicht und Intlayer vorstellt.
6
+ keywords:
7
+ - i18n
8
+ - React
9
+ - Internationalisierung
10
+ - Intlayer
11
+ - Optimierung
12
+ - Bundle-Größe
13
+ slugs:
14
+ - blog
15
+ - per-component-vs-centralized-i18n
16
+ ---
17
+
18
+ # Per-Komponente vs. Zentralisiertes i18n
19
+
20
+ Der Per-Komponenten-Ansatz ist kein neues Konzept. Zum Beispiel unterstützt `vue-i18n` im Vue-Ökosystem [SFC-i18n (Single File Component)](https://vue-i18n.intlify.dev/guide/advanced/sfc.html). Nuxt bietet auch [Übersetzungen pro Komponente](https://i18n.nuxtjs.org/docs/guide/per-component-translations), und Angular verwendet ein ähnliches Muster über seine [Feature Modules](https://v17.angular.io/guide/feature-modules).
21
+
22
+ Selbst in einer Flutter-App findet man häufig folgendes Muster:
23
+
24
+ ```bash
25
+ lib/
26
+ └── features/
27
+ └── login/
28
+ ├── login_screen.dart
29
+ └── login_screen.i18n.dart # <- Übersetzungen befinden sich hier
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
+ In der React-Welt sehen wir jedoch hauptsächlich verschiedene Ansätze, die ich in drei Kategorien zusammenfassen werde:
49
+
50
+ <Columns>
51
+ <Column>
52
+
53
+ **Zentralisierter Ansatz** (i18next, next-intl, react-intl, lingui)
54
+
55
+ - (ohne Namespaces) betrachtet eine einzelne Quelle zum Abrufen von Inhalten. Standardmäßig lädst du den Inhalt aller Seiten, wenn deine App startet.
56
+
57
+ </Column>
58
+ <Column>
59
+
60
+ Granularer Ansatz (intlayer, inlang)
61
+
62
+ - Feingranulares Abrufen von Inhalten pro Schlüssel oder pro Komponente.
63
+
64
+ </Column>
65
+ </Columns>
66
+
67
+ > In diesem Blog werde ich mich nicht auf compiler-basierte Lösungen konzentrieren, die ich bereits hier behandelt habe: [Compiler vs deklaratives i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/de/compiler_vs_declarative_i18n.md).
68
+ > Beachte, dass compiler-basierte i18n (z. B. Lingui) lediglich die Extraktion und das Laden von Inhalten automatisiert. Unter der Haube teilen sie oft dieselben Einschränkungen wie andere Ansätze.
69
+
70
+ > Beachte, je feiner du das Abrufen deiner Inhalte gestaltest, desto höher ist das Risiko, zusätzlichen State und Logik in deine Komponenten einzufügen.
71
+
72
+ Granulare Ansätze sind flexibler als zentralisierte, aber oft ein Kompromiss. Selbst wenn diese Bibliotheken "tree shaking" bewerben, lädst du in der Praxis häufig eine Seite in jeder Sprache.
73
+
74
+ Grob gesagt lässt sich die Entscheidung folgendermaßen zusammenfassen:
75
+
76
+ - Wenn deine Anwendung mehr Seiten als Sprachen hat, solltest du einen granularen Ansatz bevorzugen.
77
+ - Wenn du mehr Sprachen als Seiten hast, solltest du zu einem zentralisierten Ansatz tendieren.
78
+
79
+ Natürlich sind die Autor:innen der Bibliotheken sich dieser Einschränkungen bewusst und bieten Workarounds an. Dazu gehören: Aufteilen in Namespaces, dynamisches Laden von JSON-Dateien (`await import()`), oder das Entfernen/Bereinigen von Inhalten zur Build-Zeit.
80
+
81
+ Gleichzeitig sollten Sie wissen, dass das dynamische Laden Ihrer Inhalte zusätzliche Anfragen an Ihren Server verursacht. Jeder zusätzliche `useState` oder hook bedeutet eine zusätzliche Serveranfrage.
82
+
83
+ > Um dieses Problem zu lösen, schlägt Intlayer vor, mehrere Inhaltsdefinitionen unter demselben Key zu gruppieren; Intlayer wird diese Inhalte dann zusammenführen.
84
+
85
+ Trotz dieser Ansätze ist klar, dass der zentralisierte Ansatz der populärste ist.
86
+
87
+ ### Warum ist der zentralisierte Ansatz so beliebt?
88
+
89
+ - Erstens war i18next die erste Lösung, die weit verbreitet wurde und einer Philosophie folgte, die von PHP- und Java-Architekturen (MVC) inspiriert ist und auf einer strikten Trennung der Verantwortlichkeiten beruht (Inhalte vom Code getrennt zu halten). Sie erschien 2011 und etablierte ihre Standards noch vor der massiven Verschiebung hin zu komponentenbasierten Architekturen (wie React).
90
+ - Sobald eine Bibliothek einmal weit verbreitet ist, wird es schwierig, das Ökosystem auf andere Muster umzustellen.
91
+ - Ein zentralisierter Ansatz erleichtert zudem die Arbeit mit Translation Management Systems wie Crowdin, Phrase oder Localized.
92
+ - Die Logik hinter einem per-Komponente-Ansatz ist komplexer als die eines zentralisierten Ansatzes und erfordert mehr Entwicklungszeit, insbesondere wenn Probleme wie die Identifikation, wo sich der Inhalt befindet, gelöst werden müssen.
93
+
94
+ ### Ok, aber warum nicht einfach beim zentralisierten Ansatz bleiben?
95
+
96
+ Lass mich erklären, warum das problematisch für deine App sein kann:
97
+
98
+ - **Unbenutzte Daten:**
99
+ Wenn eine Seite geladen wird, lädst du häufig den Inhalt aller anderen Seiten mit. (In einer 10-seitigen App sind das 90 % unbenutzter Inhalte, die geladen werden). Lädst du ein Modal per Lazy Loading? Der i18n‑Bibliothek ist das egal — sie lädt die Strings trotzdem zuerst.
100
+ - **Performance:**
101
+ Bei jedem Re-Render wird jede einzelne deiner Komponenten mit einer riesigen JSON-Payload hydriert, was die Reaktivität deiner App mit wachsender Größe beeinträchtigt.
102
+ - **Wartung:**
103
+ Das Pflegen großer JSON-Dateien ist mühsam. Du musst zwischen Dateien springen, um eine Übersetzung einzufügen, und sicherstellen, dass keine Übersetzungen fehlen und keine **verwaisten Schlüssel (orphan keys)** zurückbleiben.
104
+ - **Design-System:**
105
+ Das führt zu Inkompatibilitäten mit Design-Systemen (z. B. einer `LoginForm`-Komponente) und erschwert die Duplizierung von Komponenten über verschiedene Apps hinweg.
106
+
107
+ **"Aber wir haben Namespaces erfunden!"**
108
+
109
+ Sicher, und das ist ein großer Fortschritt. Schauen wir uns den Vergleich der Hauptbundle-Größe eines Vite + React + React Router v7 + Intlayer-Setups an. Wir haben eine 20-seitige Anwendung simuliert.
110
+
111
+ Das erste Beispiel enthält keine pro-Locale lazy geladenen Übersetzungen und keine Namespace-Aufteilung. Das zweite enthält Content-Purging + dynamisches Laden der Übersetzungen.
112
+
113
+ | Optimiertes Bundle | Nicht optimiertes Bundle |
114
+ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
115
+ | ![nicht optimiertes Bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png?raw=true) | ![optimiertes Bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) |
116
+
117
+ Also dank Namespaces sind wir von dieser Struktur zu dieser übergegangen:
118
+
119
+ ```bash
120
+ locale/
121
+ ├── en.json
122
+ ├── fr.json
123
+ └── es.json
124
+ ```
125
+
126
+ Zu dieser:
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
+ Nun müssen Sie genau steuern, welcher Teil des App-Inhalts geladen werden soll und wo. Folglich überspringt die große Mehrheit der Projekte diesen Teil aufgrund der Komplexität (siehe beispielsweise den [next-i18next-Leitfaden](https://github.com/aymericzip/intlayer/blob/main/docs/blog/de/i18n_using_next-i18next.md), um die Herausforderungen zu sehen, die das (bloße) Befolgen guter Praktiken mit sich bringt).
144
+
145
+ Dementsprechend landen diese Projekte beim zuvor beschriebenen Problem des massiven JSON-Ladens.
146
+
147
+ > Beachten Sie, dass dieses Problem nicht spezifisch für i18next ist, sondern alle oben aufgeführten zentralisierten Ansätze betrifft.
148
+
149
+ Ich möchte daran erinnern, dass nicht alle granularen Ansätze dieses Problem lösen. Beispielsweise laden `vue-i18n SFC`- oder `inlang`-Ansätze die Übersetzungen pro Locale nicht automatisch lazy, sodass du das Bundle-Größenproblem einfach gegen ein anderes eintauschst.
150
+
151
+ Zudem wird es ohne eine saubere Separation of concerns deutlich schwieriger, deine Übersetzungen für die Überprüfung durch Übersetzer zu extrahieren und bereitzustellen.
152
+
153
+ ### Wie Intlayers komponentenbasierter Ansatz dieses Problem löst
154
+
155
+ Intlayer geht in mehreren Schritten vor:
156
+
157
+ 1. **Deklaration:** Deklariere deinen Content überall in deiner Codebase mit `*.content.{ts|jsx|cjs|json|json5|...}`-Dateien. Das stellt die Separation of concerns sicher und hält den Content colocated. Eine Content-Datei kann pro Locale oder mehrsprachig sein.
158
+ 2. **Verarbeitung:** Intlayer führt einen Build-Schritt aus, um JS-Logik zu verarbeiten, fehlende Übersetzungs-Fallbacks zu behandeln, TypeScript-Typen zu generieren, doppelte Inhalte zu verwalten, Inhalte aus Ihrem CMS abzurufen und mehr.
159
+ 3. **Bereinigung:** Beim Build Ihrer App entfernt Intlayer ungenutzte Inhalte (ähnlich wie Tailwind bei der Verwaltung Ihrer Klassen), indem der Inhalt wie folgt ersetzt wird:
160
+
161
+ **Deklaration:**
162
+
163
+ ```tsx
164
+ // src/MyComponent.tsx
165
+ export const MyComponent = () => {
166
+ const content = useIntlayer("my-key");
167
+ return <h1>{content.title}</h1>;
168
+ };
169
+ ```
170
+
171
+ ```tsx
172
+ // src/myComponent.content.ts
173
+ export const {
174
+ key: "my-key",
175
+ content: t({
176
+ de: { title: "Mein Titel" },
177
+ en: { title: "My title" },
178
+ fr: { title: "Mon titre" }
179
+ })
180
+ }
181
+
182
+ ```
183
+
184
+ **Verarbeitung:** Intlayer baut das Wörterbuch basierend auf der `.content` Datei und generiert:
185
+
186
+ ```json5
187
+ // .intlayer/dynamic_dictionary/en/my-key.json
188
+ {
189
+ "key": "my-key",
190
+ "content": { "title": "My title" },
191
+ }
192
+ ```
193
+
194
+ **Ersetzung:** Intlayer transformiert Ihre Komponente während des Build-Prozesses der Anwendung.
195
+
196
+ **- Statischer Import-Modus:**
197
+
198
+ ```tsx
199
+ // Representation of the component in JSX-like syntax
200
+ export const MyComponent = () => {
201
+ const content = useDictionary({
202
+ key: "my-key",
203
+ content: {
204
+ nodeType: "translation",
205
+ translation: {
206
+ en: { title: "My title" },
207
+ fr: { title: "Mon titre" },
208
+ },
209
+ },
210
+ });
211
+
212
+ return <h1>{content.title}</h1>;
213
+ };
214
+ ```
215
+
216
+ **- Dynamischer Import-Modus:**
217
+
218
+ ```tsx
219
+ // Representation of the component in JSX-like syntax
220
+ export const MyComponent = () => {
221
+ const content = useDictionaryAsync({
222
+ en: () =>
223
+ import(".intlayer/dynamic_dictionary/en/my-key.json", {
224
+ with: { type: "json" },
225
+ }).then((mod) => mod.default),
226
+ // Gleiches für andere Sprachen
227
+ });
228
+
229
+ return <h1>{content.title}</h1>;
230
+ };
231
+ ```
232
+
233
+ > `useDictionaryAsync` verwendet einen Suspense-ähnlichen Mechanismus, um die lokalisierte JSON nur bei Bedarf zu laden.
234
+
235
+ **Wesentliche Vorteile dieses pro-Komponenten-Ansatzes:**
236
+
237
+ - Wenn Sie Ihre Content-Deklaration nahe bei Ihren components halten, verbessert das die Wartbarkeit (z. B. das Verschieben einer Komponente in eine andere App oder ein Design-System. Das Löschen des Komponenten-Ordners entfernt ebenfalls die zugehörigen Inhalte, wie Sie es wahrscheinlich bereits für Ihre `.test`- und `.stories`-Dateien tun)
238
+
239
+ - Ein pro-Komponenten-Ansatz verhindert, dass AI-Agenten durch all Ihre verschiedenen Dateien springen müssen. Er fasst alle Übersetzungen an einem Ort zusammen und begrenzt so die Komplexität der Aufgabe sowie die Anzahl der verwendeten Tokens.
240
+
241
+ ### Einschränkungen
242
+
243
+ Natürlich bringt dieser Ansatz Kompromisse mit sich:
244
+
245
+ - Es ist schwieriger, sich mit anderen l10n-Systemen und zusätzlichem Tooling zu verbinden.
246
+ - Es entsteht ein Lock-in (was bei jeder i18n-Lösung aufgrund ihrer spezifischen Syntax im Grunde bereits der Fall ist).
247
+
248
+ Aus diesem Grund versucht Intlayer, ein vollständiges Toolset für i18n bereitzustellen (100% kostenlos und OSS), einschließlich AI-Übersetzung unter Verwendung Ihres eigenen AI-Providers und API-Keys. Intlayer stellt außerdem Tooling bereit, um Ihr JSON zu synchronisieren — dieses funktioniert wie die Message-Formatter von ICU / vue-i18n / i18next, um Inhalte in deren spezifische Formate zu überführen.