@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: Per-Component проти централізованого i18n: новий підхід з Intlayer
5
+ description: Детальний огляд стратегій інтернаціоналізації в React: порівняння централізованого, per-key і per-component підходів та презентація Intlayer.
6
+ keywords:
7
+ - i18n
8
+ - React
9
+ - Internationalization
10
+ - Intlayer
11
+ - Optimization
12
+ - Bundle Size
13
+ slugs:
14
+ - blog
15
+ - per-component-vs-centralized-i18n
16
+ ---
17
+
18
+ # Підхід per-component проти централізованого i18n
19
+
20
+ Підхід per-component не є новим поняттям. Наприклад, в екосистемі Vue `vue-i18n` підтримує [i18n SFC (Single File Component)](https://vue-i18n.intlify.dev/guide/advanced/sfc.html). Nuxt також пропонує [переклади per-component](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
+ > У цьому блозі я не зосереджуватимусь на рішеннях на основі компілятора, які я вже розглянув тут: [Компілятор проти декларативного i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/uk/compiler_vs_declarative_i18n.md).
68
+ > Зауважте, що компіляторні i18n-рішення (наприклад, Lingui) лише автоматизують витяг та завантаження контенту. Під капотом вони часто мають ті самі обмеження, що й інші підходи.
69
+
70
+ > Зауважте, що чим детальніше ви налаштовуєте спосіб отримання контенту, тим більший ризик додати додатковий стан і логіку в компоненти.
71
+
72
+ Гранулярні підходи є більш гнучкими, ніж централізовані, але часто це компроміс. Навіть якщо ці бібліотеки рекламують "tree shaking", на практиці ви часто в кінцевому підсумку завантажуєте сторінку на всіх мовах.
73
+
74
+ Отже, в загальних рисах рішення зводиться до наступного:
75
+
76
+ - Якщо в вашому застосунку більше сторінок, ніж мов, варто віддавати перевагу гранулярному підходу.
77
+ - Якщо мов більше, ніж сторінок, слід схилитися до централізованого підходу.
78
+
79
+ Звісно, автори бібліотек усвідомлюють ці обмеження і пропонують обхідні шляхи.
80
+ Серед них: розподіл на namespaces, динамічне завантаження JSON-файлів (`await import()`), або очищення контенту під час збірки.
81
+
82
+ Водночас потрібно знати, що коли ви динамічно завантажуєте свій вміст, ви вводите додаткові запити до сервера. Кожен додатковий `useState` або хук означає ще один запит до сервера.
83
+
84
+ > Щоб вирішити це питання, Intlayer пропонує групувати кілька визначень контенту під одним ключем — Intlayer потім об'єднає цей контент.
85
+
86
+ Але з-поміж усіх цих рішень очевидно, що найпопулярнішим є централізований підхід.
87
+
88
+ ### Чому ж централізований підхід такий популярний?
89
+
90
+ - По-перше, i18next було першим рішенням, яке стало широко використовуваним, і воно слідувало філософії, запозиченій із PHP та Java-архітектур (MVC), які базуються на суворому розділенні обов'язків (триманні контенту окремо від коду). Воно з'явилося в 2011 році, встановивши свої стандарти ще до масового переходу до архітектур на основі компонентів (наприклад, React).
91
+ - Далі, коли бібліотека широко прийнята, змінити екосистему на інші патерни стає складно.
92
+ - Використання централізованого підходу також спрощує роботу в Translation Management Systems, таких як Crowdin, Phrase або Localized.
93
+ - Логіка підходу per-component складніша за централізований і вимагає додаткового часу на розробку, особливо коли потрібно вирішувати задачі на кшталт визначення місця розташування контенту.
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 переклади за локалями і не використовує розбиття на неймспейси. Другий включає content purging + динамічне завантаження перекладів.
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
+ Отже, завдяки namespaces, ми перейшли від такої структури:
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/uk/i18n_using_next-i18next.md), щоб побачити виклики, які становить (лише) дотримання найкращих практик).
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. **Declaration:** Оголосіть ваш контент будь-де у кодовій базі, використовуючи файли `*.content.{ts|jsx|cjs|json|json5|...}`. Це забезпечує розділення обов'язків, зберігаючи контент поруч із компонентами. Файл контенту може бути на одну локаль або мультимовним.
158
+ 2. **Опрацювання:** Intlayer запускає крок збірки для обробки JS-логіки, обробки fallback-значень для відсутніх перекладів, генерації типів 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
+ uk: { 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
+ **Ключові переваги цього компонентного підходу:**
236
+
237
+ - Тримання оголошення контенту поруч із компонентами забезпечує кращу підтримуваність (наприклад, при переміщенні компонентів до іншого app або design system. Видалення папки компонента також видаляє пов'язаний контент, як ви, ймовірно, вже робите для ваших `.test`, `.stories`)
238
+
239
+ - Підхід на рівні компоненту запобігає необхідності AI-агентам переходити по всіх ваших різних файлах. Він обробляє всі переклади в одному місці, зменшуючи складність завдання та кількість використовуваних токенів.
240
+
241
+ ### Обмеження
242
+
243
+ Звісно, цей підхід має свої компроміси:
244
+
245
+ - Важче підключатися до інших l10n-систем та додаткових інструментів.
246
+ - Ви потрапляєте у залежність (що, по суті, вже відбувається з будь-яким i18n-рішенням через їхній специфічний синтаксис).
247
+
248
+ Саме тому Intlayer намагається надати повний набір інструментів для i18n (100% безкоштовний і з відкритим вихідним кодом — OSS), включно з AI-перекладом за допомогою вашого власного AI-провайдера та API-ключів. Intlayer також надає інструменти для синхронізації ваших JSON-файлів, що працюють подібно до message formatters ICU / vue-i18n / i18next, щоб відобразити контент у їхніх специфічних форматах.
@@ -0,0 +1,246 @@
1
+ ---
2
+ createdAt: 2025-09-10
3
+ updatedAt: 2025-09-10
4
+ title: i18n theo thành phần so với i18n tập trung: Một cách tiếp cận mới với Intlayer
5
+ description: Phân tích sâu các chiến lược quốc tế hóa trong React, so sánh các phương pháp tập trung, theo khóa và theo thành phần, và giới thiệu Intlayer.
6
+ keywords:
7
+ - i18n
8
+ - React
9
+ - Quốc tế hóa
10
+ - Intlayer
11
+ - Tối ưu hóa
12
+ - Kích thước bundle
13
+ slugs:
14
+ - blog
15
+ - per-component-vs-centralized-i18n
16
+ ---
17
+
18
+ # i18n theo thành phần so với i18n tập trung
19
+
20
+ Cách tiếp cận theo thành phần không phải là một khái niệm mới. Ví dụ, trong hệ sinh thái Vue, `vue-i18n` hỗ trợ [i18n SFC (Single File Component)](https://vue-i18n.intlify.dev/guide/advanced/sfc.html). Nuxt cũng cung cấp [bản dịch theo thành phần](https://i18n.nuxtjs.org/docs/guide/per-component-translations), và Angular sử dụng một mẫu tương tự thông qua [Feature Modules](https://v17.angular.io/guide/feature-modules) của nó.
21
+
22
+ Ngay cả trong một ứng dụng Flutter, chúng ta thường thấy mẫu sau:
23
+
24
+ ```bash
25
+ lib/
26
+ └── features/
27
+ └── login/
28
+ ├── login_screen.dart
29
+ └── login_screen.i18n.dart # <- Các bản dịch nằm ở đây
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
+ Tuy nhiên, trong thế giới React, chúng ta chủ yếu thấy các cách tiếp cận khác nhau, mà tôi sẽ gom thành ba loại:
49
+
50
+ <Columns>
51
+ <Column>
52
+
53
+ **Cách tiếp cận tập trung** (i18next, next-intl, react-intl, lingui)
54
+
55
+ - (không có namespaces) coi một nguồn duy nhất để truy xuất nội dung. Theo mặc định, bạn tải nội dung từ tất cả các trang khi ứng dụng của bạn tải.
56
+
57
+ </Column>
58
+ <Column>
59
+
60
+ **Tiếp cận chi tiết** (intlayer, inlang)
61
+
62
+ - phân nhỏ việc truy xuất nội dung theo key, hoặc theo component.
63
+
64
+ </Column>
65
+ </Columns>
66
+
67
+ > Trong blog này, tôi sẽ không tập trung vào các giải pháp dựa trên compiler, những cái tôi đã đề cập ở đây: [Compiler vs Declarative i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/vi/compiler_vs_declarative_i18n.md).
68
+ > Lưu ý rằng i18n dựa trên compiler (ví dụ: Lingui) chỉ đơn giản tự động hóa việc trích xuất và tải nội dung. Về bản chất, chúng thường chia sẻ những hạn chế tương tự với các phương pháp khác.
69
+
70
+ > Lưu ý rằng càng phân nhỏ cách bạn truy xuất nội dung, bạn càng có nguy cơ đưa thêm trạng thái và logic vào các component.
71
+
72
+ Granular approaches are more flexible than centralized ones, but it's often a tradeoff. Even if "tree shaking" is advertised by that libraries, in practice, you'll often end up loading a page in every language.
73
+
74
+ So, broadly speaking, the decision breaks down like this:
75
+
76
+ - Nếu ứng dụng của bạn có nhiều trang hơn số ngôn ngữ, bạn nên ưu tiên phương pháp granular.
77
+ - Nếu bạn có nhiều ngôn ngữ hơn trang, bạn nên nghiêng về phương pháp tập trung.
78
+
79
+ Tất nhiên, các tác giả thư viện nhận thức được những giới hạn này và cung cấp các cách khắc phục. Trong số đó: tách thành namespaces, tải động các file JSON (`await import()`), hoặc loại bỏ (purge) nội dung trong quá trình build.
80
+
81
+ Đồng thời, bạn cần biết rằng khi bạn tải nội dung một cách động, bạn sẽ tạo thêm các yêu cầu tới server. Mỗi `useState` bổ sung hoặc hook đồng nghĩa với một yêu cầu server thêm.
82
+
83
+ > Để khắc phục vấn đề này, Intlayer đề xuất gom nhiều định nghĩa nội dung dưới cùng một khóa; Intlayer sau đó sẽ hợp nhất những nội dung đó.
84
+
85
+ Nhưng từ tất cả các giải pháp đó, rõ ràng rằng cách tiếp cận phổ biến nhất là cách tập trung.
86
+
87
+ ### Vậy tại sao phương pháp tập trung lại được ưa chuộng đến vậy?
88
+
89
+ - Trước hết, i18next là giải pháp đầu tiên được sử dụng rộng rãi, theo triết lý lấy cảm hứng từ các kiến trúc PHP và Java (MVC), vốn dựa trên nguyên tắc tách biệt trách nhiệm nghiêm ngặt (giữ nội dung tách khỏi mã). Nó xuất hiện vào năm 2011, thiết lập các tiêu chuẩn của mình thậm chí trước cả khi có sự dịch chuyển mạnh mẽ sang kiến trúc dựa trên component (Component-Based Architectures) như React.
90
+ - Sau đó, một khi một thư viện được chấp nhận rộng rãi, sẽ rất khó để chuyển cả hệ sinh thái sang các mô hình khác.
91
+ - Việc sử dụng cách tiếp cận tập trung cũng khiến các công việc trong các hệ thống quản lý bản dịch như Crowdin, Phrase hoặc Localized trở nên dễ dàng hơn.
92
+ - Logic đằng sau cách tiếp cận theo từng component phức tạp hơn so với cách tiếp cận tập trung và tốn thêm thời gian để phát triển, đặc biệt khi phải giải quyết các vấn đề như xác định nội dung nằm ở đâu.
93
+
94
+ ### Được, nhưng tại sao không chỉ gắn bó với cách tiếp cận tập trung?
95
+
96
+ Hãy để tôi nói lý do điều đó có thể gây vấn đề cho ứng dụng của bạn:
97
+
98
+ - **Dữ liệu không sử dụng:**
99
+ Khi một trang được tải, bạn thường tải luôn nội dung từ tất cả các trang khác. (Trong một ứng dụng 10 trang, đó là 90% nội dung bị tải nhưng không dùng). Bạn lazy-load một modal? Thư viện i18n cũng mặc kệ — nó vẫn tải các chuỗi lên trước.
100
+ - **Hiệu năng:**
101
+ Mỗi lần re-render, từng component của bạn đều được hydrated với một payload JSON lớn, điều này ảnh hưởng đến tính phản ứng (reactivity) của app khi nó phát triển.
102
+ - **Bảo trì:**
103
+ Quản lý các file JSON lớn rất đau đầu. Bạn phải nhảy giữa các file để chèn bản dịch, đảm bảo không thiếu bản dịch nào và không để lại các **khóa mồ côi (orphan keys)**.
104
+ - **Hệ thống thiết kế:**
105
+ Điều đó tạo ra sự không tương thích với design systems (ví dụ: một component `LoginForm`) và hạn chế việc sao chép component giữa các ứng dụng khác nhau.
106
+
107
+ **"Nhưng chúng ta đã phát minh ra Namespaces!"**
108
+
109
+ Chắc chắn, và đó là một bước tiến lớn. Hãy xem so sánh kích thước main bundle của một cấu hình Vite + React + React Router v7 + Intlayer. Chúng tôi đã mô phỏng một ứng dụng 20 trang.
110
+
111
+ Ví dụ đầu tiên không bao gồm việc lazy-load các bản dịch theo locale và không tách namespace. Ví dụ thứ hai bao gồm content purging + tải động các bản dịch.
112
+
113
+ | Bundle tối ưu hóa | Bundle không tối ưu hóa |
114
+ | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
115
+ | ![gói chưa được tối ưu](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png?raw=true) | ![gói được tối ưu](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) |
116
+
117
+ Nhờ có namespaces, chúng ta đã chuyển từ cấu trúc này:
118
+
119
+ ```bash
120
+ locale/
121
+ ├── en.json
122
+ ├── fr.json
123
+ └── es.json
124
+ ```
125
+
126
+ sang cấu trúc này:
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
+ Bây giờ bạn phải quản lý một cách chi tiết phần nội dung nào của ứng dụng nên được tải và ở đâu. Kết luận là đại đa số các dự án chỉ bỏ qua phần này vì tính phức tạp (xem hướng dẫn [next-i18next](https://github.com/aymericzip/intlayer/blob/main/docs/blog/vi/i18n_using_next-i18next.md) để thấy các thách thức mà việc (chỉ) tuân theo các best practices mang lại).
144
+ Do đó, các dự án đó cuối cùng gặp phải vấn đề tải JSON khổng lồ đã được giải thích ở phần trước.
145
+
146
+ > Lưu ý rằng vấn đề này không đặc thù cho i18next, mà áp dụng cho tất cả các phương pháp tập trung được liệt kê ở trên.
147
+
148
+ Tuy nhiên, tôi muốn nhắc bạn rằng không phải mọi cách tiếp cận theo hướng phân mảnh đều giải quyết được vấn đề này. Ví dụ, các cách tiếp cận như `vue-i18n SFC` hay `inlang` không tự động lazy load bản dịch theo từng locale, nên bạn chỉ đang đánh đổi vấn đề kích thước bundle này sang một vấn đề khác.
149
+
150
+ Hơn nữa, nếu không tách biệt rõ ràng các mối quan tâm (separation of concerns), việc trích xuất và cung cấp bản dịch cho người dịch để họ xem xét sẽ trở nên khó khăn hơn nhiều.
151
+
152
+ ### Cách tiếp cận per-component của Intlayer giải quyết vấn đề này
153
+
154
+ Intlayer thực hiện theo một số bước:
155
+
156
+ 1. **Khai báo:** Khai báo nội dung ở bất kỳ đâu trong codebase của bạn bằng các file `*.content.{ts|jsx|cjs|json|json5|...}`. Điều này đảm bảo tách biệt các mối quan tâm trong khi vẫn giữ nội dung được colocated. Một file nội dung có thể là theo từng locale hoặc đa ngôn ngữ.
157
+ 2. **Xử lý:** Intlayer thực hiện một bước build để xử lý logic JS, xử lý các fallback cho bản dịch bị thiếu, sinh các kiểu TypeScript, quản lý nội dung trùng lặp, lấy nội dung từ CMS của bạn, và nhiều thứ khác.
158
+ 3. **Thanh lọc:** Khi ứng dụng của bạn được build, Intlayer sẽ loại bỏ nội dung không dùng (tương tự cách Tailwind quản lý các class) bằng cách thay thế nội dung như sau:
159
+
160
+ **Khai báo:**
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
+ **Xử lý:** Intlayer xây dựng dictionary dựa trên file `.content` và sinh ra:
183
+
184
+ ```json5
185
+ // .intlayer/dynamic_dictionary/en/my-key.json
186
+ {
187
+ "key": "my-key",
188
+ "content": { "title": "My title" },
189
+ }
190
+ ```
191
+
192
+ **Thay thế:** Intlayer biến đổi component của bạn trong quá trình build ứng dụng.
193
+
194
+ **- Chế độ Import Tĩnh:**
195
+
196
+ ```tsx
197
+ // Biểu diễn component theo cú pháp tương tự JSX
198
+ export const MyComponent = () => {
199
+ const content = useDictionary({
200
+ key: "my-key",
201
+ content: {
202
+ nodeType: "translation",
203
+ translation: {
204
+ en: { title: "My title" },
205
+ fr: { title: "Mon titre" },
206
+ },
207
+ },
208
+ });
209
+
210
+ return <h1>{content.title}</h1>;
211
+ };
212
+ ```
213
+
214
+ **- Chế độ Import Động:**
215
+
216
+ ```tsx
217
+ // Biểu diễn component theo cú pháp tương tự 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
+ // Tương tự cho các ngôn ngữ khác
225
+ });
226
+
227
+ return <h1>{content.title}</h1>;
228
+ };
229
+ ```
230
+
231
+ > `useDictionaryAsync` sử dụng cơ chế giống Suspense để chỉ tải JSON đã được địa phương hóa khi cần.
232
+
233
+ **Lợi ích chính của cách tiếp cận theo thành phần này:**
234
+
235
+ - Giữ khai báo nội dung gần với các components của bạn giúp việc bảo trì tốt hơn (ví dụ: di chuyển một component sang một app hoặc design system khác. Xóa thư mục component sẽ xóa luôn nội dung liên quan, giống như bạn có lẽ vẫn làm với các file `.test`, `.stories`)
236
+
237
+ - Cách tiếp cận theo từng component ngăn các agent AI phải lục qua tất cả các file khác nhau của bạn. Nó xử lý tất cả các bản dịch tại một chỗ, giảm độ phức tạp của nhiệm vụ và lượng token sử dụng.
238
+
239
+ ### Hạn chế
240
+
241
+ Tất nhiên, cách tiếp cận này đi kèm với những đánh đổi:
242
+
243
+ - Khó kết nối với các hệ thống l10n khác và các tooling bổ sung.
244
+ - Bạn có nguy cơ bị lock-in (điều này cơ bản đã xảy ra với bất kỳ giải pháp i18n nào do cú pháp đặc thù của chúng).
245
+
246
+ Đó là lý do Intlayer cố gắng cung cấp một bộ công cụ hoàn chỉnh cho i18n (100% miễn phí và OSS), bao gồm dịch AI sử dụng AI Provider và API keys của riêng bạn. Intlayer cũng cung cấp công cụ để đồng bộ hóa JSON của bạn, hoạt động giống như các message formatter của ICU / vue-i18n / i18next để ánh xạ nội dung sang định dạng tương ứng.