@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
|
@@ -61,6 +61,13 @@ Como o problema é difícil, existem muitas soluções — algumas focadas na DX
|
|
|
61
61
|
|
|
62
62
|
O Intlayer tenta otimizar em todas essas dimensões.
|
|
63
63
|
|
|
64
|
+
## TL;DR
|
|
65
|
+
|
|
66
|
+
- **Intlayer** e **next-translate**: As melhores escolhas para o desempenho do Next.js, oferecendo a menor pegada e o melhor suporte para renderização estática.
|
|
67
|
+
- **next-intl**: A opção mais badalada, mas pesada e complexa de otimizar para grandes aplicações.
|
|
68
|
+
- **next-i18next**: Popular e rico em plugins, mas carrega um peso significativo de bundle (~3× Intlayer).
|
|
69
|
+
- **Evite**: **gt-next** e **lingo.dev** devido a problemas graves de desempenho, aprisionamento tecnológico (vendor lock-in) e erros que quebram a build.
|
|
70
|
+
|
|
64
71
|
## Teste sua aplicação
|
|
65
72
|
|
|
66
73
|
Para trazer à tona esses problemas, construí um scanner gratuito que você pode experimentar [aqui](https://intlayer.org/i18n-seo-scanner).
|
|
@@ -99,14 +106,14 @@ Finalmente, o `Intlayer` aplica uma otimização no momento do build para que `u
|
|
|
99
106
|
Para este benchmark, comparamos as seguintes bibliotecas:
|
|
100
107
|
|
|
101
108
|
- `Base App` (Sem biblioteca i18n)
|
|
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 @@ Problemas encontrados:
|
|
|
161
168
|
|
|
162
169
|
**(General Translation)** (`gt-next@6.16.5`):
|
|
163
170
|
|
|
164
|
-
- Para uma aplicação de 110kb, o `gt-
|
|
171
|
+
- Para uma aplicação de 110kb, o `gt-next` adiciona mais de 440kb extras.
|
|
165
172
|
- `Quota Exceeded, please upgrade your plan` logo no primeiro build com a General Translation.
|
|
166
173
|
- 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-
|
|
174
|
+
- Ao implementar o **gt-next**, também encontrei um [problema](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) con la 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
175
|
- A biblioteca bloqueia a renderização estática das páginas do Next.js.
|
|
169
176
|
|
|
170
177
|
**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):
|
|
@@ -184,11 +191,13 @@ A ideia por trás do `Wuchale` é interessante, mas ainda não é viável. Encon
|
|
|
184
191
|
|
|
185
192
|
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
193
|
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
|
|
194
|
+
Finalmente, em comparação com outras soluções, o Paraglide não usa store (ex: React context) para recuperar a localidade atual para renderizar le 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.
|
|
195
|
+
|
|
196
|
+
> Nota sobre paraglide: a solução injeta código na sua base de código para importação, como resultado a métrica 'tamanho da lib' no relatório de benchmark é quase 0. A geração de código é algo bom, porque a função utilizada incluirá apenas a lógica necessária (prefixo total vs sem prefixo, cookie vs armazenamento, etc.). Em comparação, o Intlayer executa essa filtragem via inyeções de variáveis de ambiente na build para forçar o bundler a aplicar tree-shaking ao conteúdo dependendo da lógica. Graças a isso, paraglide e intlayer acabam sendo soluções de 6 a 10 vezes mais leves do que i18next ou next-intl.
|
|
188
197
|
|
|
189
198
|
### 3 — Soluções aceitáveis
|
|
190
199
|
|
|
191
|
-
**(Tolgee)** (
|
|
200
|
+
**(Tolgee)** (`@tolgee/react@7.0.0`):
|
|
192
201
|
|
|
193
202
|
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
203
|
|
|
@@ -216,7 +225,7 @@ O `Lingui` é frequentemente elogiado. Pessoalmente, achei o fluxo de trabalho `
|
|
|
216
225
|
|
|
217
226
|
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
227
|
|
|
219
|
-
**(Intlayer)** (`next-intlayer@8.7.
|
|
228
|
+
**(Intlayer)** (`next-intlayer@8.7.12`):
|
|
220
229
|
|
|
221
230
|
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
231
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Melhor solução i18n para Solid em 2026 - Relatório de Benchmark
|
|
5
|
+
description: Compare bibliotecas de internacionalização (i18n) para Solid como solid-primitives, solid-i18next e Intlayer. Relatório de desempenho detalhado sobre tamanho do bundle, vazamento e reatividade.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- solid
|
|
11
|
+
- desempenho
|
|
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: "Inicialização do benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Bibliotecas i18n para Solid — Relatório de Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Esta página é um relatório de benchmark para soluções i18n no Solid.
|
|
28
|
+
|
|
29
|
+
## Índice
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark Interativo
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="vite-solid" 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-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
|
+
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 um app Solid. O principal risco é enviar conteúdo desnecessário: traduções para outras páginas e outros locais no bundle de uma única rota.
|
|
53
|
+
|
|
54
|
+
À medida que seu app cresce, esse problema pode rapidamente explodir o JavaScript enviado ao cliente e tornar a navegação lenta.
|
|
55
|
+
|
|
56
|
+
Na prática, para as implementações menos otimizadas, uma página internacionalizada pode acabar sendo várias vezes mais pesada do que a versão sem i18n.
|
|
57
|
+
|
|
58
|
+
O outro impacto é na experiência do desenvolvedor (DX): como você declara conteúdo, tipos, organização de namespaces, carregamento dinâmico e reatividade quando o local muda.
|
|
59
|
+
|
|
60
|
+
## TL;DR
|
|
61
|
+
|
|
62
|
+
- **Intlayer**: Escolha recomendada para aplicações Solid profissionais que precisam de recursos avançados e otimização (v8.7.12).
|
|
63
|
+
- **@solid-primitives/i18n**: Excelente alternativa leve para projetos simples, embora careça de recursos avançados como lazy loading.
|
|
64
|
+
- **solid-i18next**: Opção padrão, mas pesada (~4.7× o Intlayer) com os mesmos pontos negativos do React i18next.
|
|
65
|
+
- **Paraglide**: Abordagem inovadora, mas DX complexa e problemas de tree-shaking em algumas configurações.
|
|
66
|
+
|
|
67
|
+
## Teste seu app
|
|
68
|
+
|
|
69
|
+
Para identificar rapidamente problemas de vazamento de i18n, configurei um scanner gratuito disponível [aqui](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
|
+
## O problema
|
|
74
|
+
|
|
75
|
+
Duas alavancas são essenciais para limitar o custo de um app multilíngue:
|
|
76
|
+
|
|
77
|
+
- Dividir o conteúdo por página / namespace para não carregar dicionários inteiros quando não for necessário.
|
|
78
|
+
- Carregar o local correto dinamicamente, apenas quando necessário.
|
|
79
|
+
|
|
80
|
+
Entendendo as limitações técnicas dessas abordagens:
|
|
81
|
+
|
|
82
|
+
**Carregamento dinâmico**
|
|
83
|
+
|
|
84
|
+
Sem carregamento dinâmico, a maioria das soluções mantém as mensagens na memória desde o primeiro render, o que adiciona um overhead significativo para apps com muitas rotas e locais.
|
|
85
|
+
|
|
86
|
+
Com carregamento dinâmico, você aceita uma troca: menos JS inicial, mas às vezes uma requisição extra ao trocar de idioma.
|
|
87
|
+
|
|
88
|
+
**Divisão de conteúdo (Splitting)**
|
|
89
|
+
|
|
90
|
+
Sintaxes construídas em torno de `t('a.b.c')` são muito convenientes, mas frequentemente 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.
|
|
91
|
+
|
|
92
|
+
## Metodologia
|
|
93
|
+
|
|
94
|
+
Para este benchmark, comparamos as seguintes bibliotecas:
|
|
95
|
+
|
|
96
|
+
- `Base App` (Sem biblioteca i18n)
|
|
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
|
+
O framework é `Solid` com um app multilíngue de **10 páginas** e **10 idiomas**.
|
|
103
|
+
|
|
104
|
+
Comparamos **quatro estratégias de carregamento**:
|
|
105
|
+
|
|
106
|
+
| Estratégia | Sem namespaces (global) | Com namespaces (scoped) |
|
|
107
|
+
| :------------------------ | :----------------------------------------------- | :-------------------------------------------------------------------- |
|
|
108
|
+
| **Carregamento estático** | **Static**: Tudo na memória ao iniciar. | **Scoped static**: Dividido por namespace; tudo carregado ao iniciar. |
|
|
109
|
+
| **Carregamento dinâmico** | **Dynamic**: Carregamento sob demanda por local. | **Scoped dynamic**: Carregamento granular por namespace e local. |
|
|
110
|
+
|
|
111
|
+
## Resumo das estratégias
|
|
112
|
+
|
|
113
|
+
- **Static**: Simples; sem latência de rede após o carregamento inicial. Desvantagem: grande tamanho de bundle.
|
|
114
|
+
- **Dynamic**: Reduz o peso inicial (lazy-loading). Ideal quando você tem muitos locais.
|
|
115
|
+
- **Scoped static**: Mantém o código organizado (separação lógica) sem requisições de rede extras complexas.
|
|
116
|
+
- **Scoped dynamic**: Melhor abordagem para _code splitting_ e desempenho. Minimiza a memória carregando apenas o que a view atual e o local ativo precisam.
|
|
117
|
+
|
|
118
|
+
## Resultados em detalhes
|
|
119
|
+
|
|
120
|
+
### 1 — Soluções a evitar
|
|
121
|
+
|
|
122
|
+
> Nenhuma solução clara a evitar no ecossistema Solid.
|
|
123
|
+
|
|
124
|
+
### 2 — Soluções aceitáveis
|
|
125
|
+
|
|
126
|
+
**(solid-i18next)** (`solid-i18next@17.0.2`):
|
|
127
|
+
|
|
128
|
+
O `solid-i18next` é provavelmente a opção mais popular porque foi uma das primeiras a atender às necessidades de i18n de apps JavaScript. Também possui um amplo conjunto de plugins da comunidade para problemas específicos.
|
|
129
|
+
|
|
130
|
+
O pacote é pesado (~14.6kb, o que é cerca de 4.7× o `solid-intlayer`).
|
|
131
|
+
|
|
132
|
+
Ainda assim, compartilha as mesmas principais desvantagens das stacks construídas sobre o `t('a.b.c')`: otimizações são possíveis, mas consomem muito tempo, e grandes projetos correm o risco de más práticas (namespaces + carregamento dinâmico + tipos).
|
|
133
|
+
|
|
134
|
+
**(@solid-primitives/i18n)** (`@solid-primitives/i18n@2.2.1`):
|
|
135
|
+
|
|
136
|
+
O Solid primitive é extremamente leve e eficiente. Recomendo essa solução para projetos leves, mas pode rapidamente carecer de recursos para soluções profissionais que incluam gerenciamento de cookies, redirecionamento de proxy, formatadores etc.
|
|
137
|
+
Também carece de lazy loading e scoping de namespaces para otimização do tamanho da página.
|
|
138
|
+
|
|
139
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.17.0`):
|
|
140
|
+
|
|
141
|
+
O `Paraglide` oferece uma abordagem inovadora e bem pensada. Ainda assim, neste benchmark, o tree-shaking que a empresa anuncia não funcionou para minha implementação. O fluxo de trabalho e a DX também são mais complexos do que outras opções.
|
|
142
|
+
Pessoalmente, não gosto de ter que regenerar arquivos JS antes de cada push, o que cria um risco constante de conflito de merge através de PRs.
|
|
143
|
+
Finalmente, em comparação com outras soluções, o Paraglide não usa um store (ex: Solid signal) para recuperar o local atual para renderizar o conteúdo. Para cada nó analisado, ele solicitará o local do localStorage / cookie etc. Isso leva à execução de lógica desnecessária que impacta a reatividade do componente.
|
|
144
|
+
|
|
145
|
+
### 3 — Recomendações
|
|
146
|
+
|
|
147
|
+
**(Intlayer)** (`solid-intlayer@8.7.12`):
|
|
148
|
+
|
|
149
|
+
Eu não julgarei pessoalmente o `solid-intlayer` por uma questão de objetividade, já que é minha própria solução.
|
|
150
|
+
|
|
151
|
+
### Nota pessoal
|
|
152
|
+
|
|
153
|
+
Esta nota é pessoal e não afeta os resultados do benchmark. Ainda assim, no mundo do i18n, você costuma ver consenso em torno de um padrão como `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` para o conteúdo traduzido.
|
|
154
|
+
|
|
155
|
+
Em apps Solid, injetar uma função como um `JSX.Element` é, na minha opinião, um antipadrão. Também adiciona complexidade evitável e overhead de execução de JavaScript (mesmo que seja quase imperceptível).
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Melhor solução i18n para Svelte em 2026 - Relatório de Benchmark
|
|
5
|
+
description: Compare bibliotecas de internacionalização (i18n) para Svelte como svelte-i18n, Paraglide e Intlayer. Relatório de desempenho detalhado sobre tamanho do bundle, vazamento e reatividade.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- svelte
|
|
11
|
+
- desempenho
|
|
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: "Inicialização do benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Bibliotecas i18n para Svelte — Relatório de Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Esta página é um relatório de benchmark para soluções i18n no Svelte.
|
|
28
|
+
|
|
29
|
+
## Índice
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark Interativo
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="vite-svelte" 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-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
|
+
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 um app Svelte. O principal risco é enviar conteúdo desnecessário: traduções para outras páginas e outros locais no bundle de uma única rota.
|
|
53
|
+
|
|
54
|
+
À medida que seu app cresce, esse problema pode rapidamente explodir o JavaScript enviado ao cliente e tornar a navegação lenta.
|
|
55
|
+
|
|
56
|
+
Na prática, para as implementações menos otimizadas, uma página internacionalizada pode acabar sendo várias vezes mais pesada do que a versão sem i18n.
|
|
57
|
+
|
|
58
|
+
O outro impacto é na experiência do desenvolvedor (DX): como você declara conteúdo, tipos, organização de namespaces, carregamento dinâmico e reatividade quando o local muda.
|
|
59
|
+
|
|
60
|
+
## TL;DR
|
|
61
|
+
|
|
62
|
+
- **Intlayer**: A escolha mais eficiente em desempenho (v8.7.12) com o menor footprint.
|
|
63
|
+
- **Paraglide**: Forte candidato para tree-shaking, mas possui uma experiência de desenvolvedor mais complexa e overhead de reatividade.
|
|
64
|
+
- **svelte-i18n**: Abrangente e padrão para Svelte, mas carrega um peso de bundle muito maior (~7× o Intlayer).
|
|
65
|
+
|
|
66
|
+
## Teste seu app
|
|
67
|
+
|
|
68
|
+
Para identificar rapidamente problemas de vazamento de i18n, configurei um scanner gratuito disponível [aqui](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
|
+
## O problema
|
|
73
|
+
|
|
74
|
+
Duas alavancas são essenciais para limitar o custo de um app multilíngue:
|
|
75
|
+
|
|
76
|
+
- Dividir o conteúdo por página / namespace para no carregar dicionários inteiros quando não for necessário.
|
|
77
|
+
- Carregar o local correto dinamicamente, apenas quando necessário.
|
|
78
|
+
|
|
79
|
+
Entendendo as limitações técnicas dessas abordagens:
|
|
80
|
+
|
|
81
|
+
**Carregamento dinâmico**
|
|
82
|
+
|
|
83
|
+
Sem carregamento dinâmico, a maioria das soluções mantém as mensagens na memória desde o primeiro render, o que adiciona um overhead significativo para apps com muitas rotas e locais.
|
|
84
|
+
|
|
85
|
+
Com carregamento dinâmico, você aceita uma troca: menos JS inicial, mas às vezes uma requisição extra ao trocar de idioma.
|
|
86
|
+
|
|
87
|
+
**Divisão de conteúdo (Splitting)**
|
|
88
|
+
|
|
89
|
+
Sintaxes construídas em torno de `t('a.b.c')` são muito convenientes, mas frequentemente 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.
|
|
90
|
+
|
|
91
|
+
## Metodologia
|
|
92
|
+
|
|
93
|
+
Para este benchmark, comparamos as seguintes bibliotecas:
|
|
94
|
+
|
|
95
|
+
- `Base App` (Sem biblioteca i18n)
|
|
96
|
+
- `svelte-intlayer` (v8.7.12)
|
|
97
|
+
- `svelte-i18n` (v4.0.1)
|
|
98
|
+
- `@inlang/paraglide-js` (v2.17.0)
|
|
99
|
+
|
|
100
|
+
O framework é `Svelte` com um app multilíngue de **10 páginas** e **10 idiomas**.
|
|
101
|
+
|
|
102
|
+
Comparamos **quatro estratégias de carregamento**:
|
|
103
|
+
|
|
104
|
+
| Estratégia | Sem namespaces (global) | Com namespaces (scoped) |
|
|
105
|
+
| :------------------------ | :----------------------------------------------- | :-------------------------------------------------------------------- |
|
|
106
|
+
| **Carregamento estático** | **Static**: Tudo na memória ao iniciar. | **Scoped static**: Dividido por namespace; tudo carregado ao iniciar. |
|
|
107
|
+
| **Carregamento dinâmico** | **Dynamic**: Carregamento sob demanda por local. | **Scoped dynamic**: Carregamento granular por namespace e local. |
|
|
108
|
+
|
|
109
|
+
## Resumo das estratégias
|
|
110
|
+
|
|
111
|
+
- **Static**: Simples; sem latência de rede após o carregamento inicial. Desvantagem: grande tamanho de bundle.
|
|
112
|
+
- **Dynamic**: Reduz o peso inicial (lazy-loading). Ideal quando você tem muitos locais.
|
|
113
|
+
- **Scoped static**: Mantém o código organizado (separação lógica) sem requisições de rede extras complexas.
|
|
114
|
+
- **Scoped dynamic**: Melhor abordagem para _code splitting_ e desempenho. Minimiza a memória carregando apenas o que a view atual e o local ativo precisam.
|
|
115
|
+
|
|
116
|
+
## Resultados em detalhes
|
|
117
|
+
|
|
118
|
+
### 1 — Soluções a evitar
|
|
119
|
+
|
|
120
|
+
> Nenhuma solução clara a evitar no ecossistema Svelte.
|
|
121
|
+
|
|
122
|
+
### 2 — Soluções aceitáveis
|
|
123
|
+
|
|
124
|
+
**(Paraglide)** (`@inlang/paraglide-js@2.17.0`):
|
|
125
|
+
|
|
126
|
+
O `Paraglide` oferece uma abordagem inovadora e bem pensada. No contexto de um app Vite + Svelte, o tree-shaking que a empresa anuncia funcionou conforme o esperado, o que é excelente.
|
|
127
|
+
Mas no caso do React + TanStack Start, o tree-shaking não funcionou como esperado, o mesmo para o Next.js. Dito isso, o uso do Paraglide em um projeto Svelte e TanStack Start valeria uma dupla verificação.
|
|
128
|
+
O fluxo de trabalho e a DX também são mais complexos do que outras opções.
|
|
129
|
+
Pessoalmente, não gosto de ter que regenerar arquivos JS antes de cada push, o que cria um risco constante de conflito de merge através de PRs. A ferramenta também parece mais focada no Vite do que no Next.js.
|
|
130
|
+
Finalmente, em comparação com outras soluções, o Paraglide não usa um store (ex: Svelte store) para recuperar o local atual para renderizar o conteúdo. Para cada nó analisado, ele solicitará o local do localStorage / cookie etc. Isso leva à execução de lógica desnecessária que impacta a reatividade do componente.
|
|
131
|
+
|
|
132
|
+
> Nota sobre o paraglide: a solução injeta código em sua base de código para importações; como resultado, a métrica 'lib size' no relatório de benchmark é quase 0. A geração de código é algo bom, porque a função utilizada incluirá apenas a lógica necessária (prefixo em todos os lugares vs sem prefixo, cookie vs armazenamento, etc.). Em comparação, o Intlayer realiza essa filtragem via injeções de variáveis de ambiente durante o build para forçar o bundler a fazer tree-shaking do conteúdo dependendo da lógica. Graças a isso, o paraglide e o intlayer acabam sendo soluções de 6 a 10 vezes mais leves que o i18next ou o next-intl.
|
|
133
|
+
|
|
134
|
+
**(svelte-i18n)** (`svelte-i18n@3.4.0`):
|
|
135
|
+
|
|
136
|
+
Esta solução atende a todas as necessidades de i18n em um projeto Svelte. Mas, como é o caso do i18next ou de outras grandes soluções de i18n, é um pouco pesada (~15.9kb, o que é cerca de 7× o `svelte-intlayer`).
|
|
137
|
+
|
|
138
|
+
### 3 — Recomendações
|
|
139
|
+
|
|
140
|
+
**(Intlayer)** (`svelte-intlayer@8.7.12`):
|
|
141
|
+
|
|
142
|
+
Eu não julgarei pessoalmente o `svelte-intlayer` por uma questão de objetividade, já que é minha própria solução.
|
|
143
|
+
|
|
144
|
+
### Nota pessoal
|
|
145
|
+
|
|
146
|
+
Esta nota é pessoal e não afeta os resultados do benchmark. Ainda assim, no mundo do i18n, você costuma ver consenso em torno de um padrão como `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` para o conteúdo traduzido.
|
|
147
|
+
|
|
148
|
+
Em apps Svelte, injetar uma função como um `Slot` é, na minha opinião, um antipadrão. Também adiciona complexidade evitável e overhead de execução de JavaScript (mesmo que seja quase imperceptível).
|
|
@@ -55,7 +55,14 @@ As soluções de internacionalização estão entre as dependências mais pesada
|
|
|
55
55
|
|
|
56
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
57
|
|
|
58
|
-
O outro impacto é na
|
|
58
|
+
O outro impacto é na experê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
|
+
## TL;DR
|
|
61
|
+
|
|
62
|
+
- **Intlayer**: Oferece o melhor desempenho e o menor tamanho de bundle (v8.7.12) para TanStack Start.
|
|
63
|
+
- **react-i18next** & **use-intl**: Alternativas maduras com grandes ecossistemas, mas significativamente mais pesadas e complexas de otimizar.
|
|
64
|
+
- **Paraglide**: Ideia inovadora de tree-shaking que não funciona na prática. DX complexo e sobrecarga de reatividade no TanStack Start.
|
|
65
|
+
- **Evitar**: **General Translation (GT)** e **Lingo.dev** devido a graves problemas de desempenho, limites de cota de IA e bloqueio do fornecedor (vendor lock-in).
|
|
59
66
|
|
|
60
67
|
## Teste sua aplicação
|
|
61
68
|
|
|
@@ -87,12 +94,12 @@ As sintaxes construídas em torno de `const t = useTranslation()` + `t('a.b.c')`
|
|
|
87
94
|
Para este benchmark, comparamos as seguintes bibliotecas:
|
|
88
95
|
|
|
89
96
|
- `Base App` (Sem biblioteca i18n)
|
|
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 @@ A ideia por trás do `Wuchale` é interessante, mas ainda não é uma solução
|
|
|
150
157
|
|
|
151
158
|
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
159
|
|
|
153
|
-
|
|
160
|
+
> Nota sobre o paraglide: a solução injeta código na sua base de código para as importações; como resultado, a métrica 'lib size' no relatório de benchmark é quase 0. A geração de código é algo bom, porque a função utilizada incluirá apenas a lógica necessária (prefixo em todos os lugares vs. sem prefixo, cookie vs. armazenamento, etc.). Em comparação, o Intlayer realiza essa filtragem via injeções de variáveis de ambiente no build para forçar o bundler a fazer o tree-shaking do conteúdo dependendo da lógica. Graças a isso, o paraglide e o intlayer acabam sendo soluções 6 a 10 vezes mais leves que o i18next ou o next-intl.
|
|
161
|
+
|
|
162
|
+
**(Tolgee)** (`@tolgee/react@7.0.0`):
|
|
154
163
|
|
|
155
164
|
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
165
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2026-04-20
|
|
3
|
+
updatedAt: 2026-04-21
|
|
4
|
+
title: Melhor solução i18n para Vue em 2026 - Relatório de Benchmark
|
|
5
|
+
description: Compare bibliotecas de internacionalização (i18n) para Vue como vue-i18n, fluent-vue e Intlayer. Relatório de desempenho detalhado sobre tamanho do bundle, vazamento e reatividade.
|
|
6
|
+
keywords:
|
|
7
|
+
- benchmark
|
|
8
|
+
- i18n
|
|
9
|
+
- intl
|
|
10
|
+
- vue
|
|
11
|
+
- desempenho
|
|
12
|
+
- intlayer
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- benchmark
|
|
16
|
+
- vue
|
|
17
|
+
author: Aymeric PINEAU
|
|
18
|
+
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n-vue-template
|
|
19
|
+
history:
|
|
20
|
+
- version: 8.7.12
|
|
21
|
+
date: 2026-01-06
|
|
22
|
+
changes: "Inicialização do benchmark"
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Bibliotecas i18n para Vue — Relatório de Benchmark 2026
|
|
26
|
+
|
|
27
|
+
Esta página é um relatório de benchmark para soluções i18n no Vue.
|
|
28
|
+
|
|
29
|
+
## Índice
|
|
30
|
+
|
|
31
|
+
<Toc/>
|
|
32
|
+
|
|
33
|
+
## Benchmark Interativo
|
|
34
|
+
|
|
35
|
+
<I18nBenchmark framework="vite-vue" 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-vite_vue.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_vue.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 um app Vue. O principal risco é enviar conteúdo desnecessário: traduções para outras páginas e outros locais no bundle de uma única rota.
|
|
53
|
+
|
|
54
|
+
À medida que seu app cresce, esse problema pode rapidamente explodir o JavaScript enviado ao cliente e tornar a navegação lenta.
|
|
55
|
+
|
|
56
|
+
Na prática, para as implementações menos otimizadas, uma página internacionalizada pode acabar sendo várias vezes mais pesada do que a versão sem i18n.
|
|
57
|
+
|
|
58
|
+
O outro impacto é na experiência do desenvolvedor (DX): como você declara conteúdo, tipos, organização de namespaces, carregamento dinâmico e reatividade quando o local muda.
|
|
59
|
+
|
|
60
|
+
## TL;DR
|
|
61
|
+
|
|
62
|
+
- **Intlayer**: A solução mais leve (v8.7.12) com escopo (scoping) e carregamento dinâmico integrados.
|
|
63
|
+
- **vue-i18n**: O padrão da indústria com um rico ecossistema, mas pode se tornar significativamente mais pesado e difícil de otimizar para code-splitting em aplicações de grande porte.
|
|
64
|
+
- **fluent-vue**: Organização de mensagens inovadora, mas carece de segurança de tipos e acaba sendo uma solução extremamente pesada.
|
|
65
|
+
|
|
66
|
+
## Teste seu app
|
|
67
|
+
|
|
68
|
+
Para identificar rapidamente problemas de vazamento de i18n, configurei um scanner gratuito disponível [aqui](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
|
+
## O problema
|
|
73
|
+
|
|
74
|
+
Duas alavancas são essenciais para limitar o custo de um app multilíngue:
|
|
75
|
+
|
|
76
|
+
- Dividir o conteúdo por página / namespace para não carregar dicionários inteiros quando não for necessário.
|
|
77
|
+
- Carregar o local correto dinamicamente, apenas quando necessário.
|
|
78
|
+
|
|
79
|
+
Entendendo as limitações técnicas dessas abordagens:
|
|
80
|
+
|
|
81
|
+
**Carregamento dinâmico**
|
|
82
|
+
|
|
83
|
+
Sem carregamento dinâmico, a maioria delle soluções mantém as mensagens na memória desde o primeiro render, o que adiciona um overhead significativo para apps com muitas rotas e locais.
|
|
84
|
+
|
|
85
|
+
Com carregamento dinâmico, você aceita uma troca: menos JS inicial, mas às vezes uma requisição extra ao trocar de idioma.
|
|
86
|
+
|
|
87
|
+
**Divisão de conteúdo (Splitting)**
|
|
88
|
+
|
|
89
|
+
Sintaxes construídas em torno de `const { t } = useI18n()` + `t('a.b.c')` são muito convenientes, mas frequentemente 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.
|
|
90
|
+
|
|
91
|
+
## Metodologia
|
|
92
|
+
|
|
93
|
+
Para este benchmark, comparamos as seguintes bibliotecas:
|
|
94
|
+
|
|
95
|
+
- `Base App` (Sem biblioteca i18n)
|
|
96
|
+
- `vue-intlayer` (v8.7.12)
|
|
97
|
+
- `vue-i18n` (v11.4.0)
|
|
98
|
+
- `fluent-vue` (v3.8.2)
|
|
99
|
+
|
|
100
|
+
O framework é `Vue` com um app multilíngue de **10 páginas** e **10 idiomas**.
|
|
101
|
+
|
|
102
|
+
Comparamos **quatro estratégias de carregamento**:
|
|
103
|
+
|
|
104
|
+
| Estratégia | Sem namespaces (global) | Com namespaces (scoped) |
|
|
105
|
+
| :------------------------ | :----------------------------------------------- | :-------------------------------------------------------------------- |
|
|
106
|
+
| **Carregamento estático** | **Static**: Tudo na memória ao iniciar. | **Scoped static**: Dividido por namespace; tudo carregado ao iniciar. |
|
|
107
|
+
| **Carregamento dinâmico** | **Dynamic**: Carregamento sob demanda por local. | **Scoped dynamic**: Carregamento granular por namespace e local. |
|
|
108
|
+
|
|
109
|
+
## Resumo das estratégias
|
|
110
|
+
|
|
111
|
+
- **Static**: Simples; sem latência de rede após o carregamento inicial. Desvantagem: grande tamanho de bundle.
|
|
112
|
+
- **Dynamic**: Reduz o peso inicial (lazy-loading). Ideal quando você tem muitos locais.
|
|
113
|
+
- **Scoped static**: Mantém o código organizado (separação lógica) sem requisições de rede extras complexas.
|
|
114
|
+
- **Scoped dynamic**: Melhor abordagem para _code splitting_ e desempenho. Minimiza a memória carregando apenas o que a view atual e o local ativo precisam.
|
|
115
|
+
|
|
116
|
+
### O que eu medi:
|
|
117
|
+
|
|
118
|
+
Executei o mesmo app multilíngue em um navegador real para cada stack e anotei o que realmente passou pela rede e quanto tempo as coisas levaram. Os tamanhos são relatados **após a compressão web normal**, pois isso é mais próximo do que as pessoas realmente baixam.
|
|
119
|
+
|
|
120
|
+
- **Tamanho da biblioteca de internacionalização**: Após bundling, tree-shaking e minificação, o tamanho da biblioteca i18n é o tamanho do código dos providers + composables em um componente vazio. Não inclui o carregamento de arquivos de tradução. Responde o quão "cara" a biblioteca é antes do seu conteúdo entrar em cena.
|
|
121
|
+
|
|
122
|
+
- **JavaScript por página**: Para cada rota de benchmark, quanto script o navegador puxa para aquela visita, calculado pela média entre as páginas do conjunto (e entre os locais). Páginas pesadas são páginas lentas.
|
|
123
|
+
|
|
124
|
+
- **Vazamento de outros locais (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`).
|
|
125
|
+
|
|
126
|
+
- **Vazamento de outras rotas**: A mesma ideia para **outras telas** no app: se os textos delas estão vindo junto 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.
|
|
127
|
+
|
|
128
|
+
- **Tamanho médio do bundle do componente**: Elementos de UI comuns são medidos **um por um**, em vez de se esconderem dentro de um único número gigante do app. Mostra se a internacionalização infla silenciosamente os componentes do dia a dia. Por exemplo, se o seu componente re-renderiza, 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 retardará o desempenho dos seus componentes.
|
|
129
|
+
|
|
130
|
+
- **Capacidade de resposta à mudança de idioma**: Eu troco o idioma usando o próprio controle do app e cronometro quanto tempo leva até que a página tenha mudado claramente, o que um visitante notaria.
|
|
131
|
+
|
|
132
|
+
- **Trabalho de renderização após uma mudança de idioma**: Um acompanhamento mais detalhado: quanto esforço a interface levou para redesenhar para o novo idioma assim que a mudança começou. Útil quando o tempo "sentido" e o custo do framework divergem.
|
|
133
|
+
|
|
134
|
+
- **Tempo inicial de carregamento da página**: Da navegação até o navegador considerar a página totalmente carregada para os cenários que testei. Bom para comparar inicializações a frio.
|
|
135
|
+
|
|
136
|
+
- **Tempo de hidratação (Hydration)**: O tempo que o cliente gasta transformando o HTML do servidor em uma interface interativa. Um traço nas tabelas significa que aquela implementação não forneceu um número de hidratação confiável neste benchmark.
|
|
137
|
+
|
|
138
|
+
## Resultados em detalhes
|
|
139
|
+
|
|
140
|
+
### 1 — Soluções a evitar
|
|
141
|
+
|
|
142
|
+
> Nenhuma solução clara a evitar no ecossistema Vue.
|
|
143
|
+
|
|
144
|
+
### 2 — Soluções aceitáveis
|
|
145
|
+
|
|
146
|
+
**(vue-i18n)** (`vue-i18n@11.4.0`):
|
|
147
|
+
|
|
148
|
+
- **vue-i18n** é sem contestação a biblioteca i18n mais usada para Vue, possui muitos recursos e um ecossistema enorme. Mas, nos bastidores, a solução é bastante pesada. Mesmo que o vue-i18n integre lazy loading para mensagens, falta um recurso de escopo (scoping). No caso de um app Vue SPA clássico, não há problema, mas para um app Nuxt, usando @nuxt/i18n, isso leva à inclusão das mensagens de todas as páginas em uma única. Para um app Nuxt grande com mais de 10 páginas, isso pode se tornar realmente problemático.
|
|
149
|
+
|
|
150
|
+
O pacote é muito pesado (~24.3kb, o que é cerca de 9× o `vue-intlayer`).
|
|
151
|
+
|
|
152
|
+
**(fluent-vue)** (`fluent-vue@0.5.0`):
|
|
153
|
+
|
|
154
|
+
- **fluent-vue** oferece uma tentativa de inovação através do formato .ftl. A organização das mensagens é ótima, mais fácil de começar. Mas na prática, a falta de segurança de tipos aumenta o risco de erro e pode rapidamente se tornar demorada para debugar. Além disso, essa solução carrega as mensagens usando um plugin vite que força o carregamento de todo o conteúdo em todos os idiomas em cada página. Adicionalmente, é uma solução extremamente pesada (~92.7kb, o que é cerca de 34× o `vue-intlayer`).
|
|
155
|
+
|
|
156
|
+
### 3 — Recomendações
|
|
157
|
+
|
|
158
|
+
**(Intlayer)** (`vue-intlayer@8.7.12`):
|
|
159
|
+
|
|
160
|
+
Eu não julgarei pessoalmente o `vue-intlayer` por uma questão de objetividade, já que é minha própria solução.
|