@intlayer/docs 8.9.4 → 8.9.6-canary.0
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.
- package/docs/ar/benchmark/index.md +0 -3
- package/docs/ar/benchmark/nextjs.md +15 -6
- package/docs/ar/benchmark/solid.md +155 -0
- package/docs/ar/benchmark/svelte.md +148 -0
- package/docs/ar/benchmark/tanstack.md +12 -3
- package/docs/ar/benchmark/vue.md +160 -0
- package/docs/ar/configuration.md +16 -12
- package/docs/ar/dictionary/content_file.md +51 -1
- package/docs/ar/plugins/sync-po.md +0 -21
- package/docs/bn/configuration.md +16 -12
- package/docs/cs/configuration.md +16 -12
- package/docs/de/benchmark/index.md +0 -3
- package/docs/de/benchmark/nextjs.md +15 -6
- package/docs/de/benchmark/solid.md +155 -0
- package/docs/de/benchmark/svelte.md +148 -0
- package/docs/de/benchmark/tanstack.md +12 -3
- package/docs/de/benchmark/vue.md +160 -0
- package/docs/de/configuration.md +16 -12
- package/docs/de/dictionary/content_file.md +52 -2
- package/docs/de/plugins/sync-po.md +0 -22
- package/docs/en/benchmark/nextjs.md +11 -2
- package/docs/en/benchmark/solid.md +22 -4
- package/docs/en/benchmark/svelte.md +17 -5
- package/docs/en/benchmark/tanstack.md +18 -3
- package/docs/en/benchmark/vue.md +17 -11
- package/docs/en/configuration.md +16 -13
- package/docs/en/dictionary/content_file.md +51 -1
- package/docs/en/plugins/sync-po.md +0 -21
- package/docs/en-GB/benchmark/index.md +0 -3
- package/docs/en-GB/benchmark/nextjs.md +15 -6
- package/docs/en-GB/benchmark/solid.md +155 -0
- package/docs/en-GB/benchmark/svelte.md +148 -0
- package/docs/en-GB/benchmark/tanstack.md +12 -3
- package/docs/en-GB/benchmark/vue.md +160 -0
- package/docs/en-GB/configuration.md +15 -11
- package/docs/en-GB/dictionary/content_file.md +51 -1
- package/docs/en-GB/plugins/sync-po.md +0 -21
- package/docs/es/benchmark/index.md +0 -3
- package/docs/es/benchmark/nextjs.md +15 -6
- package/docs/es/benchmark/solid.md +155 -0
- package/docs/es/benchmark/svelte.md +148 -0
- package/docs/es/benchmark/tanstack.md +12 -3
- package/docs/es/benchmark/vue.md +160 -0
- package/docs/es/configuration.md +16 -12
- package/docs/es/dictionary/content_file.md +51 -1
- package/docs/es/plugins/sync-po.md +0 -21
- package/docs/fr/benchmark/index.md +0 -3
- package/docs/fr/benchmark/nextjs.md +15 -6
- package/docs/fr/benchmark/solid.md +155 -0
- package/docs/fr/benchmark/svelte.md +148 -0
- package/docs/fr/benchmark/tanstack.md +12 -3
- package/docs/fr/benchmark/vue.md +160 -0
- package/docs/fr/configuration.md +16 -12
- package/docs/fr/dictionary/content_file.md +51 -1
- package/docs/fr/plugins/sync-po.md +0 -21
- package/docs/hi/benchmark/nextjs.md +15 -6
- package/docs/hi/benchmark/solid.md +155 -0
- package/docs/hi/benchmark/svelte.md +148 -0
- package/docs/hi/benchmark/tanstack.md +12 -3
- package/docs/hi/benchmark/vue.md +160 -0
- package/docs/hi/configuration.md +16 -12
- package/docs/hi/dictionary/content_file.md +51 -1
- package/docs/hi/plugins/sync-po.md +0 -21
- package/docs/id/benchmark/index.md +0 -3
- package/docs/id/benchmark/nextjs.md +15 -6
- package/docs/id/benchmark/solid.md +155 -0
- package/docs/id/benchmark/svelte.md +148 -0
- package/docs/id/benchmark/tanstack.md +12 -3
- package/docs/id/benchmark/vue.md +160 -0
- package/docs/id/configuration.md +16 -12
- package/docs/id/dictionary/content_file.md +51 -1
- package/docs/id/plugins/sync-po.md +0 -21
- package/docs/it/benchmark/index.md +1 -4
- package/docs/it/benchmark/nextjs.md +15 -6
- package/docs/it/benchmark/solid.md +155 -0
- package/docs/it/benchmark/svelte.md +148 -0
- package/docs/it/benchmark/tanstack.md +12 -3
- package/docs/it/benchmark/vue.md +160 -0
- package/docs/it/configuration.md +16 -12
- package/docs/it/dictionary/content_file.md +51 -1
- package/docs/it/plugins/sync-po.md +0 -21
- package/docs/ja/benchmark/index.md +5 -5
- package/docs/ja/benchmark/nextjs.md +15 -6
- package/docs/ja/benchmark/solid.md +155 -0
- package/docs/ja/benchmark/svelte.md +148 -0
- package/docs/ja/benchmark/tanstack.md +12 -3
- package/docs/ja/benchmark/vue.md +160 -0
- package/docs/ja/configuration.md +16 -12
- package/docs/ja/dictionary/content_file.md +50 -2
- package/docs/ja/intlayer_with_nextjs_no_locale_path.md +4 -3
- package/docs/ja/plugins/sync-po.md +0 -21
- package/docs/ko/benchmark/nextjs.md +15 -6
- package/docs/ko/benchmark/solid.md +155 -0
- package/docs/ko/benchmark/svelte.md +148 -0
- package/docs/ko/benchmark/tanstack.md +12 -3
- package/docs/ko/benchmark/vue.md +160 -0
- package/docs/ko/configuration.md +16 -12
- package/docs/ko/dictionary/content_file.md +51 -1
- package/docs/ko/intlayer_with_nextjs_no_locale_path.md +3 -2
- package/docs/ko/plugins/sync-po.md +0 -21
- package/docs/nl/configuration.md +16 -12
- package/docs/pl/benchmark/index.md +0 -3
- package/docs/pl/benchmark/nextjs.md +15 -6
- package/docs/pl/benchmark/solid.md +155 -0
- package/docs/pl/benchmark/svelte.md +148 -0
- package/docs/pl/benchmark/tanstack.md +12 -3
- package/docs/pl/benchmark/vue.md +160 -0
- package/docs/pl/configuration.md +16 -12
- package/docs/pl/dictionary/content_file.md +51 -1
- package/docs/pl/plugins/sync-po.md +0 -21
- package/docs/pt/benchmark/index.md +0 -3
- package/docs/pt/benchmark/nextjs.md +16 -7
- package/docs/pt/benchmark/solid.md +155 -0
- package/docs/pt/benchmark/svelte.md +148 -0
- package/docs/pt/benchmark/tanstack.md +13 -4
- package/docs/pt/benchmark/vue.md +160 -0
- package/docs/pt/configuration.md +16 -12
- package/docs/pt/dictionary/content_file.md +51 -1
- package/docs/pt/plugins/sync-po.md +0 -21
- package/docs/ru/benchmark/nextjs.md +15 -6
- package/docs/ru/benchmark/solid.md +155 -0
- package/docs/ru/benchmark/svelte.md +148 -0
- package/docs/ru/benchmark/tanstack.md +12 -3
- package/docs/ru/benchmark/vue.md +160 -0
- package/docs/ru/configuration.md +16 -12
- package/docs/ru/dictionary/content_file.md +52 -2
- package/docs/ru/plugins/sync-po.md +0 -21
- package/docs/tr/benchmark/index.md +0 -3
- package/docs/tr/benchmark/nextjs.md +15 -6
- package/docs/tr/benchmark/solid.md +155 -0
- package/docs/tr/benchmark/svelte.md +148 -0
- package/docs/tr/benchmark/tanstack.md +12 -3
- package/docs/tr/benchmark/vue.md +160 -0
- package/docs/tr/configuration.md +16 -12
- package/docs/tr/dictionary/content_file.md +51 -1
- package/docs/tr/plugins/sync-po.md +0 -21
- package/docs/uk/benchmark/nextjs.md +15 -6
- package/docs/uk/benchmark/solid.md +155 -0
- package/docs/uk/benchmark/svelte.md +148 -0
- package/docs/uk/benchmark/tanstack.md +12 -3
- package/docs/uk/benchmark/vue.md +160 -0
- package/docs/uk/configuration.md +16 -12
- package/docs/uk/dictionary/content_file.md +51 -1
- package/docs/uk/plugins/sync-po.md +0 -21
- package/docs/ur/configuration.md +16 -12
- package/docs/vi/benchmark/index.md +0 -3
- package/docs/vi/benchmark/nextjs.md +15 -6
- package/docs/vi/benchmark/solid.md +155 -0
- package/docs/vi/benchmark/svelte.md +148 -0
- package/docs/vi/benchmark/tanstack.md +12 -3
- package/docs/vi/benchmark/vue.md +160 -0
- package/docs/vi/configuration.md +16 -12
- package/docs/vi/dictionary/content_file.md +51 -1
- package/docs/vi/intlayer_with_nextjs_15.md +10 -57
- package/docs/vi/plugins/sync-po.md +0 -21
- package/docs/zh/benchmark/nextjs.md +15 -6
- package/docs/zh/benchmark/solid.md +155 -0
- package/docs/zh/benchmark/svelte.md +148 -0
- package/docs/zh/benchmark/tanstack.md +12 -3
- package/docs/zh/benchmark/vue.md +160 -0
- package/docs/zh/configuration.md +16 -12
- package/docs/zh/dictionary/content_file.md +51 -3
- package/docs/zh/plugins/sync-po.md +0 -21
- package/frequent_questions/ar/intlayerNode.md +3 -3
- package/frequent_questions/de/intlayerNode.md +3 -3
- package/frequent_questions/en/intlayerNode.md +3 -3
- package/frequent_questions/en-GB/intlayerNode.md +3 -3
- package/frequent_questions/es/intlayerNode.md +3 -3
- package/frequent_questions/fr/intlayerNode.md +3 -3
- package/frequent_questions/hi/intlayerNode.md +3 -3
- package/frequent_questions/id/intlayerNode.md +3 -3
- package/frequent_questions/it/intlayerNode.md +3 -3
- package/frequent_questions/ja/intlayerNode.md +3 -3
- package/frequent_questions/ko/intlayerNode.md +3 -3
- package/frequent_questions/pl/intlayerNode.md +3 -3
- package/frequent_questions/pt/intlayerNode.md +3 -3
- package/frequent_questions/ru/intlayerNode.md +3 -3
- package/frequent_questions/tr/intlayerNode.md +3 -3
- package/frequent_questions/uk/intlayerNode.md +3 -3
- package/frequent_questions/vi/intlayerNode.md +3 -3
- package/frequent_questions/zh/intlayerNode.md +3 -3
- package/package.json +8 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
createdAt: 2025-02-07
|
|
3
|
-
updatedAt: 2026-
|
|
3
|
+
updatedAt: 2026-05-12
|
|
4
4
|
title: Content File
|
|
5
5
|
description: Learn how to customize the extensions for your content declaration files. Follow this documentation to implement conditions efficiently in your project.
|
|
6
6
|
keywords:
|
|
@@ -12,6 +12,9 @@ slugs:
|
|
|
12
12
|
- concept
|
|
13
13
|
- content
|
|
14
14
|
history:
|
|
15
|
+
- version: 8.9.0
|
|
16
|
+
date: 2026-05-12
|
|
17
|
+
changes: "Add `plural` content node type"
|
|
15
18
|
- version: 8.0.0
|
|
16
19
|
date: 2026-01-28
|
|
17
20
|
changes: "Add `html` content node type"
|
|
@@ -69,6 +72,7 @@ import { type ReactNode } from "react";
|
|
|
69
72
|
import {
|
|
70
73
|
t,
|
|
71
74
|
enu,
|
|
75
|
+
plural,
|
|
72
76
|
cond,
|
|
73
77
|
nest,
|
|
74
78
|
md,
|
|
@@ -88,6 +92,7 @@ interface Content {
|
|
|
88
92
|
};
|
|
89
93
|
multilingualContent: string;
|
|
90
94
|
quantityContent: string;
|
|
95
|
+
pluralContent: string;
|
|
91
96
|
conditionalContent: string;
|
|
92
97
|
markdownContent: never;
|
|
93
98
|
htmlContent: never;
|
|
@@ -123,6 +128,10 @@ export default {
|
|
|
123
128
|
">5": "Some cars",
|
|
124
129
|
">19": "Many cars",
|
|
125
130
|
}),
|
|
131
|
+
pluralContent: plural({
|
|
132
|
+
one: "One car",
|
|
133
|
+
other: "{{count}} cars",
|
|
134
|
+
}),
|
|
126
135
|
conditionalContent: cond({
|
|
127
136
|
true: "Validation is enabled",
|
|
128
137
|
false: "Validation is disabled",
|
|
@@ -178,6 +187,13 @@ export default {
|
|
|
178
187
|
">19": "Many cars",
|
|
179
188
|
},
|
|
180
189
|
},
|
|
190
|
+
"pluralContent": {
|
|
191
|
+
"nodeType": "plural",
|
|
192
|
+
"plural": {
|
|
193
|
+
"one": "One car",
|
|
194
|
+
"other": "{{count}} cars",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
181
197
|
"conditionalContent": {
|
|
182
198
|
"nodeType": "condition",
|
|
183
199
|
"condition": {
|
|
@@ -233,6 +249,7 @@ Intlayer supports various content types through typed nodes:
|
|
|
233
249
|
- **Translation Content**: Multilingual text with locale-specific values [see Translation Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/translation_content.md)
|
|
234
250
|
- **Condition Content**: Conditional content based on boolean expressions [see Condition Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/condition_content.md)
|
|
235
251
|
- **Enumeration Content**: Content that varies based on enumerated values [see Enumeration Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/enumeration_content.md)
|
|
252
|
+
- **Plural Content**: Content that varies based on plural rules [see Plural Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/plural.md)
|
|
236
253
|
- **Insertion Content**: Content that can be inserted into other content [see Insertion Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/insertion_content.md)
|
|
237
254
|
- **Markdown Content**: Rich text content in Markdown format [see Markdown Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/markdown_content.md)
|
|
238
255
|
- **HTML Content**: Rich HTML content with optional custom components [see HTML Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/html.md)
|
|
@@ -547,6 +564,8 @@ multilingualContent: t({
|
|
|
547
564
|
});
|
|
548
565
|
```
|
|
549
566
|
|
|
567
|
+
> See [Translation Doc](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/translation.md) for more information.
|
|
568
|
+
|
|
550
569
|
### Condition Content (`cond`)
|
|
551
570
|
|
|
552
571
|
Content that changes based on boolean conditions:
|
|
@@ -560,6 +579,23 @@ conditionalContent: cond({
|
|
|
560
579
|
});
|
|
561
580
|
```
|
|
562
581
|
|
|
582
|
+
> See [Condition Doc](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/condition.md) for more information.
|
|
583
|
+
|
|
584
|
+
### Plural Content (`plural`)
|
|
585
|
+
|
|
586
|
+
Content that varies based on plural rules:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
import { plural } from "intlayer";
|
|
590
|
+
|
|
591
|
+
pluralContent: plural({
|
|
592
|
+
one: "One car",
|
|
593
|
+
other: "{{count}} cars",
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
> See [Plural Doc](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/plural.md) for more information.
|
|
598
|
+
|
|
563
599
|
### Enumeration Content (`enu`)
|
|
564
600
|
|
|
565
601
|
Content that varies based on enumerated values:
|
|
@@ -574,6 +610,8 @@ statusContent: enu({
|
|
|
574
610
|
});
|
|
575
611
|
```
|
|
576
612
|
|
|
613
|
+
> See [Enumeration Doc](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/enumeration.md) for more information.
|
|
614
|
+
|
|
577
615
|
### Insertion Content (`insert`)
|
|
578
616
|
|
|
579
617
|
Content that can be inserted into other content:
|
|
@@ -584,6 +622,8 @@ import { insert } from "intlayer";
|
|
|
584
622
|
insertionContent: insert("This text can be inserted anywhere");
|
|
585
623
|
```
|
|
586
624
|
|
|
625
|
+
> See [Insertion Doc](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/insertion.md) for more information.
|
|
626
|
+
|
|
587
627
|
### Nested Content (`nest`)
|
|
588
628
|
|
|
589
629
|
References to other dictionaries:
|
|
@@ -594,6 +634,8 @@ import { nest } from "intlayer";
|
|
|
594
634
|
nestedContent: nest("about-page");
|
|
595
635
|
```
|
|
596
636
|
|
|
637
|
+
> See [Nested Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/nesting.md) for more information.
|
|
638
|
+
|
|
597
639
|
### Markdown Content (`md`)
|
|
598
640
|
|
|
599
641
|
Rich text content in Markdown format:
|
|
@@ -611,6 +653,8 @@ localizedMarkdownContent: t({
|
|
|
611
653
|
});
|
|
612
654
|
```
|
|
613
655
|
|
|
656
|
+
> See [Markdown Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/markdown.md) for more information.
|
|
657
|
+
|
|
614
658
|
### HTML Content (`html`)
|
|
615
659
|
|
|
616
660
|
Rich HTML content that can use standard tags or custom components:
|
|
@@ -628,6 +672,8 @@ localizedHtmlContent: t({
|
|
|
628
672
|
});
|
|
629
673
|
```
|
|
630
674
|
|
|
675
|
+
> See [HTML Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/html.md) for more information.
|
|
676
|
+
|
|
631
677
|
### Gender Content (`gender`)
|
|
632
678
|
|
|
633
679
|
Content that varies based on gender:
|
|
@@ -642,6 +688,8 @@ genderContent: gender({
|
|
|
642
688
|
});
|
|
643
689
|
```
|
|
644
690
|
|
|
691
|
+
> See [Gender Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/gender.md) for more information.
|
|
692
|
+
|
|
645
693
|
### File Content (`file`)
|
|
646
694
|
|
|
647
695
|
References to external files:
|
|
@@ -652,6 +700,8 @@ import { file } from "intlayer";
|
|
|
652
700
|
fileContent: file("./path/to/content.txt");
|
|
653
701
|
```
|
|
654
702
|
|
|
703
|
+
> See [File Content](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/dictionary/file.md) for more information.
|
|
704
|
+
|
|
655
705
|
## Creating Content Files
|
|
656
706
|
|
|
657
707
|
### Basic Content File Structure
|
|
@@ -160,30 +160,9 @@ syncPO({
|
|
|
160
160
|
source: ({ key, locale }) => string, // required
|
|
161
161
|
location?: string, // optional label, default: "sync-po::path/to/source"
|
|
162
162
|
priority?: number, // optional priority for conflict resolution, default: 0
|
|
163
|
-
format?: 'icu' | 'i18next' | 'vue-i18n', // optional, only needed when your msgstr values use a specific interpolation syntax
|
|
164
163
|
});
|
|
165
164
|
```
|
|
166
165
|
|
|
167
|
-
#### `format` ('icu' | 'i18next' | 'vue-i18n')
|
|
168
|
-
|
|
169
|
-
PO files are always Gettext Portable Object files — that is fixed. This option only describes the **interpolation syntax** used inside the `msgstr` values, so Intlayer can convert them to its own format at parse time (via `formatDictionary`) and back when writing output.
|
|
170
|
-
|
|
171
|
-
- `undefined` _(default)_: `msgstr` values are treated as plain strings — no transformation. Use this for most PO files.
|
|
172
|
-
- `'icu'`: `msgstr` values use ICU message syntax (e.g. `{count, plural, one {# item} other {# items}}`).
|
|
173
|
-
- `'i18next'`: `msgstr` values use i18next interpolation syntax (e.g. `{{variable}}`).
|
|
174
|
-
- `'vue-i18n'`: `msgstr` values use Vue I18n syntax.
|
|
175
|
-
|
|
176
|
-
> Transformation is applied by `@intlayer/chokidar`'s `formatDictionary` on load, and reversed with `formatDictionaryOutput` on write. For complex rules like ICU plurals, round-trip fidelity is not guaranteed.
|
|
177
|
-
|
|
178
|
-
**Example — PO files contain i18next-style interpolation:**
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
syncPO({
|
|
182
|
-
source: ({ key, locale }) => `./locales/${locale}/${key}.po`,
|
|
183
|
-
format: "i18next",
|
|
184
|
-
}),
|
|
185
|
-
```
|
|
186
|
-
|
|
187
166
|
### Multiple PO sources and priority
|
|
188
167
|
|
|
189
168
|
You can add multiple `syncPO` plugins to synchronize different PO sources. This is useful when you have multiple translation sources or different PO structures in your project.
|
|
@@ -30,6 +30,3 @@ Find the detailed reports and technical documentation for each framework below:
|
|
|
30
30
|
- [**Vue Benchmark Report**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/vue.md)
|
|
31
31
|
- [**Solid Benchmark Report**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/solid.md)
|
|
32
32
|
- [**Svelte Benchmark Report**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/svelte.md)
|
|
33
|
-
- [**Vue Benchmark Report**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/vue.md)
|
|
34
|
-
- [**Solid Benchmark Report**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/solid.md)
|
|
35
|
-
- [**Svelte Benchmark Report**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/en/benchmark/svelte.md)
|
|
@@ -61,6 +61,13 @@ Because the problem is hard, many solutions exist—some focused on DX, others o
|
|
|
61
61
|
|
|
62
62
|
Intlayer tries to optimise across these dimensions.
|
|
63
63
|
|
|
64
|
+
## TL;DR
|
|
65
|
+
|
|
66
|
+
- **Intlayer** & **next-translate**: Best choices for Next.js performance, offering the smallest footprint and best static rendering support.
|
|
67
|
+
- **next-intl**: The trendiest option, but heavy and complex to optimise for large applications.
|
|
68
|
+
- **next-i18next**: Popular and plugin-rich, but carries significant bundle weight (~3× Intlayer).
|
|
69
|
+
- **Avoid**: **gt-next** and **lingo.dev** due to severe performance issues, vendor lock-in, and build-breaking bugs.
|
|
70
|
+
|
|
64
71
|
## Test your app
|
|
65
72
|
|
|
66
73
|
To surface these issues, I built a free scanner you can try [here](https://intlayer.org/i18n-seo-scanner).
|
|
@@ -99,14 +106,14 @@ Finally, `Intlayer` applies a build-time optimisation so `useIntlayer('my-key')`
|
|
|
99
106
|
For this benchmark, we compared the following libraries:
|
|
100
107
|
|
|
101
108
|
- `Base App` (No i18n library)
|
|
102
|
-
- `next-intlayer` (v8.7.
|
|
109
|
+
- `next-intlayer` (v8.7.12)
|
|
103
110
|
- `next-i18next` (v16.0.5)
|
|
104
111
|
- `next-intl` (v4.9.1)
|
|
105
112
|
- `@lingui/core` (v5.3.0)
|
|
106
113
|
- `next-translate` (v3.1.2)
|
|
107
114
|
- `next-international` (v1.3.1)
|
|
108
115
|
- `@inlang/paraglide-js` (v2.15.1)
|
|
109
|
-
-
|
|
116
|
+
- `@tolgee/react` (v7.0.0)
|
|
110
117
|
- `@lingo.dev/compiler` (v0.4.0)
|
|
111
118
|
- `wuchale` (v0.22.11)
|
|
112
119
|
- `gt-next` (v6.16.5)
|
|
@@ -161,10 +168,10 @@ Issues encountered:
|
|
|
161
168
|
|
|
162
169
|
**(General Translation)** (`gt-next@6.16.5`):
|
|
163
170
|
|
|
164
|
-
- For a 110kb app, `gt-
|
|
171
|
+
- For a 110kb app, `gt-next` adds more than 440kb extra.
|
|
165
172
|
- `Quota Exceeded, please upgrade your plan` on the very first build with General Translation.
|
|
166
173
|
- Translations are not rendered; I get the error `Error: <T> used on the client-side outside of <GTProvider>`, which seems to be a bug in the library.
|
|
167
|
-
- While implementing **gt-
|
|
174
|
+
- While implementing **gt-next**, I also came across an [issue](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) with the library: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, which was making the application break. After reporting this issue, the maintainer fixed it within 24 hours.
|
|
168
175
|
- The library blocks static rendering of Next.js pages.
|
|
169
176
|
|
|
170
177
|
**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
|
|
@@ -187,9 +194,11 @@ Personally I dislike having to regenerate JS files before every push, which crea
|
|
|
187
194
|
Even if in theory the tree-shaking strategy works, it does includes all locales in the bundle anyway. Paraglide offers not way to lazy-load the content. That says your page size will grow up correlated to the number of locales you have.
|
|
188
195
|
Finally, in comparison of other solutions, Paraglide does not use store (e.g. React context) to retrieve the current locale to render the content. For each node parsed, it will request the locale from the localeStorage / cookie etc. It leads to execution of unnecessary logic that impact the component reactivity.
|
|
189
196
|
|
|
197
|
+
> Note on paraglide: the solution injects code into your codebase for import, as a result the 'lib size' metric in the benchmark report is nearly 0. Code generation is a good thing, because the function used will include only the necessary logic (all prefixes vs no prefixes, cookies vs storage, etc.). In comparison, Intlayer performs this filtering via environment variable injections in the build to force the bundler to tree-shake content depending on the logic. Thanks to this, paraglide and intlayer end up being 6 to 10 times lighter solutions than i18next or next-intl.
|
|
198
|
+
|
|
190
199
|
### 3 — Acceptable solutions
|
|
191
200
|
|
|
192
|
-
**(Tolgee)** (
|
|
201
|
+
**(Tolgee)** (`@tolgee/react@7.0.0`):
|
|
193
202
|
|
|
194
203
|
`Tolgee` addresses many of the issues mentioned earlier. I found it harder to adopt than similar tools. It does not provide type safety, which also makes catching missing keys at compile time harder. I had to wrap Tolgee’s functions with my own to add missing-key detection.
|
|
195
204
|
|
|
@@ -217,7 +226,7 @@ Message formats also differ: `next-intl` uses ICU MessageFormat, while `i18next`
|
|
|
217
226
|
|
|
218
227
|
`next-translate` is my main recommendation if you like a `t()`-style API. It is elegant via `next-translate-plugin`, loading namespaces through `getStaticProps` with a Webpack / Turbopack loader. It is also the lightest option here (~2.5kb). For namespacing, defining namespaces per page or route in config is well thought out and easier to maintain than main alternatives like **next-intl** or **next-i18next**. In version `3.1.2`, I noted that static rendering did not work; Next.js fell back to dynamic rendering.
|
|
219
228
|
|
|
220
|
-
**(Intlayer)** (`next-intlayer@8.7.
|
|
229
|
+
**(Intlayer)** (`next-intlayer@8.7.12`):
|
|
221
230
|
|
|
222
231
|
I will not personally judge `next-intlayer` for objectivity’s sake, since it is my own solution.
|
|
223
232
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Best i18n solution for Solid in 2026 - Benchmark Report
|
|
5
|
+
description: Compare Solid internationalisation (i18n) libraries like solid-primitives, solid-i18next, and Intlayer. Detailed performance report on bundle size, leakage, and reactivity.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- solid
|
|
11
|
+
- performance
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- solid
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n-solid-template
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.12
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Init benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Solid i18n Libraries — 2026 Benchmark Report
|
|
26
|
+
|
|
27
|
+
This page is a benchmark report for i18n solutions on Solid.
|
|
28
|
+
|
|
29
|
+
## Table of Contents
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Interactive Benchmark
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="vite-solid" vertical/>
|
|
36
|
+
|
|
37
|
+
## Results reference:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-vite_solid.md"
|
|
41
|
+
width="100%"
|
|
42
|
+
height="600px"
|
|
43
|
+
style="border:none;">
|
|
44
|
+
</iframe>
|
|
45
|
+
|
|
46
|
+
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-vite_solid.md
|
|
47
|
+
|
|
48
|
+
See complete benchmark repository [here](https://github.com/intlayer-org/benchmark-i18n/tree/main).
|
|
49
|
+
|
|
50
|
+
## Introduction
|
|
51
|
+
|
|
52
|
+
Internationalisation solutions are among the heaviest dependencies in a Solid app. The main risk is shipping unnecessary content: translations for other pages and other locales in a single route’s bundle.
|
|
53
|
+
|
|
54
|
+
As your app grows, that problem can quickly blow up the JavaScript sent to the client and slow down navigation.
|
|
55
|
+
|
|
56
|
+
In practice, for the least optimised implementations, an internationalised page can end up several times heavier than the version without i18n.
|
|
57
|
+
|
|
58
|
+
The other impact is on developer experience: how you declare content, types, namespace organisation, dynamic loading, and reactivity when the locale changes.
|
|
59
|
+
|
|
60
|
+
## TL;DR
|
|
61
|
+
|
|
62
|
+
- **Intlayer**: Recommended choice for professional Solid applications needing advanced features and optimisation (v8.7.12).
|
|
63
|
+
- **@solid-primitives/i18n**: Excellent lightweight alternative for simple projects, though lacks advanced features like lazy loading.
|
|
64
|
+
- **solid-i18next**: Standard but heavy option (~4.7× Intlayer) with same downsides as React i18next.
|
|
65
|
+
- **Paraglide**: Innovative approach but complex DX and tree-shaking issues in some setups.
|
|
66
|
+
|
|
67
|
+
## Test your app
|
|
68
|
+
|
|
69
|
+
To quickly spot i18n leakage issues, I set up a free scanner available [here](https://intlayer.org/i18n-seo-scanner).
|
|
70
|
+
|
|
71
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
72
|
+
|
|
73
|
+
## The problem
|
|
74
|
+
|
|
75
|
+
Two levers are essential to limit the cost of a multilingual app:
|
|
76
|
+
|
|
77
|
+
- Split content by page / namespace so you do not load whole dictionaries when you do not need them
|
|
78
|
+
- Load the right locale dynamically, only when needed
|
|
79
|
+
|
|
80
|
+
Understanding the technical limitations of these approaches:
|
|
81
|
+
|
|
82
|
+
**Dynamic loading**
|
|
83
|
+
|
|
84
|
+
Without dynamic loading, most solutions keep messages in memory from the first render, which adds significant overhead for apps with many routes and locales.
|
|
85
|
+
|
|
86
|
+
With dynamic loading, you accept a trade-off: less initial JS, but sometimes an extra request when switching language.
|
|
87
|
+
|
|
88
|
+
**Content splitting**
|
|
89
|
+
|
|
90
|
+
Syntaxes built around `t('a.b.c')` are very convenient but often encourage keeping large JSON objects at runtime. That model makes tree-shaking hard unless the library offers a real per-page split strategy.
|
|
91
|
+
|
|
92
|
+
## Methodology
|
|
93
|
+
|
|
94
|
+
For this benchmark, we compared the following libraries:
|
|
95
|
+
|
|
96
|
+
- `Base App` (No i18n library)
|
|
97
|
+
- `solid-intlayer` (v8.7.12)
|
|
98
|
+
- `@solid-primitives/i18n` (v2.2.1)
|
|
99
|
+
- `solid-i18next` (v17.0.2)
|
|
100
|
+
- `@inlang/paraglide-js` (v2.17.0)
|
|
101
|
+
|
|
102
|
+
The framework is `Solid` with a multilingual app of **10 pages** and **10 languages**.
|
|
103
|
+
|
|
104
|
+
We compared **four loading strategies**:
|
|
105
|
+
|
|
106
|
+
| Strategy | No namespaces (global) | With namespaces (scoped) |
|
|
107
|
+
| :------------------ | :------------------------------------------- | :------------------------------------------------------------------- |
|
|
108
|
+
| **Static loading** | **Static**: Everything in memory at startup. | **Scoped static**: Split by namespace; everything loaded at startup. |
|
|
109
|
+
| **Dynamic loading** | **Dynamic**: On-demand loading per locale. | **Scoped dynamic**: Granular loading per namespace and locale. |
|
|
110
|
+
|
|
111
|
+
## Strategy summary
|
|
112
|
+
|
|
113
|
+
- **Static**: Simple; no network latency after the initial load. Downside: large bundle size.
|
|
114
|
+
- **Dynamic**: Reduces initial weight (lazy-loading). Ideal when you have many locales.
|
|
115
|
+
- **Scoped static**: Keeps code organised (logical separation) without complex extra network requests.
|
|
116
|
+
- **Scoped dynamic**: Best approach for _code splitting_ and performance. Minimises memory by loading only what the current view and active locale need.
|
|
117
|
+
|
|
118
|
+
## Results in detail
|
|
119
|
+
|
|
120
|
+
### 1 — Solutions to avoid
|
|
121
|
+
|
|
122
|
+
> No clear solution to avoid in solid ecosystem.
|
|
123
|
+
|
|
124
|
+
### 2 — Acceptable solutions
|
|
125
|
+
|
|
126
|
+
**(solid-i18next)** (`solid-i18next@17.0.2`):
|
|
127
|
+
|
|
128
|
+
`solid-i18next` is probably the most popular option because it was among the first to serve JavaScript app i18n needs. It also has a wide set of community plugins for specific problems.
|
|
129
|
+
|
|
130
|
+
The package is heavy (~14.6kb, which is about 4.7× `solid-intlayer`).
|
|
131
|
+
|
|
132
|
+
Still, it shares the same major downsides as stacks built on `t('a.b.c')`: optimisations are possible but very time-consuming, and large projects risk bad practices (namespaces + dynamic loading + types).
|
|
133
|
+
|
|
134
|
+
**(@solid-primitives/i18n)** (`@solid-primitives/i18n@2.2.1`):
|
|
135
|
+
|
|
136
|
+
Solid primitive is extremely light and efficient. I recommend that solution for light projects, but it can quickly become lacking features for professional solutions including cookie management, proxy redirection, formatters etc.
|
|
137
|
+
It also misses lazy loading and scoping namespaces for page size optimisation.
|
|
138
|
+
|
|
139
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.17.0`):
|
|
140
|
+
|
|
141
|
+
`Paraglide` offers an innovative, well-thought-out approach. Even so, in this benchmark the tree-shaking their company advertises did not work for my implementation. The workflow and DX are also more complex than other options.
|
|
142
|
+
Personally I dislike having to regenerate JS files before every push, which creates constant merge conflict risk via PRs.
|
|
143
|
+
Finally, in comparison with other solutions, Paraglide does not use a store (e.g. Solid signal) to retrieve the current locale to render the content. For each node parsed, it will request the locale from the localStorage / cookie etc. It leads to execution of unnecessary logic that impacts the component reactivity.
|
|
144
|
+
|
|
145
|
+
### 3 — Recommendations
|
|
146
|
+
|
|
147
|
+
**(Intlayer)** (`solid-intlayer@8.7.12`):
|
|
148
|
+
|
|
149
|
+
I will not personally judge `solid-intlayer` for objectivity’s sake, since it is my own solution.
|
|
150
|
+
|
|
151
|
+
### Personal note
|
|
152
|
+
|
|
153
|
+
This note is personal and does not affect the benchmark results. Still, in the i18n world you often see consensus around a pattern like `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` for translated content.
|
|
154
|
+
|
|
155
|
+
In Solid apps, injecting a function as a `JSX.Element` is, in my view, an anti-pattern. It also adds avoidable complexity and JavaScript execution overhead (even if it is barely noticeable).
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Best i18n solution for Svelte in 2026 - Benchmark Report
|
|
5
|
+
description: Compare Svelte internationalisation (i18n) libraries like svelte-i18n, Paraglide, and Intlayer. Detailed performance report on bundle size, leakage, and reactivity.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- svelte
|
|
11
|
+
- performance
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- svelte
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n-svelte-template
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.12
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Init benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Svelte i18n Libraries — 2026 Benchmark Report
|
|
26
|
+
|
|
27
|
+
This page is a benchmark report for i18n solutions on Svelte.
|
|
28
|
+
|
|
29
|
+
## Table of Contents
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Interactive Benchmark
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="vite-svelte" vertical/>
|
|
36
|
+
|
|
37
|
+
## Results reference:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-vite_svelte.md"
|
|
41
|
+
width="100%"
|
|
42
|
+
height="600px"
|
|
43
|
+
style="border:none;">
|
|
44
|
+
</iframe>
|
|
45
|
+
|
|
46
|
+
> https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-vite_svelte.md
|
|
47
|
+
|
|
48
|
+
See complete benchmark repository [here](https://github.com/intlayer-org/benchmark-i18n/tree/main).
|
|
49
|
+
|
|
50
|
+
## Introduction
|
|
51
|
+
|
|
52
|
+
Internationalisation solutions are among the heaviest dependencies in a Svelte app. The main risk is shipping unnecessary content: translations for other pages and other locales in a single route’s bundle.
|
|
53
|
+
|
|
54
|
+
As your app grows, that problem can quickly blow up the JavaScript sent to the client and slow down navigation.
|
|
55
|
+
|
|
56
|
+
In practice, for the least optimised implementations, an internationalised page can end up several times heavier than the version without i18n.
|
|
57
|
+
|
|
58
|
+
The other impact is on developer experience: how you declare content, types, namespace organisation, dynamic loading, and reactivity when the locale changes.
|
|
59
|
+
|
|
60
|
+
## TL;DR
|
|
61
|
+
|
|
62
|
+
- **Intlayer**: The most performance-efficient choice (v8.7.12) with the smallest footprint.
|
|
63
|
+
- **Paraglide**: Strong contender for tree-shaking but has a more complex developer experience and reactivity overhead.
|
|
64
|
+
- **svelte-i18n**: Comprehensive and standard for Svelte, but carries much larger bundle weight (~7× Intlayer).
|
|
65
|
+
|
|
66
|
+
## Test your app
|
|
67
|
+
|
|
68
|
+
To quickly spot i18n leakage issues, I set up a free scanner available [here](https://intlayer.org/i18n-seo-scanner).
|
|
69
|
+
|
|
70
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
71
|
+
|
|
72
|
+
## The problem
|
|
73
|
+
|
|
74
|
+
Two levers are essential to limit the cost of a multilingual app:
|
|
75
|
+
|
|
76
|
+
- Split content by page / namespace so you do not load whole dictionaries when you do not need them
|
|
77
|
+
- Load the right locale dynamically, only when needed
|
|
78
|
+
|
|
79
|
+
Understanding the technical limitations of these approaches:
|
|
80
|
+
|
|
81
|
+
**Dynamic loading**
|
|
82
|
+
|
|
83
|
+
Without dynamic loading, most solutions keep messages in memory from the first render, which adds significant overhead for apps with many routes and locales.
|
|
84
|
+
|
|
85
|
+
With dynamic loading, you accept a trade-off: less initial JS, but sometimes an extra request when switching language.
|
|
86
|
+
|
|
87
|
+
**Content splitting**
|
|
88
|
+
|
|
89
|
+
Syntaxes built around `t('a.b.c')` are very convenient but often encourage keeping large JSON objects at runtime. That model makes tree-shaking hard unless the library offers a real per-page split strategy.
|
|
90
|
+
|
|
91
|
+
## Methodology
|
|
92
|
+
|
|
93
|
+
For this benchmark, we compared the following libraries:
|
|
94
|
+
|
|
95
|
+
- `Base App` (No i18n library)
|
|
96
|
+
- `svelte-intlayer` (v8.7.12)
|
|
97
|
+
- `svelte-i18n` (v4.0.1)
|
|
98
|
+
- `@inlang/paraglide-js` (v2.17.0)
|
|
99
|
+
|
|
100
|
+
The framework is `Svelte` with a multilingual app of **10 pages** and **10 languages**.
|
|
101
|
+
|
|
102
|
+
We compared **four loading strategies**:
|
|
103
|
+
|
|
104
|
+
| Strategy | No namespaces (global) | With namespaces (scoped) |
|
|
105
|
+
| :------------------ | :------------------------------------------- | :------------------------------------------------------------------- |
|
|
106
|
+
| **Static loading** | **Static**: Everything in memory at startup. | **Scoped static**: Split by namespace; everything loaded at startup. |
|
|
107
|
+
| **Dynamic loading** | **Dynamic**: On-demand loading per locale. | **Scoped dynamic**: Granular loading per namespace and locale. |
|
|
108
|
+
|
|
109
|
+
## Strategy summary
|
|
110
|
+
|
|
111
|
+
- **Static**: Simple; no network latency after the initial load. Downside: large bundle size.
|
|
112
|
+
- **Dynamic**: Reduces initial weight (lazy-loading). Ideal when you have many locales.
|
|
113
|
+
- **Scoped static**: Keeps code organised (logical separation) without complex extra network requests.
|
|
114
|
+
- **Scoped dynamic**: Best approach for _code splitting_ and performance. Minimises memory by loading only what the current view and active locale need.
|
|
115
|
+
|
|
116
|
+
## Results in detail
|
|
117
|
+
|
|
118
|
+
### 1 — Solutions to avoid
|
|
119
|
+
|
|
120
|
+
> No clear solution to avoid in svelte ecosystem.
|
|
121
|
+
|
|
122
|
+
### 2 — Acceptable solutions
|
|
123
|
+
|
|
124
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.17.0`):
|
|
125
|
+
|
|
126
|
+
`Paraglide` offers an innovative, well-thought-out approach. In the context of a Vite + Svelte app, the tree-shaking their company advertises worked as expected, which is great.
|
|
127
|
+
But in the case of React + TanStack Start, tree-shaking did not work as expected, same for Next.js. That said, Paraglide's usage in a Svelte and TanStack Start project would be worth a double check.
|
|
128
|
+
The workflow and DX are also more complex than other options.
|
|
129
|
+
Personally I am not a fan of having to regenerate JS files before every push, which creates constant merge conflict risk via PRs. The tool also seems more focused on Vite than Next.js.
|
|
130
|
+
Finally, in comparison with other solutions, Paraglide does not use a store (e.g. Svelte store) to retrieve the current locale to render the content. For each node parsed, it will request the locale from the localStorage / cookie etc. It leads to execution of unnecessary logic that impacts the component reactivity.
|
|
131
|
+
|
|
132
|
+
> Note on paraglide: the solution injects code into your codebase for imports; as a result, the 'lib size' metric in the benchmark report is almost 0. Code generation is a good thing, because the function used will include only the necessary logic (prefix everywhere vs no prefix, cookie vs storage, etc.). In comparison, Intlayer performs this filtering via environment variable injections in the build to force the bundler to tree-shake the content depending on the logic. Thanks to this, paraglide and intlayer end up being 6 to 10 times lighter solutions than i18next or next-intl.
|
|
133
|
+
|
|
134
|
+
**(svelte-i18n)** (`svelte-i18n@3.4.0`):
|
|
135
|
+
|
|
136
|
+
This solution answers all i18n needs in a Svelte project. But as it is the case for i18next or other major i18n solutions, it is a bit heavy (~15.9kb, which is about 7× `svelte-intlayer`).
|
|
137
|
+
|
|
138
|
+
### 3 — Recommendations
|
|
139
|
+
|
|
140
|
+
**(Intlayer)** (`svelte-intlayer@8.7.12`):
|
|
141
|
+
|
|
142
|
+
I will not personally judge `svelte-intlayer` for objectivity’s sake, since it is my own solution.
|
|
143
|
+
|
|
144
|
+
### Personal note
|
|
145
|
+
|
|
146
|
+
This note is personal and does not affect the benchmark results. Still, in the i18n world you often see consensus around a pattern like `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` for translated content.
|
|
147
|
+
|
|
148
|
+
In Svelte apps, injecting a function as a `Slot` is, in my view, an anti-pattern. It also adds avoidable complexity and JavaScript execution overhead (even if it is barely noticeable).
|
|
@@ -57,6 +57,13 @@ In practice, for the least optimised implementations, an internationalised page
|
|
|
57
57
|
|
|
58
58
|
The other impact is on developer experience: how you declare content, types, namespace organisation, dynamic loading, and reactivity when the locale changes.
|
|
59
59
|
|
|
60
|
+
## TL;DR
|
|
61
|
+
|
|
62
|
+
- **Intlayer**: Provides the best performance and smallest bundle size (v8.7.12) for TanStack Start.
|
|
63
|
+
- **react-i18next** & **use-intl**: Mature alternatives with large ecosystems, but significantly heavier and more complex to optimise.
|
|
64
|
+
- **Paraglide**: Innovative tree-shaking idea that does not work in practice. Complex DX and reactivity overhead in TanStack Start.
|
|
65
|
+
- **Avoid**: **General Translation (GT)** and **Lingo.dev** due to severe performance issues, AI quota limits, and vendor lock-in.
|
|
66
|
+
|
|
60
67
|
## Test your app
|
|
61
68
|
|
|
62
69
|
To quickly spot i18n leakage issues, I set up a free scanner available [here](https://intlayer.org/i18n-seo-scanner).
|
|
@@ -87,12 +94,12 @@ Syntaxes built around `const t = useTranslation()` + `t('a.b.c')` are very conve
|
|
|
87
94
|
For this benchmark, we compared the following libraries:
|
|
88
95
|
|
|
89
96
|
- `Base App` (No i18n library)
|
|
90
|
-
- `react-intlayer` (v8.7.
|
|
97
|
+
- `react-intlayer` (v8.7.12)
|
|
91
98
|
- `react-i18next` (v17.0.2)
|
|
92
99
|
- `use-intl` (v4.9.1)
|
|
93
100
|
- `@lingui/core` (v5.3.0)
|
|
94
101
|
- `@inlang/paraglide-js` (v2.15.1)
|
|
95
|
-
-
|
|
102
|
+
- `@tolgee/react` (v7.0.0)
|
|
96
103
|
- `react-intl` (v10.1.1)
|
|
97
104
|
- `wuchale` (v0.22.11)
|
|
98
105
|
- `gt-react` (vlatest)
|
|
@@ -150,7 +157,9 @@ The idea behind `Wuchale` is interesting but not yet a viable solution. I hit re
|
|
|
150
157
|
|
|
151
158
|
`Paraglide` offers an innovative, well-thought-out approach. Even so, in this benchmark the tree-shaking their company advertises did not work for my Next.js implementation or for TanStack Start. The workflow and DX are also more complex than other options. Personally I am not a fan of having to regenerate JS files before every push, which creates constant merge conflict risk for developers via PRs.
|
|
152
159
|
|
|
153
|
-
|
|
160
|
+
> Note on paraglide: the solution injects code into your codebase for imports; as a result, the 'lib size' metric in the benchmark report is almost 0. Code generation is a good thing, because the function used will include only the necessary logic (prefix everywhere vs no prefix, cookie vs storage, etc.). In comparison, Intlayer performs this filtering via environment variable injections in the build to force the bundler to tree-shake the content depending on the logic. Thanks to this, paraglide and intlayer end up being 6 to 10 times lighter solutions than i18next or next-intl.
|
|
161
|
+
|
|
162
|
+
**(Tolgee)** (`@tolgee/react@7.0.0`):
|
|
154
163
|
|
|
155
164
|
`Tolgee` addresses many of the issues mentioned earlier. I found it harder to get started with than other tools with similar approaches. It does not provide type safety, which also makes catching missing keys at compile time much harder. I had to wrap Tolgee’s APIs with my own to add missing-key detection.
|
|
156
165
|
|