@intlayer/docs 8.7.4 → 8.7.5
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/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/id/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/pl/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/blog/vi/list_i18n_technologies/frameworks/svelte.md +0 -2
- package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +0 -2
- package/dist/cjs/generated/docs.entry.cjs +60 -0
- package/dist/cjs/generated/docs.entry.cjs.map +1 -1
- package/dist/esm/generated/docs.entry.mjs +60 -0
- package/dist/esm/generated/docs.entry.mjs.map +1 -1
- package/dist/types/generated/docs.entry.d.ts +3 -0
- package/dist/types/generated/docs.entry.d.ts.map +1 -1
- package/docs/ar/benchmark/index.md +29 -0
- package/docs/ar/benchmark/nextjs.md +227 -0
- package/docs/ar/benchmark/tanstack.md +193 -0
- package/docs/ar/intlayer_with_tanstack.md +0 -2
- package/docs/de/benchmark/index.md +29 -0
- package/docs/de/benchmark/nextjs.md +227 -0
- package/docs/de/benchmark/tanstack.md +193 -0
- package/docs/en/benchmark/___NOTE.md +82 -0
- package/docs/en/benchmark/___nextjs.md +195 -0
- package/docs/en/benchmark/___tanstack.md +187 -0
- package/docs/en/benchmark/index.md +29 -0
- package/docs/en/benchmark/nextjs.md +228 -0
- package/docs/en/benchmark/tanstack.md +217 -0
- package/docs/en-GB/benchmark/index.md +29 -0
- package/docs/en-GB/benchmark/nextjs.md +228 -0
- package/docs/en-GB/benchmark/tanstack.md +193 -0
- package/docs/es/benchmark/index.md +29 -0
- package/docs/es/benchmark/nextjs.md +226 -0
- package/docs/es/benchmark/tanstack.md +193 -0
- package/docs/fr/benchmark/index.md +29 -0
- package/docs/fr/benchmark/nextjs.md +227 -0
- package/docs/fr/benchmark/tanstack.md +193 -0
- package/docs/hi/benchmark/index.md +29 -0
- package/docs/hi/benchmark/nextjs.md +227 -0
- package/docs/hi/benchmark/tanstack.md +193 -0
- package/docs/id/benchmark/index.md +29 -0
- package/docs/id/benchmark/nextjs.md +227 -0
- package/docs/id/benchmark/tanstack.md +193 -0
- package/docs/id/intlayer_with_react_native+expo.md +0 -2
- package/docs/it/benchmark/index.md +29 -0
- package/docs/it/benchmark/nextjs.md +227 -0
- package/docs/it/benchmark/tanstack.md +193 -0
- package/docs/ja/benchmark/index.md +29 -0
- package/docs/ja/benchmark/nextjs.md +227 -0
- package/docs/ja/benchmark/tanstack.md +193 -0
- package/docs/ko/benchmark/index.md +29 -0
- package/docs/ko/benchmark/nextjs.md +227 -0
- package/docs/ko/benchmark/tanstack.md +193 -0
- package/docs/ko/intlayer_with_tanstack.md +0 -2
- package/docs/pl/benchmark/index.md +29 -0
- package/docs/pl/benchmark/nextjs.md +227 -0
- package/docs/pl/benchmark/tanstack.md +193 -0
- package/docs/pt/benchmark/index.md +29 -0
- package/docs/pt/benchmark/nextjs.md +227 -0
- package/docs/pt/benchmark/tanstack.md +193 -0
- package/docs/ru/benchmark/index.md +29 -0
- package/docs/ru/benchmark/nextjs.md +227 -0
- package/docs/ru/benchmark/tanstack.md +193 -0
- package/docs/tr/benchmark/index.md +29 -0
- package/docs/tr/benchmark/nextjs.md +227 -0
- package/docs/tr/benchmark/tanstack.md +193 -0
- package/docs/uk/benchmark/index.md +29 -0
- package/docs/uk/benchmark/nextjs.md +227 -0
- package/docs/uk/benchmark/tanstack.md +193 -0
- package/docs/vi/benchmark/index.md +29 -0
- package/docs/vi/benchmark/nextjs.md +227 -0
- package/docs/vi/benchmark/tanstack.md +193 -0
- package/docs/zh/benchmark/index.md +29 -0
- package/docs/zh/benchmark/nextjs.md +227 -0
- package/docs/zh/benchmark/tanstack.md +193 -0
- package/package.json +6 -6
- package/src/generated/docs.entry.ts +60 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Migliore soluzione i18n per Next.js nel 2026 - Rapporto Benchmark
|
|
5
|
+
description: Confronta le librerie di internazionalizzazione (i18n) per Next.js come next-intl, next-i18next e Intlayer. Rapporto dettagliato sulle prestazioni relative a dimensioni del bundle, leakage e reattività.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- nextjs
|
|
11
|
+
- prestazioni
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- nextjs
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.5
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Inizializzazione benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Librerie i18n per Next.js — Rapporto Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Questa pagina è un rapporto benchmark per le soluzioni i18n su Next.js.
|
|
28
|
+
|
|
29
|
+
## Sommario
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark Interattivo
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="nextjs" vertical/>
|
|
36
|
+
|
|
37
|
+
## Riferimento risultati:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.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-nextjs.md
|
|
47
|
+
|
|
48
|
+
Vedi il repository completo del benchmark [qui](https://github.com/intlayer-org/benchmark-i18n).
|
|
49
|
+
|
|
50
|
+
## Introduzione
|
|
51
|
+
|
|
52
|
+
Le librerie di internazionalizzazione hanno un impatto pesante sulla tua applicazione. Il rischio principale è caricare contenuti per ogni pagina e ogni lingua quando l'utente visita solo una pagina.
|
|
53
|
+
|
|
54
|
+
Man mano che l'app cresce, le dimensioni del bundle possono aumentare esponenzialmente, il che può compromettere notevolmente le prestazioni.
|
|
55
|
+
|
|
56
|
+
Ad esempio, nei casi peggiori, una volta internazionalizzata, la tua pagina può finire per essere quasi 4 volte più grande.
|
|
57
|
+
|
|
58
|
+
Un altro impatto delle librerie i18n è il rallentamento dello sviluppo. Trasformare i componenti in contenuti multilingue in diverse lingue richiede tempo.
|
|
59
|
+
|
|
60
|
+
Poiché il problema è difficile, esistono molte soluzioni, alcune focalizzate sulla DX (Developer Experience), altre sulle prestazioni o sulla scalabilità, e così via.
|
|
61
|
+
|
|
62
|
+
Intlayer cerca di ottimizzare tutte queste dimensioni.
|
|
63
|
+
|
|
64
|
+
## Testa la tua app
|
|
65
|
+
|
|
66
|
+
Per far emergere questi problemi, ho creato uno scanner gratuito che puoi provare [qui](https://intlayer.org/i18n-seo-scanner).
|
|
67
|
+
|
|
68
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
69
|
+
|
|
70
|
+
## Il problema
|
|
71
|
+
|
|
72
|
+
Esistono due modi principali per limitare l'impatto di un'app multilingue sul bundle:
|
|
73
|
+
|
|
74
|
+
- Dividere i JSON (o contenuti) tra file / variabili / namespace in modo che il bundler possa scartare (tree-shake) i contenuti inutilizzati per una data pagina
|
|
75
|
+
- Caricare dinamicamente i contenuti della pagina solo nella lingua dell'utente
|
|
76
|
+
|
|
77
|
+
Limitazioni tecniche di questi approcci:
|
|
78
|
+
|
|
79
|
+
**Caricamento dinamico**
|
|
80
|
+
|
|
81
|
+
Anche quando dichiari percorsi come `[locale]/page.tsx`, con Webpack o Turbopack, e anche se `generateStaticParams` è definito, il bundler non tratta `locale` come una costante statica. Ciò significa che potrebbe includere contenuti per tutte le lingue in ogni pagina. Il modo principale per limitare questo è caricare i contenuti tramite un import dinamico (es. `import('./locales/${locale}.json')`).
|
|
82
|
+
|
|
83
|
+
Ciò che accade al build time è che Next.js emette un bundle JS per lingua (es. `./locales_it_12345.js`). Dopo che il sito è stato inviato al client, quando la pagina viene eseguita, il browser effettua una richiesta HTTP aggiuntiva per il file JS necessario (es. `./locales_it_12345.js`).
|
|
84
|
+
|
|
85
|
+
> Un altro modo per affrontare lo stesso problema è usare `fetch()` per caricare dinamicamente i JSON. È così che funziona `Tolgee` quando i JSON risiedono sotto `/public`, o `next-translate`, che si affida a `getStaticProps` per caricare i contenuti. Il flusso è lo stesso: il browser effettua una richiesta HTTP aggiuntiva per caricare l'asset.
|
|
86
|
+
|
|
87
|
+
**Suddivisione dei contenuti (Content splitting)**
|
|
88
|
+
|
|
89
|
+
Se usi una sintassi come `const t = useTranslation()` + `t('my-object.my-sub-object.my-key')`, l'intero JSON deve solitamente essere presente nel bundle in modo che la libreria possa analizzarlo e risolvere la chiave. Gran parte di quel contenuto viene spedito anche quando è inutilizzato nella pagina.
|
|
90
|
+
|
|
91
|
+
Per mitigare questo, alcune librerie chiedono di dichiarare per pagina quali namespace caricare — es. `next-i18next`, `next-intl`, `lingui`, `next-translate`, `next-international`.
|
|
92
|
+
|
|
93
|
+
Al contrario, `Paraglide` aggiunge un passaggio extra prima della build per trasformare i JSON in simboli piatti come `const en_my_var = () => 'my value'`. In teoria questo abilita il tree-shaking dei contenuti inutilizzati nella pagina. Come vedremo, questo metodo presenta comunque dei compromessi.
|
|
94
|
+
|
|
95
|
+
Infine, `Intlayer` applica un'ottimizzazione build-time in modo che `useIntlayer('my-key')` venga sostituito direttamente con il contenuto corrispondente.
|
|
96
|
+
|
|
97
|
+
## Metodologia
|
|
98
|
+
|
|
99
|
+
Per questo benchmark, abbiamo confrontato le seguenti librerie:
|
|
100
|
+
|
|
101
|
+
- `Base App` (Nessuna libreria i18n)
|
|
102
|
+
- `next-intlayer` (v8.7.5)
|
|
103
|
+
- `next-i18next` (v16.0.5)
|
|
104
|
+
- `next-intl` (v4.9.1)
|
|
105
|
+
- `@lingui/core` (v5.3.0)
|
|
106
|
+
- `next-translate` (v3.1.2)
|
|
107
|
+
- `next-international` (v1.3.1)
|
|
108
|
+
- `@inlang/paraglide-js` (v2.15.1)
|
|
109
|
+
- `tolgee` (v7.0.0)
|
|
110
|
+
- `@lingo.dev/compiler` (v0.4.0)
|
|
111
|
+
- `wuchale` (v0.22.11)
|
|
112
|
+
- `gt-next` (v6.16.5)
|
|
113
|
+
|
|
114
|
+
Ho utilizzato `Next.js` versione `16.2.4` con App Router.
|
|
115
|
+
|
|
116
|
+
Ho costruito un'app multilingue con **10 pagine** e **10 lingue**.
|
|
117
|
+
|
|
118
|
+
Ho confrontato **quattro strategie di caricamento**:
|
|
119
|
+
|
|
120
|
+
| Strategia | Senza namespace (globale) | Con namespace (scoped) |
|
|
121
|
+
| :----------------------- | :--------------------------------------------- | :----------------------------------------------------------------- |
|
|
122
|
+
| **Caricamento statico** | **Static**: Tutto in memoria all'avvio. | **Scoped static**: Diviso per namespace; tutto caricato all'avvio. |
|
|
123
|
+
| **Caricamento dinamico** | **Dynamic**: Caricamento on-demand per lingua. | **Scoped dynamic**: Caricamento granulare per namespace e lingua. |
|
|
124
|
+
|
|
125
|
+
## Sintesi delle strategie
|
|
126
|
+
|
|
127
|
+
- **Static**: Semplice; nessuna latenza di rete dopo il caricamento iniziale. Svantaggio: grandi dimensioni del bundle.
|
|
128
|
+
- **Dynamic**: Riduce il peso iniziale (lazy-loading). Ideale quando si hanno molte localizzazioni.
|
|
129
|
+
- **Scoped static**: Mantiene il codice organizzato (separazione logica) senza requisiti di rete complessi.
|
|
130
|
+
- **Scoped dynamic**: Il miglior approccio per il _code splitting_ e le prestazioni. Riduce al minimo la memoria caricando solo ciò di cui la vista corrente e la lingua attiva hanno bisogno.
|
|
131
|
+
|
|
132
|
+
### Cosa ho misurato:
|
|
133
|
+
|
|
134
|
+
Ho eseguito la stessa applicazione multilingue in un browser reale per ogni stack, quindi ho annotato cosa è apparso effettivamente sulla rete e quanto tempo hanno impiegato le operazioni. Le dimensioni sono riportate **dopo la normale compressione web**, poiché è più vicina a ciò che le persone scaricano realmente rispetto al conteggio dei sorgenti grezzi.
|
|
135
|
+
|
|
136
|
+
- **Dimensioni della libreria di internazionalizzazione**: Dopo bundling, tree-shaking e minificazione, la dimensione della libreria i18n è la dimensione dei provider (es. `NextIntlClientProvider`) + il codice degli hook (es. `useTranslations`) in un componente vuoto. Non include il caricamento dei file di traduzione. Risponde a quanto sia "costosa" la libreria prima che entrino in gioco i tuoi contenuti.
|
|
137
|
+
|
|
138
|
+
- **JavaScript per pagina**: Per ogni percorso del benchmark, quanto script il browser scarica per quella visita, mediato su tutte le pagine della suite (e su tutte le lingue dove il rapporto le raggruppa). Le pagine pesanti sono pagine lente.
|
|
139
|
+
|
|
140
|
+
- **Leakage da altre lingue**: È il contenuto della stessa pagina ma in un'altra lingua che verrebbe caricato per errore nella pagina esaminata. Questo contenuto è superfluo e dovrebbe essere evitato (es. contenuto della pagina `/fr/about` nel bundle della pagina `/en/about`).
|
|
141
|
+
|
|
142
|
+
- **Leakage da altri percorsi**: La stessa idea per le **altre schermate** nell'app: se i loro testi sono inclusi nel bundle quando hai aperto solo una pagina (es. contenuto della pagina `/en/about` nel bundle della pagina `/en/contact`). Un punteggio elevato suggerisce uno splitting debole o bundle eccessivamente ampi.
|
|
143
|
+
|
|
144
|
+
- **Dimensione media del bundle per componente**: I pezzi comuni della UI vengono misurati **uno alla volta** invece di nascondersi all'interno di un unico numero gigante dell'app. Mostra se l'internazionalizzazione gonfia silenziosamente i componenti quotidiani. Ad esempio, se il tuo componente esegue un re-rendering, caricherà tutti quei dati dalla memoria. Allegare un JSON gigante a qualsiasi componente è come collegare un grande magazzino di dati inutilizzati che rallenterà le prestazioni dei tuoi componenti.
|
|
145
|
+
|
|
146
|
+
- **Reattività al cambio di lingua**: Cambio la lingua usando il controllo dell'app e cronometro quanto tempo ci vuole finché la pagina non è chiaramente cambiata — ciò che un visitatore noterebbe, non un micro-passaggio di laboratorio.
|
|
147
|
+
|
|
148
|
+
- **Lavoro di rendering dopo un cambio di lingua**: Un approfondimento: quanto sforzo ha impiegato l'interfaccia per ridisegnarsi per la nuova lingua una volta avviato il cambio. Utile quando il tempo "percepito" e il costo del framework divergono.
|
|
149
|
+
|
|
150
|
+
- **Tempo di caricamento iniziale della pagina**: Dalla navigazione fino a quando il browser considera la pagina completamente caricata per gli scenari che ho testato. Ottimo per confrontare i caricamenti a freddo (cold start).
|
|
151
|
+
|
|
152
|
+
- **Tempo di idratazione (Hydration)**: Quando l'app lo espone, quanto tempo impiega il client per trasformare l'HTML del server in qualcosa su cui si può effettivamente cliccare. Un trattino nelle tabelle significa che quell'implementazione non ha fornito un dato di idratazione affidabile in questo benchmark.
|
|
153
|
+
|
|
154
|
+
## Risultati nel dettaglio
|
|
155
|
+
|
|
156
|
+
### 1 — Soluzioni da evitare
|
|
157
|
+
|
|
158
|
+
Alcune soluzioni, come `gt-next` o `lingo.dev`, sono chiaramente da evitare. Combinano il vendor lock-in con l'inquinamento della base di codice. Nonostante molte ore trascorse cercando di implementarle, non sono mai riuscito a farle funzionare, né su TanStack Start né su Next.js.
|
|
159
|
+
|
|
160
|
+
Problemi riscontrati:
|
|
161
|
+
|
|
162
|
+
**(General Translation)** (`gt-next@6.16.5`):
|
|
163
|
+
|
|
164
|
+
- Per un'app da 110kb, `gt-react` aggiunge più di 440kb extra.
|
|
165
|
+
- `Quota Exceeded, please upgrade your plan` al primissimo build con General Translation.
|
|
166
|
+
- Le traduzioni non vengono renderizzate; ottengo l'errore `Error: <T> used on the client-side outside of <GTProvider>`, che sembra essere un bug nella libreria.
|
|
167
|
+
- Durante l'implementazione di **gt-tanstack-start-react**, ho riscontrato anche un [problema](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) con la libreria: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, che faceva fallire l'applicazione. Dopo aver segnalato questo problema, il manutentore lo ha risolto entro 24 ore.
|
|
168
|
+
- La libreria blocca il rendering statico delle pagine Next.js.
|
|
169
|
+
|
|
170
|
+
**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
|
|
171
|
+
|
|
172
|
+
- Quota AI superata, bloccando interamente la build — quindi non puoi andare in produzione senza pagare.
|
|
173
|
+
- Il compilatore perdeva quasi il 40% del contenuto tradotto. Ho dovuto riscrivere tutti i `.map` in blocchi di componenti piatti per farlo funzionare.
|
|
174
|
+
- La loro CLI è buggata e tendeva a resettare il file di configurazione senza motivo.
|
|
175
|
+
- Alla build, cancellava totalmente i JSON generati quando veniva aggiunto nuovo contenuto. Di conseguenza, una manciata di chiavi poteva cancellare più di 300 chiavi esistenti.
|
|
176
|
+
|
|
177
|
+
### 2 — Soluzioni sperimentali
|
|
178
|
+
|
|
179
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
180
|
+
|
|
181
|
+
L'idea alla base di `Wuchale` è interessante ma non ancora praticabile. Ho riscontrato problemi di reattività e ho dovuto forzare il re-rendering del provider per far funzionare l'app. La documentazione è inoltre piuttosto oscura, il che rende difficile l'onboarding.
|
|
182
|
+
|
|
183
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
184
|
+
|
|
185
|
+
`Paraglide` offre un approccio innovativo e ben ponderato. Tuttavia, in questo benchmark il tree-shaking pubblicizzato non ha funzionato per le mie configurazioni Next.js o TanStack Start. Il workflow e la DX sono più complessi rispetto ad altre opzioni.
|
|
186
|
+
Personalmente non mi piace dover rigenerare file JS prima di ogni push, il che crea un rischio costante di conflitti di merge tramite PR. Lo strumento sembra inoltre più focalizzato su Vite che su Next.js.
|
|
187
|
+
Infine, rispetto ad altre soluzioni, Paraglide non usa uno store (es. contesto React) per recuperare la lingua corrente per renderizzare il contenuto. Per ogni nodo analizzato, richiederà la lingua da localStorage / cookie ecc. Ciò porta all'esecuzione di logica non necessaria che impatta sulla reattività del componente.
|
|
188
|
+
|
|
189
|
+
### 3 — Soluzioni accettabili
|
|
190
|
+
|
|
191
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
192
|
+
|
|
193
|
+
`Tolgee` affronta molti dei problemi menzionati in precedenza. L'ho trovato più difficile da adottare rispetto a strumenti simili. Non fornisce type safety, il che rende anche più difficile individuare le chiavi mancanti a compile time. Ho dovuto avvolgere le funzioni di Tolgee con le mie per aggiungere il rilevamento delle chiavi mancanti.
|
|
194
|
+
|
|
195
|
+
**(Next Intl)** (`next-intl@4.9.1`):
|
|
196
|
+
|
|
197
|
+
`next-intl` è l'opzione più di tendenza e quella che gli assistenti AI spingono di più, ma a mio avviso a torto. Iniziare è facile. In pratica, ottimizzare per limitare i leakage è complesso. Combinare caricamento dinamico + namespace + tipi TypeScript rallenta molto lo sviluppo. Il pacchetto è anche piuttosto pesante (~13kb per `NextIntlClientProvider` + `useTranslations`, che è più di 2 volte `next-intlayer`). **next-intl** tendeva a bloccare il rendering statico delle pagine Next.js. Fornisce un helper chiamato `setRequestLocale()`. Sembra che ciò sia stato parzialmente risolto per file centralizzati come `en.json` / `fr.json`, ma il rendering statico si interrompe ancora quando il contenuto è diviso in namespace come `en/shared.json` / `fr/shared.json` / `es/shared.json`.
|
|
198
|
+
|
|
199
|
+
**(Next I18next)** (`next-i18next@16.0.5`):
|
|
200
|
+
|
|
201
|
+
`next-i18next` è probabilmente l'opzione più popolare perché è stata tra le prime soluzioni i18n per app JavaScript. Ha molti plugin della community. Condivide gli stessi svantaggi principali di `next-intl`. Il pacchetto è particolarmente pesante (~18kb per `I18nProvider` + `useTranslation`, circa 3 volte `next-intlayer`).
|
|
202
|
+
|
|
203
|
+
I formati dei messaggi differiscono inoltre: `next-intl` usa ICU MessageFormat, mentre `i18next` usa il proprio formato.
|
|
204
|
+
|
|
205
|
+
**(Next International)** (`next-international@1.3.1`):
|
|
206
|
+
|
|
207
|
+
`next-international` affronta anch'esso i problemi di cui sopra ma non differisce molto da `next-intl` o `next-i18next`. Include `scopedT()` per traduzioni specifiche di un namespace, ma usarlo non ha praticamente alcun impatto sulle dimensioni del bundle.
|
|
208
|
+
|
|
209
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
210
|
+
|
|
211
|
+
`Lingui` è spesso elogiato. Personalmente ho trovato il workflow `lingui extract` / `lingui compile` più complesso delle alternative, senza un chiaro vantaggio. Ho anche notato sintassi inconsistenti che confondono le AI (es. `t()`, `t''`, `i18n.t()`, `<Trans>`).
|
|
212
|
+
|
|
213
|
+
### 4 — Raccomandazioni
|
|
214
|
+
|
|
215
|
+
**(Next Translate)** (`next-translate@3.1.2`):
|
|
216
|
+
|
|
217
|
+
`next-translate` è la mia raccomandazione principale se ti piace un'API in stile `t()`. È elegante grazie a `next-translate-plugin`, caricando i namespace attraverso `getStaticProps` con un caricatore Webpack / Turbopack. È anche l'opzione più leggera qui (~2.5kb). Per il namespacing, la definizione dei namespace per pagina o percorso nella config è ben pensata e più facile da mantenere rispetto alle principali alternative come **next-intl** o **next-i18next**. Nella versione `3.1.2`, ho notato che il rendering statico non funzionava; Next.js ripiegava sul rendering dinamico.
|
|
218
|
+
|
|
219
|
+
**(Intlayer)** (`next-intlayer@8.7.5`):
|
|
220
|
+
|
|
221
|
+
Non giudicherò personalmente `next-intlayer` per motivi di obiettività, essendo la mia propria soluzione.
|
|
222
|
+
|
|
223
|
+
### Nota personale
|
|
224
|
+
|
|
225
|
+
Questa nota è personale e non influisce sui risultati del benchmark. Nel mondo i18n si vede spesso un consenso attorno a `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>`.
|
|
226
|
+
|
|
227
|
+
Nelle app React, iniettare una funzione come `ReactNode` è, a mio avviso, un anti-pattern. Inoltre aggiunge una complessità evitabile e un sovraccarico di esecuzione JavaScript (anche se appena percettibile).
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Migliore soluzione i18n per TanStack Start nel 2026 - Rapporto Benchmark
|
|
5
|
+
description: Confronta le librerie di internazionalizzazione per TanStack Start come react-i18next, use-intl e Intlayer. Rapporto dettagliato sulle prestazioni relative a dimensioni del bundle, leakage e reattività.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- tanstack
|
|
11
|
+
- prestazioni
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- tanstack
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n-tanstack-start-template
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.5
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Inizializzazione benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Librerie i18n per TanStack Start — Rapporto Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Questa pagina è un rapporto benchmark per le soluzioni i18n su TanStack Start.
|
|
28
|
+
|
|
29
|
+
## Sommario
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark Interattivo
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="tanstack" vertical/>
|
|
36
|
+
|
|
37
|
+
## Riferimento risultati:
|
|
38
|
+
|
|
39
|
+
<iframe
|
|
40
|
+
src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.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-tanstack.md
|
|
47
|
+
|
|
48
|
+
Vedi il repository completo del benchmark [qui](https://github.com/intlayer-org/benchmark-i18n/tree/main).
|
|
49
|
+
|
|
50
|
+
## Introduzione
|
|
51
|
+
|
|
52
|
+
Le soluzioni di internazionalizzazione sono tra le dipendenze più pesanti in un'app React. Su TanStack Start, il rischio principale è spedire contenuti non necessari: traduzioni per altre pagine e altre lingue nel bundle di una singola rotta.
|
|
53
|
+
|
|
54
|
+
Man mano che l'app cresce, questo problema può far esplodere rapidamente il JavaScript inviato al client e rallentare la navigazione.
|
|
55
|
+
|
|
56
|
+
In pratica, per le implementazioni meno ottimizzate, una pagina internazionalizzata può finire per essere diverse volte più pesante della versione senza i18n.
|
|
57
|
+
|
|
58
|
+
L'altro impatto riguarda la developer experience (DX): come si dichiara il contenuto, i tipi, l'organizzazione dei namespace, il caricamento dinamico e la reattività al cambio di lingua.
|
|
59
|
+
|
|
60
|
+
## Testa la tua app
|
|
61
|
+
|
|
62
|
+
Per individuare rapidamente problemi di leakage i18n, ho configurato uno scanner gratuito disponibile [qui](https://intlayer.org/i18n-seo-scanner).
|
|
63
|
+
|
|
64
|
+
<iframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>
|
|
65
|
+
|
|
66
|
+
## Il problema
|
|
67
|
+
|
|
68
|
+
Due leve sono essenziali per limitare il costo di un'app multilingue:
|
|
69
|
+
|
|
70
|
+
- Suddividere i contenuti per pagina / namespace per non caricare interi dizionari quando non servono
|
|
71
|
+
- Caricare la lingua corretta dinamicamente, solo quando necessario
|
|
72
|
+
|
|
73
|
+
Capire i limiti tecnici di questi approcci:
|
|
74
|
+
|
|
75
|
+
**Caricamento dinamico**
|
|
76
|
+
|
|
77
|
+
Senza caricamento dinamico, la maggior parte delle soluzioni mantiene i messaggi in memoria fin dal primo rendering, il che aggiunge un sovraccarico significativo per le app con molti percorsi e lingue.
|
|
78
|
+
|
|
79
|
+
Con il caricamento dinamico, accetti un compromesso: meno JS iniziale, ma a volte una richiesta extra quando si cambia lingua.
|
|
80
|
+
|
|
81
|
+
**Suddivisione dei contenuti (Content splitting)**
|
|
82
|
+
|
|
83
|
+
Le sintassi costruite attorno a `const t = useTranslation()` + `t('a.b.c')` sono molto comode ma spesso incoraggiano il mantenimento di grandi oggetti JSON a runtime. Quel modello rende difficile il tree-shaking a meno che la libreria non offra una reale strategia di splitting per pagina.
|
|
84
|
+
|
|
85
|
+
## Metodologia
|
|
86
|
+
|
|
87
|
+
Per questo benchmark, abbiamo confrontato le seguenti librerie:
|
|
88
|
+
|
|
89
|
+
- `Base App` (Nessuna libreria i18n)
|
|
90
|
+
- `react-intlayer` (v8.7.5-canary.0)
|
|
91
|
+
- `react-i18next` (v17.0.2)
|
|
92
|
+
- `use-intl` (v4.9.1)
|
|
93
|
+
- `@lingui/core` (v5.3.0)
|
|
94
|
+
- `@inlang/paraglide-js` (v2.15.1)
|
|
95
|
+
- `tolgee` (v7.0.0)
|
|
96
|
+
- `react-intl` (v10.1.1)
|
|
97
|
+
- `wuchale` (v0.22.11)
|
|
98
|
+
- `gt-react` (vlatest)
|
|
99
|
+
- `lingo.dev` (v0.133.9)
|
|
100
|
+
|
|
101
|
+
Il framework è `TanStack Start` con un'app multilingue di **10 pagine** e **10 lingue**.
|
|
102
|
+
|
|
103
|
+
Abbiamo confrontato **quattro strategie di caricamento**:
|
|
104
|
+
|
|
105
|
+
| Strategia | Senza namespace (globale) | Con namespace (scoped) |
|
|
106
|
+
| :----------------------- | :--------------------------------------------- | :----------------------------------------------------------------- |
|
|
107
|
+
| **Caricamento statico** | **Static**: Tutto in memoria all'avvio. | **Scoped static**: Diviso per namespace; tutto caricato all'avvio. |
|
|
108
|
+
| **Caricamento dinamico** | **Dynamic**: Caricamento on-demand per lingua. | **Scoped dynamic**: Caricamento granulare per namespace e lingua. |
|
|
109
|
+
|
|
110
|
+
## Sintesi delle strategie
|
|
111
|
+
|
|
112
|
+
- **Static**: Semplice; nessuna latenza di rete dopo il caricamento iniziale. Svantaggio: grandi dimensioni del bundle.
|
|
113
|
+
- **Dynamic**: Riduce il peso iniziale (lazy-loading). Ideale quando si hanno molte localizzazioni.
|
|
114
|
+
- **Scoped static**: Mantiene il codice organizzato (separazione logica) senza requisiti di rete complessi.
|
|
115
|
+
- **Scoped dynamic**: Il miglior approccio per il _code splitting_ e le prestazioni. Riduce al minimo la memoria caricando solo ciò di cui la vista corrente e la lingua attiva hanno bisogno.
|
|
116
|
+
|
|
117
|
+
## Risultati nel dettaglio
|
|
118
|
+
|
|
119
|
+
### 1 — Soluzioni da evitare
|
|
120
|
+
|
|
121
|
+
Alcune soluzioni, come `gt-react` o `lingo.dev`, sono chiaramente da evitare. Combinano il vendor lock-in con l'inquinamento della base di codice. Peggio ancora: nonostante molte ore trascorse cercando di implementarle, non sono mai riuscito a farle funzionare correttamente su TanStack Start (come per Next.js con `gt-next`).
|
|
122
|
+
|
|
123
|
+
Problemi riscontrati:
|
|
124
|
+
|
|
125
|
+
**(General Translation)** (`gt-react@latest`):
|
|
126
|
+
|
|
127
|
+
- Per un'app di circa 110kb, `gt-react` può aggiungere più di 440kb extra (ordine di grandezza osservato sull'implementazione Next.js nello stesso benchmark).
|
|
128
|
+
- `Quota Exceeded, please upgrade your plan` al primissimo build con General Translation.
|
|
129
|
+
- Le traduzioni non vengono renderizzate; ottengo l'errore `Error: <T> used on the client-side outside of <GTProvider>`, che sembra essere un bug nella libreria.
|
|
130
|
+
- Durante l'implementazione di **gt-tanstack-start-react**, ho riscontrato anche un [problema](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) con la libreria: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, che causava il crash dell'applicazione. Dopo la segnalazione, il manutentore lo ha risolto in 24 ore.
|
|
131
|
+
- Queste librerie usano un anti-pattern tramite la funzione `initializeGT()`, impedendo al bundle di essere ripulito correttamente tramite tree-shaking.
|
|
132
|
+
|
|
133
|
+
**(Lingo.dev)** (`lingo.dev@0.133.9`):
|
|
134
|
+
|
|
135
|
+
- Quota AI superata (o dipendenza del server bloccata), rendendo build/produzione rischiosi senza pagare.
|
|
136
|
+
- Il compilatore perdeva quasi il 40% del contenuto tradotto. Ho dovuto riscrivere tutti i `.map` in blocchi di componenti piatti per farlo funzionare.
|
|
137
|
+
- La loro CLI è buggata e tendeva a resettare il file di configurazione senza motivo.
|
|
138
|
+
- Alla build, cancellava totalmente i JSON generati quando veniva aggiunto nuovo contenuto. Di conseguenza, poche chiavi potevano cancellare centinaia di chiavi esistenti.
|
|
139
|
+
- Ho riscontrato problemi di reattività con la libreria su TanStack Start: al cambio di lingua ho dovuto forzare il re-rendering del provider per farlo funzionare.
|
|
140
|
+
|
|
141
|
+
### 2 — Soluzioni sperimentali
|
|
142
|
+
|
|
143
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
144
|
+
|
|
145
|
+
L'idea alla base di `Wuchale` è interessante ma non ancora una soluzione praticabile. Ho riscontrato problemi di reattività con questa libreria e ho dovuto forzare il re-rendering del provider per far funzionare l'app su TanStack Start. La documentazione è inoltre piuttosto oscura, il che rende difficile l'onboarding.
|
|
146
|
+
|
|
147
|
+
### 3 — Soluzioni accettabili
|
|
148
|
+
|
|
149
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
150
|
+
|
|
151
|
+
`Paraglide` offre un approccio innovativo e ben ponderato. Tuttavia, in questo benchmark il tree-shaking pubblicizzato non ha funzionato per la mia implementazione Next.js o per TanStack Start. Il workflow e la DX sono inoltre più complessi di altre opzioni. Personalmente non sono un fan del dover rigenerare file JS prima di ogni push, il che crea un rischio costante di conflitti di merge per gli sviluppatori tramite PR.
|
|
152
|
+
|
|
153
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
154
|
+
|
|
155
|
+
`Tolgee` affronta molti dei problemi menzionati in precedenza. L'ho trovato più difficile da avviare rispetto ad altri strumenti con approcci simili. Non fornisce type safety, il che rende anche molto difficile individuare le chiavi mancanti a compile time. Ho dovuto avvolgere le API di Tolgee con le mie per aggiungere il rilevamento delle chiavi mancanti.
|
|
156
|
+
|
|
157
|
+
Su TanStack Start ho avuto anche problemi di reattività: al cambio di lingua, dovevo forzare il provider a rieseguire il rendering e sottoscrivermi agli eventi di cambio lingua in modo che il caricamento in un'altra lingua si comportasse correttamente.
|
|
158
|
+
|
|
159
|
+
**(use-intl)** (`use-intl@4.9.1`):
|
|
160
|
+
|
|
161
|
+
`use-intl` è il pezzo "intl" più alla moda nell'ecosistema React (stessa famiglia di `next-intl`) ed è spesso spinto dagli assistenti AI, ma a mio avviso a torto in un contesto orientato alle prestazioni. Iniziare è abbastanza semplice. In pratica, il processo per ottimizzare e limitare i leakage è piuttosto complesso. Allo stesso modo, combinare caricamento dinamico + namespace + tipi TypeScript rallenta molto lo sviluppo.
|
|
162
|
+
|
|
163
|
+
Su TanStack Start eviti le trappole specifiche di Next.js (`setRequestLocale`, rendering statico), ma il problema di fondo è lo stesso: senza una disciplina rigida, il bundle trasporta rapidamente troppi messaggi e la manutenzione dei namespace per ogni rotta diventa faticosa.
|
|
164
|
+
|
|
165
|
+
**(react-i18next)** (`react-i18next@17.0.2`):
|
|
166
|
+
|
|
167
|
+
`react-i18next` è probabilmente l'opzione più popolare perché è stata tra le prime a soddisfare le esigenze i18n delle app JavaScript. Ha anche un ampio set di plugin della community per problemi specifici.
|
|
168
|
+
|
|
169
|
+
Tuttavia, condivide gli stessi svantaggi principali degli stack basati su `t('a.b.c')`: le ottimizzazioni sono possibili ma richiedono molto tempo, e i grandi progetti rischiano cattive pratiche (namespace + caricamento dinamico + tipi).
|
|
170
|
+
|
|
171
|
+
I formati dei messaggi divergono inoltre: `use-intl` usa ICU MessageFormat, mentre `i18next` usa il proprio formato — il che complica il tooling o le migrazioni se li mescoli.
|
|
172
|
+
|
|
173
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
174
|
+
|
|
175
|
+
`Lingui` è spesso elogiato. Personalmente ho trovato il workflow attorno a `lingui extract` / `lingui compile` più complesso di altri approcci, senza un chiaro vantaggio in questo benchmark su TanStack Start. Ho anche notato sintassi inconsistenti che confondono le AI (es. `t()`, `t''`, `i18n.t()`, `<Trans>`).
|
|
176
|
+
|
|
177
|
+
**(react-intl)** (`react-intl@10.1.1`):
|
|
178
|
+
|
|
179
|
+
`react-intl` è un'implementazione performante del team Format.js. La DX rimane prolissa: `const intl = useIntl()` + `intl.formatMessage({ id: "xx.xx" })` aggiunge complessità, lavoro extra JavaScript e lega l'istanza globale i18n a molti nodi nell'albero React.
|
|
180
|
+
|
|
181
|
+
### 4 — Raccomandazioni
|
|
182
|
+
|
|
183
|
+
Questo benchmark su TanStack Start non ha un equivalente diretto di `next-translate` (plugin Next.js + `getStaticProps`). Per i team che vogliono davvero un'API `t()` con un ecosistema maturo, `react-i18next` e `use-intl` rimangono scelte "ragionevoli", ma preparatevi a investire molto tempo nell'ottimizzazione per evitare leakage.
|
|
184
|
+
|
|
185
|
+
**(Intlayer)** (`react-intlayer@8.7.5-canary.0`):
|
|
186
|
+
|
|
187
|
+
Non giudicherò personalmente `react-intlayer` per motivi di obiettività, essendo la mia propria soluzione.
|
|
188
|
+
|
|
189
|
+
### Nota personale
|
|
190
|
+
|
|
191
|
+
Questa nota è personale e non influisce sui risultati del benchmark. Tuttavia, nel mondo i18n si vede spesso un consenso attorno a un pattern come `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` per il contenuto tradotto.
|
|
192
|
+
|
|
193
|
+
Nelle app React, iniettare una funzione come `ReactNode` è, a mio avviso, un anti-pattern. Inoltre aggiunge una complessità evitabile e un sovraccarico di esecuzione JavaScript (anche se è appena percettibile).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-20
|
|
4
|
+
title: i18nライブラリのベンチマーク
|
|
5
|
+
description: パフォーマンスとバンドルサイズの観点から、Intlayerが他のi18nライブラリとどのように比較されるかをご覧ください。
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- nextjs
|
|
11
|
+
- tanstack
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
history:
|
|
17
|
+
- version: 8.7.5
|
|
18
|
+
date: 2026-01-06
|
|
19
|
+
changes: "ベンチマーク初期化"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Benchmark - レポート
|
|
23
|
+
|
|
24
|
+
Benchmark Bloom は、複数の React フレームワークとロード戦略における i18n(国際化)ライブラリの実環境への影響を測るパフォーマンスベンチマークの一式です。
|
|
25
|
+
|
|
26
|
+
各フレームワークの詳細レポートと技術ドキュメントは次のとおりです。
|
|
27
|
+
|
|
28
|
+
- [**Next.js ベンチマークレポート**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/benchmark/nextjs.md)
|
|
29
|
+
- [**TanStack Start ベンチマークレポート**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ja/benchmark/tanstack.md)
|