@intlayer/docs 8.7.5-canary.0 → 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: Melhor solução i18n para Next.js em 2026 - Relatório de Benchmark
|
|
5
|
+
description: Compare bibliotecas de internacionalização (i18n) para Next.js como next-intl, next-i18next e Intlayer. Relatório detalhado de desempenho sobre tamanho do bundle, vazamento e reatividade.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- nextjs
|
|
11
|
+
- desempenho
|
|
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: "Início do benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Bibliotecas i18n para Next.js — Relatório de Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Esta página é um relatório de benchmark para soluções i18n no Next.js.
|
|
28
|
+
|
|
29
|
+
## Índice
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark Interativo
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="nextjs" vertical/>
|
|
36
|
+
|
|
37
|
+
## Referência de resultados:
|
|
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
|
+
Veja o repositório completo do benchmark [aqui](https://github.com/intlayer-org/benchmark-i18n).
|
|
49
|
+
|
|
50
|
+
## Introdução
|
|
51
|
+
|
|
52
|
+
As bibliotecas de internacionalização têm um impacto pesado na sua aplicação. O principal risco é carregar conteúdo para cada página e cada idioma quando o usuário visita apenas uma página.
|
|
53
|
+
|
|
54
|
+
À medida que sua aplicação cresce, o tamanho do bundle pode crescer exponencialmente, o que pode prejudicar visivelmente o desempenho.
|
|
55
|
+
|
|
56
|
+
Como exemplo, nos piores casos, uma vez internacionalizada, sua página pode acabar ficando quase 4 vezes maior.
|
|
57
|
+
|
|
58
|
+
Outro impacto das bibliotecas i18n é o desenvolvimento mais lento. Transformar componentes em conteúdo multilíngue em todos os idiomas consome muito tempo.
|
|
59
|
+
|
|
60
|
+
Como o problema é difícil, existem muitas soluções — algumas focadas na DX (experiência do desenvolvedor), outras no desempenho ou escalabilidade, e assim por diante.
|
|
61
|
+
|
|
62
|
+
O Intlayer tenta otimizar em todas essas dimensões.
|
|
63
|
+
|
|
64
|
+
## Teste sua aplicação
|
|
65
|
+
|
|
66
|
+
Para trazer à tona esses problemas, construí um scanner gratuito que você pode experimentar [aqui](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
|
+
## O problema
|
|
71
|
+
|
|
72
|
+
Existem duas formas principais de limitar o impacto de uma aplicação multilíngue no seu bundle:
|
|
73
|
+
|
|
74
|
+
- Dividir seu JSON (ou conteúdo) entre arquivos / variáveis / namespaces para que o bundler possa aplicar tree-shaking no conteúdo não utilizado para uma determinada página
|
|
75
|
+
- Carregar dinamicamente o conteúdo da sua página apenas no idioma do usuário
|
|
76
|
+
|
|
77
|
+
Limitações técnicas para essas abordagens:
|
|
78
|
+
|
|
79
|
+
**Carregamento dinâmico**
|
|
80
|
+
|
|
81
|
+
Mesmo quando você declara rotas como `[locale]/page.tsx`, com Webpack ou Turbopack, e mesmo que `generateStaticParams` seja definido, o bundler não trata `locale` como uma constante estática. Isso significa que ele pode puxar conteúdo para todos os idiomas em cada página. A principal forma de limitar isso é carregar o conteúdo através de um import dinâmico (ex: `import('./locales/${locale}.json')`).
|
|
82
|
+
|
|
83
|
+
O que acontece no momento do build é que o Next.js emite um bundle JS por localidade (ex: `./locales_pt_12345.js`). Após o site ser enviado para o cliente, quando a página é executada, o navegador realiza uma requisição HTTP extra para o arquivo JS necessário (ex: `./locales_pt_12345.js`).
|
|
84
|
+
|
|
85
|
+
> Outra forma de abordar o mesmo problema é usar `fetch()` para carregar o JSON dinamicamente. É assim que o `Tolgee` funciona quando o JSON reside em `/public`, ou o `next-translate`, que depende do `getStaticProps` para carregar o conteúdo. O fluxo é o mesmo: o navegador faz uma requisição HTTP extra para carregar o recurso.
|
|
86
|
+
|
|
87
|
+
**Divisão de conteúdo (Content splitting)**
|
|
88
|
+
|
|
89
|
+
Se você usar uma sintaxe como `const t = useTranslation()` + `t('meu-objeto.meu-subobjeto.minha-chave')`, o JSON inteiro geralmente precisa estar no bundle para que a biblioteca possa analisá-lo e resolver a chave. Grande parte desse conteúdo é enviado mesmo quando não é utilizado na página.
|
|
90
|
+
|
|
91
|
+
Para mitigar isso, algumas bibliotecas pedem que você declare por página quais namespaces carregar — ex: `next-i18next`, `next-intl`, `lingui`, `next-translate`, `next-international`.
|
|
92
|
+
|
|
93
|
+
Em contrapartida, o `Paraglide` adiciona um passo extra antes do build para transformar o JSON em símbolos planos como `const en_my_var = () => 'meu valor'`. Em teoria, isso permite o tree-shaking de conteúdo não utilizado na página. Como veremos, esse método ainda tem seus compromissos.
|
|
94
|
+
|
|
95
|
+
Finalmente, o `Intlayer` aplica uma otimização no momento do build para que `useIntlayer('minha-chave')` seja substituído diretamente pelo conteúdo correspondente.
|
|
96
|
+
|
|
97
|
+
## Metodologia
|
|
98
|
+
|
|
99
|
+
Para este benchmark, comparamos as seguintes bibliotecas:
|
|
100
|
+
|
|
101
|
+
- `Base App` (Sem biblioteca 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
|
+
Utilizei a versão `16.2.4` do `Next.js` com o App Router.
|
|
115
|
+
|
|
116
|
+
Construí uma aplicação multilíngue com **10 páginas** e **10 idiomas**.
|
|
117
|
+
|
|
118
|
+
Comparei **quatro estratégias de carregamento**:
|
|
119
|
+
|
|
120
|
+
| Estratégia | Sem namespaces (global) | Com namespaces (escopado) |
|
|
121
|
+
| :------------------------ | :---------------------------------------------------- | :-------------------------------------------------------------------- |
|
|
122
|
+
| **Carregamento estático** | **Static**: Tudo na memória ao iniciar. | **Scoped static**: Dividido por namespace; tudo carregado ao iniciar. |
|
|
123
|
+
| **Carregamento dinâmico** | **Dynamic**: Carregamento sob demanda por localidade. | **Scoped dynamic**: Carregamento granular por namespace e localidade. |
|
|
124
|
+
|
|
125
|
+
## Resumo das estratégias
|
|
126
|
+
|
|
127
|
+
- **Static**: Simples; sem latência de rede após o carregamento inicial. Desvantagem: tamanho grande do bundle.
|
|
128
|
+
- **Dynamic**: Reduz o peso inicial (lazy-loading). Ideal quando se tem muitas localidades.
|
|
129
|
+
- **Scoped static**: Mantém o código organizado (separação lógica) sem requisitos complexos de rede extras.
|
|
130
|
+
- **Scoped dynamic**: Melhor abordagem para _code splitting_ e desempenho. Minimiza a memória carregando apenas o que a visualização atual e a localidade ativa precisam.
|
|
131
|
+
|
|
132
|
+
### O que eu medi:
|
|
133
|
+
|
|
134
|
+
Executei a mesma aplicação multilíngue em um navegador real para cada stack e anotei o que realmente aparecia na rede e quanto tempo as coisas levavam. Os tamanhos são relatados **após a compressão web normal**, porque isso é mais próximo do que as pessoas realmente baixam.
|
|
135
|
+
|
|
136
|
+
- **Tamanho da biblioteca de internacionalização**: Após o bundling, tree-shaking e minificação, o tamanho da biblioteca i18n é o tamanho dos provedores (ex: `NextIntlClientProvider`) + código dos hooks (ex: `useTranslations`) em um componente vazio. Não inclui o carregamento dos arquivos de tradução. Responde a quão cara é a biblioteca antes de seu conteúdo entrar em cena.
|
|
137
|
+
|
|
138
|
+
- **JavaScript por página**: Para cada rota de benchmark, quanto script o navegador puxa para aquela visita, com média entre as páginas da suíte (e entre localidades onde o relatório as agrupa). Páginas pesadas são páginas lentas.
|
|
139
|
+
|
|
140
|
+
- **Vazamento de outras localidades (Leakage)**: É o conteúdo da mesma página, mas em outro idioma, que seria carregado por engano na página auditada. Este conteúdo é desnecessário e deve ser evitado (ex: conteúdo da página `/fr/about` no bundle da página `/en/about`).
|
|
141
|
+
|
|
142
|
+
- **Vazamento de outras rotas**: A mesma ideia para **outras telas** na aplicação: se os textos delas estão presentes quando você abriu apenas uma página (ex: conteúdo da página `/en/about` no bundle da página `/en/contact`). Uma pontuação alta sugere divisão fraca ou bundles excessivamente amplos.
|
|
143
|
+
|
|
144
|
+
- **Tamanho médio do bundle por componente**: Peças comuns da UI são medidas **uma por uma** em vez de se esconderem dentro de um número gigante da aplicação. Isso mostra se a internacionalização infla silenciosamente os componentes do dia a dia. Por exemplo, se seu componente renderizar novamente, ele carregará todos esses dados da memória. Anexar um JSON gigante a qualquer componente é como conectar um grande depósito de dados não utilizados que diminuirá o desempenho dos seus componentes.
|
|
145
|
+
|
|
146
|
+
- **Responsividade ao trocar de idioma**: Eu troco o idioma usando o controle da própria aplicação e cronometro quanto tempo leva até que a página tenha mudado claramente — o que um visitante notaria, não um micro-passo de laboratório.
|
|
147
|
+
|
|
148
|
+
- **Trabalho de renderização após uma mudança de idioma**: Um acompanhamento mais específico: quanto esforço a interface levou para redesenhar para o novo idioma uma vez que a mudança está em andamento. Útil quando o tempo "sentido" e o custo do framework divergem.
|
|
149
|
+
|
|
150
|
+
- **Tempo de carregamento inicial da página**: Da navegação até o navegador considerar a página totalmente carregada para os cenários que testei. Bom para comparar arranques a frio (cold starts).
|
|
151
|
+
|
|
152
|
+
- **Tempo de hidratação (Hydration)**: Quando a aplicação o expõe, quanto tempo o cliente gasta transformando o HTML do servidor em algo que você pode realmente clicar. Um traço nas tabelas significa que aquela implementação não forneceu um valor de hidratação confiável neste benchmark.
|
|
153
|
+
|
|
154
|
+
## Resultados em detalhe
|
|
155
|
+
|
|
156
|
+
### 1 — Soluções a evitar
|
|
157
|
+
|
|
158
|
+
Algumas soluções, como `gt-next` ou `lingo.dev`, devem claramente ser evitadas. Elas combinam o aprisionamento tecnológico (vendor lock-in) com a poluição da sua base de código. Apesar de passar muitas horas tentando implementá-las, nunca consegui fazê-las funcionar — nem no TanStack Start nem no Next.js.
|
|
159
|
+
|
|
160
|
+
Problemas encontrados:
|
|
161
|
+
|
|
162
|
+
**(General Translation)** (`gt-next@6.16.5`):
|
|
163
|
+
|
|
164
|
+
- Para uma aplicação de 110kb, o `gt-react` adiciona mais de 440kb extras.
|
|
165
|
+
- `Quota Exceeded, please upgrade your plan` logo no primeiro build com a General Translation.
|
|
166
|
+
- Traduções não são renderizadas; recebo o erro `Error: <T> used on the client-side outside of <GTProvider>`, o que parece ser um bug na biblioteca.
|
|
167
|
+
- Ao implementar o **gt-tanstack-start-react**, também encontrei um [problema](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) com a biblioteca: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, que fazia a aplicação falhar. Após relatar esse problema, o mantenedor corrigiu-o em 24 horas.
|
|
168
|
+
- A biblioteca bloqueia a renderização estática das páginas do Next.js.
|
|
169
|
+
|
|
170
|
+
**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
|
|
171
|
+
|
|
172
|
+
- Cota de IA excedida, bloqueando a build inteiramente — então você não pode lançar para produção sem pagar.
|
|
173
|
+
- O compilador estava perdendo quase 40% do conteúdo traduzido. Tive que reescrever todos os `.map` em blocos de componentes planos para fazer funcionar.
|
|
174
|
+
- A CLI deles é instável e costumava resetar o arquivo de configuração sem motivo.
|
|
175
|
+
- No build, ele apagava totalmente os JSONs gerados quando um novo conteúdo era adicionado. Como resultado, um punhado de chaves poderia apagar mais de 300 chaves existentes.
|
|
176
|
+
|
|
177
|
+
### 2 — Soluções experimentais
|
|
178
|
+
|
|
179
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
180
|
+
|
|
181
|
+
A ideia por trás do `Wuchale` é interessante, mas ainda não é viável. Encontrei problemas de reatividade e tive que forçar a re-renderização do provedor para fazer a aplicação funcionar. A documentação também é bastante incerta, o que torna a integração mais difícil.
|
|
182
|
+
|
|
183
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
184
|
+
|
|
185
|
+
O `Paraglide` oferece uma abordagem inovadora e bem pensada. Mesmo assim, neste benchmark, o tree-shaking anunciado pela empresa não funcionou para minhas configurações de Next.js ou TanStack Start. O fluxo de trabalho e o DX são mais complexos do que outras opções.
|
|
186
|
+
Pessoalmente, não gosto de ter que regenerar arquivos JS antes de cada push, o que cria um risco constante de conflito de merge via PRs. A ferramenta também parece mais focada no Vite do que no Next.js.
|
|
187
|
+
Finalmente, em comparação com outras soluções, o Paraglide não usa store (ex: React context) para recuperar a localidade atual para renderizar o conteúdo. Para cada nó analisado, ele solicitará a localidade do localStorage / cookie etc. Isso leva à execução de lógica desnecessária que impacta a reatividade do componente.
|
|
188
|
+
|
|
189
|
+
### 3 — Soluções aceitáveis
|
|
190
|
+
|
|
191
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
192
|
+
|
|
193
|
+
O `Tolgee` resolve muitos dos problemas mencionados anteriormente. Achei mais difícil de adotar do que ferramentas semelhantes. Ele não fornece segurança de tipos (type safety), o que também torna mais difícil encontrar chaves ausentes no momento da compilação. Tive que envolver as funções do Tolgee com as minhas para adicionar a detecção de chaves ausentes.
|
|
194
|
+
|
|
195
|
+
**(Next Intl)** (`next-intl@4.9.1`):
|
|
196
|
+
|
|
197
|
+
O `next-intl` é a opção mais badalada e a que os agentes de IA mais recomendam, mas, na minha visão, erradamente. Começar é fácil. Na prática, otimizar para limitar o vazamento é complexo. Combinar carregamento dinâmico + namespaces + tipos TypeScript retarda muito o desenvolvimento. O pacote também é bastante pesado (~13kb para `NextIntlClientProvider` + `useTranslations`, que é mais de 2x o `next-intlayer`). O **next-intl** costumava bloquear a renderização estática das páginas do Next.js. Ele fornece um auxiliar chamado `setRequestLocale()`. Isso parece ter sido parcialmente resolvido para arquivos centralizados como `en.json` / `fr.json`, mas a renderização estática ainda quebra quando o conteúdo é dividido em namespaces como `en/shared.json` / `fr/shared.json` / `es/shared.json`.
|
|
198
|
+
|
|
199
|
+
**(Next I18next)** (`next-i18next@16.0.5`):
|
|
200
|
+
|
|
201
|
+
O `next-i18next` é provavelmente a opção mais popular porque foi uma das primeiras soluções de i18n para aplicações JavaScript. Possui muitos plugins da comunidade. Compartilha os mesmos grandes pontos negativos que o `next-intl`. O pacote é especialmente pesado (~18kb para `I18nProvider` + `useTranslation`, cerca de 3x o `next-intlayer`).
|
|
202
|
+
|
|
203
|
+
Os formatos de mensagem também diferem: o `next-intl` usa ICU MessageFormat, enquanto o `i18next` usa seu próprio formato.
|
|
204
|
+
|
|
205
|
+
**(Next International)** (`next-international@1.3.1`):
|
|
206
|
+
|
|
207
|
+
O `next-international` também aborda os problemas acima, mas não difere muito do `next-intl` ou `next-i18next`. Inclui `scopedT()` para traduções específicas de um namespace, mas usá-lo não tem praticamente impacto no tamanho do bundle.
|
|
208
|
+
|
|
209
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
210
|
+
|
|
211
|
+
O `Lingui` é frequentemente elogiado. Pessoalmente, achei o fluxo de trabalho `lingui extract` / `lingui compile` mais complexo que as alternativas, sem uma vantagem clara. Também notei sintaxes inconsistentes que confundem as IAs (ex: `t()`, `t''`, `i18n.t()`, `<Trans>`).
|
|
212
|
+
|
|
213
|
+
### 4 — Recomendações
|
|
214
|
+
|
|
215
|
+
**(Next Translate)** (`next-translate@3.1.2`):
|
|
216
|
+
|
|
217
|
+
O `next-translate` é minha recomendação principal se você gosta de uma API no estilo `t()`. É elegante via `next-translate-plugin`, carregando namespaces através de `getStaticProps` com um carregador Webpack / Turbopack. É também a opção mais leve aqui (~2.5kb). Para os namespaces, definir os namespaces por página ou rota na configuração é bem pensado e mais fácil de manter do que as principais alternativas como **next-intl** ou **next-i18next**. Na versão `3.1.2`, notei que a renderização estática não funcionava; o Next.js recorria à renderização dinâmica.
|
|
218
|
+
|
|
219
|
+
**(Intlayer)** (`next-intlayer@8.7.5`):
|
|
220
|
+
|
|
221
|
+
Não serei eu a julgar pessoalmente o `next-intlayer` por uma questão de objetividade, já que é a minha própria solução.
|
|
222
|
+
|
|
223
|
+
### Nota pessoal
|
|
224
|
+
|
|
225
|
+
Esta nota é pessoal e não afeta os resultados do benchmark. No mundo da internacionalização, muitas vezes vemos um consenso em torno do padrão `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>`.
|
|
226
|
+
|
|
227
|
+
Em aplicações React, injetar uma função como um `ReactNode` é, na minha visão, um antipadrão. Também adiciona uma complexidade evitável e uma sobrecarga de execução de JavaScript (mesmo que quase imperceptível).
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Melhor solução i18n para TanStack Start em 2026 - Relatório de Benchmark
|
|
5
|
+
description: Compare bibliotecas de internacionalização para TanStack Start como react-i18next, use-intl e Intlayer. Relatório detalhado de desempenho sobre tamanho do bundle, vazamento e reatividade.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- tanstack
|
|
11
|
+
- desempenho
|
|
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: "Início do benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Bibliotecas i18n para TanStack Start — Relatório de Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Esta página é um relatório de benchmark para soluções i18n no TanStack Start.
|
|
28
|
+
|
|
29
|
+
## Índice
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark Interativo
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="tanstack" vertical/>
|
|
36
|
+
|
|
37
|
+
## Referência de resultados:
|
|
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
|
+
Veja o repositório completo do benchmark [aqui](https://github.com/intlayer-org/benchmark-i18n/tree/main).
|
|
49
|
+
|
|
50
|
+
## Introdução
|
|
51
|
+
|
|
52
|
+
As soluções de internacionalização estão entre as dependências mais pesadas em uma aplicação React. No TanStack Start, o principal risco é enviar conteúdo desnecessário: traduções para outras páginas e outras localidades no bundle de uma única rota.
|
|
53
|
+
|
|
54
|
+
À medida que sua aplicação cresce, esse problema pode rapidamente explodir o JavaScript enviado para o cliente e retardar a navegação.
|
|
55
|
+
|
|
56
|
+
Na prática, para as implementações menos otimizadas, uma página internacionalizada pode acabar ficando várias vezes mais pesada do que a versão sem i18n.
|
|
57
|
+
|
|
58
|
+
O outro impacto é na experiência do desenvolvedor: como você declara o conteúdo, tipos, organização de namespaces, carregamento dinâmico e reatividade quando a localidade muda.
|
|
59
|
+
|
|
60
|
+
## Teste sua aplicação
|
|
61
|
+
|
|
62
|
+
Para detectar rapidamente problemas de vazamento de i18n, configurei um scanner gratuito disponível [aqui](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
|
+
## O problema
|
|
67
|
+
|
|
68
|
+
Duas alavancas são essenciais para limitar o custo de uma aplicação multilíngue:
|
|
69
|
+
|
|
70
|
+
- Dividir o conteúdo por página / namespace para não carregar dicionários inteiros quando não precisar deles
|
|
71
|
+
- Carregar a localidade correta dinamicamente, apenas quando necessário
|
|
72
|
+
|
|
73
|
+
Entendendo as limitações técnicas dessas abordagens:
|
|
74
|
+
|
|
75
|
+
**Carregamento dinâmico**
|
|
76
|
+
|
|
77
|
+
Sem o carregamento dinâmico, a maioria das soluções mantém as mensagens na memória desde a primeira renderização, o que adiciona uma sobrecarga significativa para aplicações com muitas rotas e localidades.
|
|
78
|
+
|
|
79
|
+
Com o carregamento dinâmico, você aceita um compromisso: menos JS inicial, mas às vezes uma requisição extra ao mudar o idioma.
|
|
80
|
+
|
|
81
|
+
**Divisão de conteúdo (Content splitting)**
|
|
82
|
+
|
|
83
|
+
As sintaxes construídas em torno de `const t = useTranslation()` + `t('a.b.c')` são muito convenientes, mas muitas vezes incentivam a manutenção de grandes objetos JSON em tempo de execução. Esse modelo torna o tree-shaking difícil, a menos que a biblioteca ofereça uma estratégia real de divisão por página.
|
|
84
|
+
|
|
85
|
+
## Metodologia
|
|
86
|
+
|
|
87
|
+
Para este benchmark, comparamos as seguintes bibliotecas:
|
|
88
|
+
|
|
89
|
+
- `Base App` (Sem biblioteca 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
|
+
O framework é o `TanStack Start` com uma aplicação multilíngue de **10 páginas** e **10 idiomas**.
|
|
102
|
+
|
|
103
|
+
Comparamos **quatro estratégias de carregamento**:
|
|
104
|
+
|
|
105
|
+
| Estratégia | Sem namespaces (global) | Com namespaces (escopado) |
|
|
106
|
+
| :------------------------ | :---------------------------------------------------- | :-------------------------------------------------------------------- |
|
|
107
|
+
| **Carregamento estático** | **Static**: Tudo na memória ao iniciar. | **Scoped static**: Dividido por namespace; tudo carregado ao iniciar. |
|
|
108
|
+
| **Carregamento dinâmico** | **Dynamic**: Carregamento sob demanda por localidade. | **Scoped dynamic**: Carregamento granular por namespace e localidade. |
|
|
109
|
+
|
|
110
|
+
## Resumo das estratégias
|
|
111
|
+
|
|
112
|
+
- **Static**: Simples; sem latência de rede após o carregamento inicial. Desvantagem: tamanho grande do bundle.
|
|
113
|
+
- **Dynamic**: Reduz o peso inicial (lazy-loading). Ideal quando se tem muitas localidades.
|
|
114
|
+
- **Scoped static**: Mantém o código organizado (separação lógica) sem requisitos complexos de rede extras.
|
|
115
|
+
- **Scoped dynamic**: Melhor abordagem para _code splitting_ e desempenho. Minimiza a memória carregando apenas o que a visualização atual e a localidade ativa precisam.
|
|
116
|
+
|
|
117
|
+
## Resultados em detalhe
|
|
118
|
+
|
|
119
|
+
### 1 — Soluções a evitar
|
|
120
|
+
|
|
121
|
+
Algumas soluções, como `gt-react` ou `lingo.dev`, são claramente opções das quais se deve afastar. Elas combinam o aprisionamento tecnológico com a poluição da sua base de código. Pior: apesar de passar muitas horas tentando implementá-las, nunca consegui fazê-las funcionar corretamente no TanStack Start (semelhante ao Next.js com `gt-next`).
|
|
122
|
+
|
|
123
|
+
Problemas encontrados:
|
|
124
|
+
|
|
125
|
+
**(General Translation)** (`gt-react@latest`):
|
|
126
|
+
|
|
127
|
+
- Para uma app de cerca de 110kb, o `gt-react` pode adicionar mais de 440kb extras (ordem de grandeza vista na implementação do Next.js no mesmo benchmark).
|
|
128
|
+
- `Quota Exceeded, please upgrade your plan` logo no primeiro build com a General Translation.
|
|
129
|
+
- Traduções não são renderizadas; recebo o erro `Error: <T> used on the client-side outside of <GTProvider>`, o que parece ser um bug na biblioteca.
|
|
130
|
+
- Ao implementar o **gt-tanstack-start-react**, também encontrei um [problema](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) com a biblioteca: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, que fazia a aplicação falhar. Após relatar esse problema, o mantenedor corrigiu-o em 24 horas.
|
|
131
|
+
- Essas bibliotecas usam um antipadrão através da função `initializeGT()`, impedindo que o bundle seja limpo corretamente via tree-shaking.
|
|
132
|
+
|
|
133
|
+
**(Lingo.dev)** (`lingo.dev@0.133.9`):
|
|
134
|
+
|
|
135
|
+
- Cota de IA excedida (ou dependência de servidor bloqueada), tornando o build / produção arriscado sem pagar.
|
|
136
|
+
- O compilador estava perdendo quase 40% do conteúdo traduzido. Tive que reescrever todos os `.map` em blocos de componentes planos para fazer funcionar.
|
|
137
|
+
- A CLI deles é instável e costumava resetar o arquivo de configuração sem motivo.
|
|
138
|
+
- No build, ele apagava totalmente os JSONs gerados quando havia novo conteúdo adicionado. Como resultado, você poderia terminar com apenas algumas chaves apagando centenas de chaves existentes.
|
|
139
|
+
- Encontrei problemas de reatividade com a biblioteca no TanStack Start: na mudança de localidade tive que forçar a re-renderização do provedor para fazer funcionar.
|
|
140
|
+
|
|
141
|
+
### 2 — Soluções experimentais
|
|
142
|
+
|
|
143
|
+
**(Wuchale)** (`wuchale@0.22.11`):
|
|
144
|
+
|
|
145
|
+
A ideia por trás do `Wuchale` é interessante, mas ainda não é uma solução viável. Encontrei problemas de reatividade com a biblioteca e tive que forçar a re-renderização do provedor para fazer a aplicação funcionar no TanStack Start. A documentazione é também bastante incerta, o que torna a integração mais difícil.
|
|
146
|
+
|
|
147
|
+
### 3 — Soluções aceitáveis
|
|
148
|
+
|
|
149
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):
|
|
150
|
+
|
|
151
|
+
O `Paraglide` oferece uma abordagem inovadora e bem pensada. Mesmo assim, neste benchmark, o tree-shaking anunciado pela empresa não funcionou para minha implementação no Next.js ou para o TanStack Start. O fluxo de trabalho e o DX também são mais complexos do que outras opções. Pessoalmente, não sou fã de ter que regenerar arquivos JS antes de cada push, o que cria um risco constante de conflitos de merge para os desenvolvedores via PRs.
|
|
152
|
+
|
|
153
|
+
**(Tolgee)** (`tolgee@7.0.0`):
|
|
154
|
+
|
|
155
|
+
O `Tolgee` resolve muitos dos problemas mencionados anteriormente. Achei mais difícil de começar com ele do que com outras ferramentas com abordagens semelhantes. Ele não fornece segurança de tipos, o que também torna muito difícil encontrar chaves ausentes no momento da compilação. Tive que envolver as APIs do Tolgee com as minhas para adicionar a detecção de chaves ausentes.
|
|
156
|
+
|
|
157
|
+
No TanStack Start também tive problemas de reatividade: na mudança de localidade, tive que forçar o provedor a renderizar novamente e me inscrever em eventos de mudança de localidade para que o carregamento em outro idioma se comportasse corretamente.
|
|
158
|
+
|
|
159
|
+
**(use-intl)** (`use-intl@4.9.1`):
|
|
160
|
+
|
|
161
|
+
O `use-intl` é a peça "intl" mais badalada no ecossistema React (mesma família do `next-intl`) e é frequentemente recomendada por agentes de IA, mas, na minha visão, erroneamente em um ambiente focado em desempenho. Começar é bastante simples. Na prática, o processo para otimizar e limitar o vazamento é bastante complexo. Da mesma forma, combinar carregamento dinâmico + namespaces + tipos TypeScript retarda muito o desenvolvimento.
|
|
162
|
+
|
|
163
|
+
No TanStack Start, você evita as armadilhas específicas do Next.js (`setRequestLocale`, renderização estática), mas o problema principal é o mesmo: sem uma disciplina rigorosa, o bundle rapidamente carrega muitas mensagens e a manutenção de namespaces por rota torna-se penosa.
|
|
164
|
+
|
|
165
|
+
**(react-i18next)** (`react-i18next@17.0.2`):
|
|
166
|
+
|
|
167
|
+
O `react-i18next` é provavelmente a opção mais popular porque foi uma das primeiras a atender às necessidades de i18n de aplicações JavaScript. Também possui um amplo conjunto de plugins da comunidade para problemas específicos.
|
|
168
|
+
|
|
169
|
+
Ainda assim, compartilha os mesmos grandes pontos negativos que as stacks baseadas em `t('a.b.c')`: as otimizações são possíveis, mas consomem muito tempo, e grandes projetos correm o risco de cair em más práticas (namespaces + carregamento dinâmico + tipos).
|
|
170
|
+
|
|
171
|
+
Os formatos de mensagem também divergem: o `use-intl` usa ICU MessageFormat, enquanto o `i18next` usa seu próprio formato — o que complica o ferramental ou migrações se você os misturar.
|
|
172
|
+
|
|
173
|
+
**(Lingui)** (`@lingui/core@5.3.0`):
|
|
174
|
+
|
|
175
|
+
O `Lingui` é frequentemente elogiado. Pessoalmente, achei o fluxo de trabalho em torno de `lingui extract` / `lingui compile` mais complexo do que outras abordagens, sem uma vantagem clara neste benchmark do TanStack Start. Também notei sintaxes inconsistentes que confundem as IAs (ex: `t()`, `t''`, `i18n.t()`, `<Trans>`).
|
|
176
|
+
|
|
177
|
+
**(react-intl)** (`react-intl@10.1.1`):
|
|
178
|
+
|
|
179
|
+
O `react-intl` é uma implementação de alto desempenho da equipe do Format.js. O DX permanece prolixo: `const intl = useIntl()` + `intl.formatMessage({ id: "xx.xx" })` adiciona complexidade, trabalho extra de JavaScript e vincula a instância global de i18n a muitos nós na árvore React.
|
|
180
|
+
|
|
181
|
+
### 4 — Recomendações
|
|
182
|
+
|
|
183
|
+
Este benchmark do TanStack Start não possui um equivalente direto do `next-translate` (plugin Next.js + `getStaticProps`). Para as equipes que realmente desejam uma API `t()` com um ecossistema maduro, o `react-i18next` e o `use-intl` continuam sendo escolhas "razoáveis", mas espere investir muito tempo otimizando para evitar vazamentos.
|
|
184
|
+
|
|
185
|
+
**(Intlayer)** (`react-intlayer@8.7.5-canary.0`):
|
|
186
|
+
|
|
187
|
+
Não serei eu a julgar pessoalmente o `react-intlayer` por uma questão de objetividade, já que é a minha própria solução.
|
|
188
|
+
|
|
189
|
+
### Nota pessoal
|
|
190
|
+
|
|
191
|
+
Esta nota é pessoal e não afeta os resultados do benchmark. Ainda assim, no mundo da internacionalização, muitas vezes vemos um consenso em torno de um padrão como `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` para conteúdo traduzido.
|
|
192
|
+
|
|
193
|
+
Em aplicações React, injetar uma função como um `ReactNode` é, na minha visão, um antipadrão. Também adiciona uma complexidade evitabile e uma sobrecarga de execução de JavaScript (mesmo que quase imperceptível).
|
|
@@ -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 — это набор тестов производительности, который измеряет реальное влияние библиотек i18n (интернационализации) в разных React-фреймворках и при разных стратегиях загрузки.
|
|
25
|
+
|
|
26
|
+
Ниже — подробные отчёты и техническая документация по каждому фреймворку:
|
|
27
|
+
|
|
28
|
+
- [**Отчёт о бенчмарке Next.js**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/benchmark/nextjs.md)
|
|
29
|
+
- [**Отчёт о бенчмарке TanStack Start**](https://github.com/aymericzip/intlayer/blob/main/docs/docs/ru/benchmark/tanstack.md)
|