@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,252 @@
1
+ ---
2
+ createdAt: 2025-09-10
3
+ updatedAt: 2025-09-10
4
+ title: Per-Component vs. Centralized i18n: A New Approach with Intlayer
5
+ description: A deep dive into internationalization strategies in React, comparing centralized, per-key, and per-component approaches, and introducing 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 vs. Centralized i18n
19
+
20
+ I'm the creator of Intlayer, an i18n solution that takes a **per-component approach**.
21
+
22
+ The per-component approach isn't exactly new.
23
+ On the Vue side, `vue-i18n` has a ['Single File Components'](https://vue-i18n.intlify.dev/guide/advanced/sfc.html) approach.It also exists in Nuxt with per-component translations [here](https://i18n.nuxtjs.org/docs/guide/per-component-translations).
24
+
25
+ Even in a Flutter app, we can often find this pattern:
26
+
27
+ ```bash
28
+ lib/
29
+ ├── features/
30
+ │ ├── login/
31
+ │ │ ├── login_screen.dart
32
+ │ │ └── login_screen.i18n.dart <- Translations live here
33
+
34
+ ```
35
+
36
+ ```dart
37
+ import 'package:i18n_extension/i18n_extension.dart';
38
+
39
+ extension Localization on String {
40
+ static var _t = Translations("en_us") +
41
+ {
42
+ "en_us": "Hello",
43
+ "pt_br": "Olá",
44
+ "es": "Hola",
45
+ };
46
+
47
+ String get i18n => localize(this, _t);
48
+ }
49
+
50
+ ```
51
+
52
+ However, in the React world, we mainly see different approaches, that I will group in three categories:
53
+
54
+ 1. **Centralized approach** (i18next, next-intl, react-intl, lingui)
55
+ 2. **Per-component approach** (Intlayer)
56
+ 3. **Per-key approach** (inlang)
57
+
58
+ In this blog, I won't focus on compiler-based solutions, which I already covered here: [Compiler vs Declarative i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/compiler_vs_declarative_i18n.md).
59
+ Note that compiler-based i18n (e.g., Lingui) simply automates the extraction and loading of content. Under the hood, they often share the same limitations as the centralized approach.
60
+
61
+ So, what are the differences?
62
+
63
+ - **A Centralized approach** (with no namespaces) considers a single source to retrieve content. By default, you load the content from all pages when your app loads.
64
+ - **Per-component and Per-key approaches** fine-grain the content retrieval.
65
+
66
+ > Note that the more you fine-grain how you retrieve your content, the more you risk inserting additional state and logic into your components.
67
+ > For instance, a page can easily include 500 text nodes (`<p>`, `<span>`). A per-key approach will effectively insert 500 new component instances to handle the translation, each potentially using their own `useState`, `useEffect`, or `useContext`.
68
+
69
+ Here is a visualization of the trade-off:
70
+
71
+ ```bash
72
+ Count of useState / Overhead (100 blocks <p> / 10 components)
73
+ 100 | x Per-key
74
+ |
75
+ |
76
+ ...
77
+ |
78
+ |
79
+ |
80
+ |
81
+ 10 | x Per-component
82
+ |
83
+ 1 | x Centralized
84
+ |-------------------------------------------------------->
85
+ 0 900 1000
86
+ % of unused content (Based on 10 unique pages)
87
+
88
+ ```
89
+
90
+ In parallel, you should know that when you dynamically load your content (`await import()`), you end up with extra requests to your server. One extra `useState` or hook relates to one extra request to your server.
91
+
92
+ > To fix this point, Intlayer suggests grouping multiple content definitions under a same key, Intlayer will then merge that content.
93
+
94
+ ### So why is the Centralized approach so popular?
95
+
96
+ - First, i18next was the first solution to become widely used, following an approach inspired by PHP and Java, which rely on a clear separation of concerns (MVP). i18next arrive even before the trend of frontend frameworks and component based architecture.
97
+ - Then, once a library is widely adopted, it becomes difficult to shift the ecosystem to other patterns.
98
+ - Using a centralized approach also makes things easier in Translation Management Systems such as Crowdin, Phrase, or Localized.
99
+ - The logic behind a per-component approach is more complex than a centralized one and takes extra time to develop, especially when you have to solve problems like identifying where the content is located.
100
+
101
+ ### Okay, but why not just stick to a Centralized approach?
102
+
103
+ Let me tell you why it can be problematic for your app:
104
+
105
+ - **Unused Data:** When a page loads, you often load the content from all other pages. (In a 10-page app, that's 90% unused content loaded). You lazy load a modal? The i18n library doesn't care, it loads the strings first anyway.
106
+ - **Performance:** For each re-render, every single one of your components is hydrated with a massive JSON payload, which impacts your app's reactivity as it grows.
107
+ - **Maintenance:** Maintaining large JSON files is painful. You have to jump across files to insert a translation, ensuring no translations are missing and no **orphan keys** are left behind.
108
+ - **Design-system:** It creates incompatibility with design systems (e.g., a `LoginForm` component) and constrains component duplication across different apps.
109
+
110
+ **"But we invented Namespaces!"**
111
+
112
+ Sure, and that's an massive move forward. Let's look at the comparison of the main bundle size of a Vite + React + React Router v7 + Intlayer setup. We simulated a 20-page application.
113
+
114
+ The first example does not include lazy-loaded translations per locale and no namespace splitting. The second includes content purging + dynamic loading for translations.
115
+
116
+ | Optimized bundle | Bundle not optimized |
117
+ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
118
+ | ![optimized bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) | ![no optimized bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png?raw=true) |
119
+
120
+ So thanks for namespaces, we moved from this structure:
121
+
122
+ ```bash
123
+ locale/
124
+ ├── en.json
125
+ ├── fr.json
126
+ ├── es.json
127
+
128
+ ```
129
+
130
+ To this one:
131
+
132
+ ```bash
133
+ locale/
134
+ ├── en/
135
+ │ ├── common.json
136
+ │ ├── navbar.json
137
+ │ ├── footer.json
138
+ │ ├── home.json
139
+ │ ├── about.json
140
+ ├── fr/
141
+ │ ├── ...
142
+ ├── es/
143
+ │ ├── ...
144
+
145
+ ```
146
+
147
+ Now you have to finely manage what part of your app content should be loaded, and where. Conclusion, 90% of projects just skip this part due to the complexity (see [next-i18next guide](https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/i18n_using_next-i18next.md) for instance to see the challenges that represents (just) following good practices).
148
+ Consequently, 95% of projects end up with the massive JSON loading problem explained earlier.
149
+
150
+ However, not all per-component approaches solve this. For instance, `vue-i18n`'s approach does not inherently lazy load the translations, so you face the same bundle issues.
151
+ Moreover, without proper separation of concerns, it becomes much more difficult to extract and provide your translations to translators for review.
152
+
153
+ ### How Intlayer's per-component approach solves this
154
+
155
+ Intlayer proceeds in several steps:
156
+
157
+ 1. **Declaration:** Declare your content anywhere in your codebase using `.content.{ts|jsx|cjs|json|json5|...}` files. This ensures separation of concerns while keeping content colocated. A content file can be per-locale or multilingual.
158
+ 2. **Processing:** Intlayer runs a build step to process JS logic, handle missing translation fallbacks, generate TypeScript types, manage duplicated content, and fetch content from your CMS, and more.
159
+ 3. **Purging:** When your app builds, Intlayer purges unused content (a bit like how Tailwind manages your classes) by replacing the content as follows:
160
+
161
+ **Declaration:**
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
+ en: { title: "My title" },
177
+ fr: { title: "Mon titre" }
178
+ })
179
+ }
180
+
181
+ ```
182
+
183
+ **Processing:** Intlayer builds the dictionary based on the `.content` file and generates:
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
+ **Replacement:** Intlayer transforms your component during the application build.
194
+
195
+ **- Static Import Mode:**
196
+
197
+ ```tsx
198
+ // Representation of the component in JSX-like syntax
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
+ **- Dynamic Import Mode:**
216
+
217
+ ```tsx
218
+ // Representation of the component in JSX-like syntax
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
+ // Same for other languages
226
+ });
227
+
228
+ return <h1>{content.title}</h1>;
229
+ };
230
+ ```
231
+
232
+ > `useDictionaryAsync` uses a Suspense-like mechanism to load the localized JSON only when needed.
233
+
234
+ **Key benefits of this per-component approach:**
235
+
236
+ - Keeping your content declaration close to your components allows better maintainability (e.g moving a components to another app or design system. Deleting the component folder remove the related content too, as you probably already do for your `.test`, `.stories`)
237
+
238
+ - A per-component approach prevents AI agents from needing to jump across all your different files. It treats all translations in one place, limiting the complexity of the task, and the amount of tokens used.
239
+
240
+ ### Limitations
241
+
242
+ Of course, this approach comes with trade-offs:
243
+
244
+ - It is harder to connect to other l10n systems and extra tooling.
245
+ - You get locked in (which is basically already the case with any i18n solution due to their specific syntax).
246
+
247
+ That's the reason why Intlayer tries to provide a complete toolset for i18n (100% free and OSS), including AI translation using your own AI Provider and API keys. Intlayer also provides tooling to synchronize your JSON, functioning like ICU / vue-i18n / i18next message formatters to map the content to their specific formats.
248
+
249
+ ---
250
+
251
+ Is it overkill? Or will it become the next Tailwind for content?
252
+ I'm happy to take your honest feedback about it. Your objections really help me build a better product.
@@ -0,0 +1,248 @@
1
+ ---
2
+ createdAt: 2025-09-10
3
+ updatedAt: 2025-09-10
4
+ title: Per-Component vs. Centralized i18n: A New Approach with Intlayer
5
+ description: A deep dive into internationalization strategies in React, comparing centralized, per-key, and per-component approaches, and introducing 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 vs. Centralized i18n
19
+
20
+ The per-component approach is not a new concept. For example, in the Vue ecosystem, `vue-i18n` supports [Single File Component (SFC) i18n](https://vue-i18n.intlify.dev/guide/advanced/sfc.html). Nuxt also offers [per-component translations](https://i18n.nuxtjs.org/docs/guide/per-component-translations), and Angular employs a similar pattern through its [Feature Modules](https://v17.angular.io/guide/feature-modules).
21
+
22
+ Even in a Flutter app, we can often find this pattern:
23
+
24
+ ```bash
25
+ lib/
26
+ └── features/
27
+ └── login/
28
+ ├── login_screen.dart
29
+ └── login_screen.i18n.dart # <- Translations live here
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
+ However, in the React world, we mainly see different approaches, that I will group in three categories:
49
+
50
+ <Columns>
51
+ <Column>
52
+
53
+ **Centralized approach** (i18next, next-intl, react-intl, lingui)
54
+
55
+ - (with no namespaces) considers a single source to retrieve content. By default, you load the content from all pages when your app loads.
56
+
57
+ </Column>
58
+ <Column>
59
+
60
+ **Granular approach** (intlayer, inlang)
61
+
62
+ - fine-grain the content retrieval per key, or per-component.
63
+
64
+ </Column>
65
+ </Columns>
66
+
67
+ > In this blog, I won't focus on compiler-based solutions, which I already covered here: [Compiler vs Declarative i18n](https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/compiler_vs_declarative_i18n.md).
68
+ > Note that compiler-based i18n (e.g., Lingui) simply automates the extraction and loading of content. Under the hood, they often share the same limitations as others approaches.
69
+
70
+ > Note that the more you fine-grain how you retrieve your content, the more you risk inserting additional state and logic into your components.
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
+ - **More pages than languages** → Favor a granular approach
77
+ - **More languages than pages** → Lean toward a centralized approach
78
+
79
+ Of course, the library authors are aware of these limitations and provide workarounds.
80
+ Among them: splitting into namespaces, dynamically loading JSON files (`await import()`), or purging content at build time.
81
+
82
+ At the same time, you should know that when you dynamically load your content, you introduce additional requests to your server. Each extra `useState` or hook means an extra server request.
83
+
84
+ > To fix this point, Intlayer suggests grouping multiple content definitions under a same key, Intlayer will then merge that content.
85
+
86
+ But from all that solution, it's clear that the most popular approach is the centralized one.
87
+
88
+ ### So why is the Centralized approach so popular?
89
+
90
+ - First, i18next was the first solution to become widely used, following a philosophy inspired by PHP and Java architectures (MVC), which rely on a strict separation of concerns (keeping content separate from code). It arrived in 2011, establishing its standards even before the massive shift toward Component-Based Architectures (like React).
91
+ - Then, once a library is widely adopted, it becomes difficult to shift the ecosystem to other patterns.
92
+ - Using a centralized approach also makes things easier in Translation Management Systems such as Crowdin, Phrase, or Localized.
93
+ - The logic behind a per-component approach is more complex than a centralized one and takes extra time to develop, especially when you have to solve problems like identifying where the content is located.
94
+
95
+ ### Ok, but why not just stick to a Centralized approach?
96
+
97
+ Let me tell you why it can be problematic for your app:
98
+
99
+ - **Unused Data:**
100
+ When a page loads, you often load the content from all other pages. (In a 10-page app, that's 90% unused content loaded). You lazy load a modal? The i18n library doesn't care, it loads the strings first anyway.
101
+ - **Performance:**
102
+ For each re-render, every single one of your components is hydrated with a massive JSON payload, which impacts your app's reactivity as it grows.
103
+ - **Maintenance:**
104
+ Maintaining large JSON files is painful. You have to jump across files to insert a translation, ensuring no translations are missing and no **orphan keys** are left behind.
105
+ - **Design-system:**
106
+ It creates incompatibility with design systems (e.g., a `LoginForm` component) and constrains component duplication across different apps.
107
+
108
+ **"But we invented Namespaces!"**
109
+
110
+ Sure, and that's an massive move forward. Let's look at the comparison of the main bundle size of a Vite + React + React Router v7 + Intlayer setup. We simulated a 20-page application.
111
+
112
+ The first example does not include lazy-loaded translations per locale and no namespace splitting. The second includes content purging + dynamic loading for translations.
113
+
114
+ | Optimized bundle | Bundle not optimized |
115
+ | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
116
+ | ![no optimized bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle_no_optimization.png?raw=true) | ![optimized bundle](https://github.com/aymericzip/intlayer/blob/main/docs/assets/bundle.png?raw=true) |
117
+
118
+ So thanks for namespaces, we moved from this structure:
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
+ Great! But is it enough? Not really. Now you have to finely manage what part of your app content should be loaded, and where.
145
+ Conclusion, the vast majority of projects just skip this part due to the complexity (see [next-i18next guide](https://github.com/aymericzip/intlayer/blob/main/docs/blog/en/i18n_using_next-i18next.md) for instance to see the challenges that represents (just) following good practices).
146
+ Consequently, those projects end up with the massive JSON loading problem explained earlier.
147
+
148
+ > Note that this problem is not specific to i18next, but to all centralized approaches listed above.
149
+
150
+ However, I want to remember your that not all granular approaches solve this. For instance, `vue-i18n SFC`'s or `inlang` approaches do not inherently lazy load the translations per locale, so you simply trade the bundle size problem for another one.
151
+
152
+ Moreover, without proper separation of concerns, it becomes much more difficult to extract and provide your translations to translators for review.
153
+
154
+ ### How Intlayer's per-component approach solves this
155
+
156
+ Intlayer proceeds in several steps:
157
+
158
+ 1. **Declaration:** Declare your content anywhere in your codebase using `*.content.{ts|jsx|cjs|json|json5|...}` files. This ensures separation of concerns while keeping content colocated. A content file can be per-locale or multilingual.
159
+ 2. **Processing:** Intlayer runs a build step to process JS logic, handle missing translation fallbacks, generate TypeScript types, manage duplicated content, and fetch content from your CMS, and more.
160
+ 3. **Purging:** When your app builds, Intlayer purges unused content (a bit like how Tailwind manages your classes) by replacing the content as follows:
161
+
162
+ **Declaration:**
163
+
164
+ ```tsx
165
+ // src/MyComponent.tsx
166
+ export const MyComponent = () => {
167
+ const content = useIntlayer("my-key");
168
+ return <h1>{content.title}</h1>;
169
+ };
170
+ ```
171
+
172
+ ```tsx
173
+ // src/myComponent.content.ts
174
+ export const {
175
+ key: "my-key",
176
+ content: t({
177
+ en: { title: "My title" },
178
+ fr: { title: "Mon titre" }
179
+ })
180
+ }
181
+
182
+ ```
183
+
184
+ **Processing:** Intlayer builds the dictionary based on the `.content` file and generates:
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
+ **Replacement:** Intlayer transforms your component during the application build.
195
+
196
+ **- Static Import Mode:**
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
+ **- Dynamic Import Mode:**
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
+ // Same for other languages
227
+ });
228
+
229
+ return <h1>{content.title}</h1>;
230
+ };
231
+ ```
232
+
233
+ > `useDictionaryAsync` uses a Suspense-like mechanism to load the localized JSON only when needed.
234
+
235
+ **Key benefits of this per-component approach:**
236
+
237
+ - Keeping your content declaration close to your components allows better maintainability (e.g moving a components to another app or design system. Deleting the component folder remove the related content too, as you probably already do for your `.test`, `.stories`)
238
+
239
+ - A per-component approach prevents AI agents from needing to jump across all your different files. It treats all translations in one place, limiting the complexity of the task, and the amount of tokens used.
240
+
241
+ ### Limitations
242
+
243
+ Of course, this approach comes with trade-offs:
244
+
245
+ - It makes things harder to connect to other l10n systems and extra tooling.
246
+ - You get locked in (which is basically already the case with any i18n solution due to their specific syntax).
247
+
248
+ That's the reason why Intlayer tries to provide a complete toolset for i18n (100% free and OSS), including AI translation using your own AI Provider and API keys. Intlayer also provides tooling to synchronize your JSON, functioning like ICU / vue-i18n / i18next message formatters to map the content to their specific formats.