Faça sua pergunta e obtenha um resumo do documento referenciando esta página e o provedor AI de sua escolha
O conteúdo desta página foi traduzido com uma IA.
Veja a última versão do conteúdo original em inglêsSe você tiver uma ideia para melhorar esta documentação, sinta-se à vontade para contribuir enviando uma pull request no GitHub.
Link do GitHub para a documentaçãoCopiar o Markdown do documento para a área de transferência
next-intl VS @intlayer/next-intl | Mesma API, Bundle Diferente
@intlayer/next-intl é um adaptador de compatibilidade: expõe a API next-intl (useTranslations, getTranslations, useLocale, t.rich(), plurais ICU, NextIntlClientProvider...) e a serve a partir de dicionários compilados pelo Intlayer. O código da aplicação não muda. O bundle muda.
Este artigo compara os dois na mesma aplicação Next.js, construída uma vez com next-intl e outra com o adaptador. Os números vêm de Benchmark Bloom, uma suite open-source que registra o que o navegador realmente baixa. Se você quiser a comparação next-intl vs Intlayer como bibliotecas, leia next-intl vs Intlayer. Este é sobre o que o adaptador muda quando você mantém seus componentes como estão.
tl;dr: No mesmo aplicativo Next.js, trocarnext-intlpor@intlayer/next-intlreduziu o JavaScript por página de 153.6 KB para 147.5 KB gzip, o componente médio de 21.8 KB para 8.1 KB, vazamento de strings de página estrangeira de ~90% para 0%, e hidratação de 14.7 ms para 12.8 ms, sem editar nenhum componente. No TanStack Start, o equivalenteuse-intl(@intlayer/use-intl) reduziu componentes de 76-87 KB para 9-11 KB e alternância de localidade de 7-21 ms para 4-9 ms. O adaptador custa 8.0 KB de runtime versus 14.7 KB paranext-intle 5.5 KB paranext-intlayernativo. Navegação e middleware são re-implementados na configuração de roteamento do Intlayer; nomes de caminho localizados (pathnames) são o único recurso não transferido.
O que é @intlayer/next-intl
next-intl é um runtime: getRequestConfig carrega um messages/{locale}.json por requisição, NextIntlClientProvider o envia para o cliente, e useTranslations("about") lê chaves daquele objeto no tempo de renderização. Cada otimização (namespaces, pick(messages, [...]) por página, lazy loading) é sua responsabilidade escrever.
@intlayer/next-intl mantém a primeira e última parte dessa cadeia e substitui o meio. Seus componentes ainda chamam useTranslations("about"); o que eles recebem vem de um dicionário Intlayer compilado no tempo de construção, escopo para aquele componente, apenas na locale ativa.
Três mecanismos fazem isso funcionar:
- Import aliasing.
createNextIntlPlugin()de@intlayer/next-intl/pluginencapsulawithIntlayere adiciona aliases do Webpack / Turbopack para quenext-intl,next-intl/server,next-intl/navigationenext-intl/middlewaresejam resolvidos para@intlayer/next-intl. Nenhuma importação em sua codebase é renomeada. - JSON como fonte de verdade. O plugin
syncJSONlê seusmessages/{locale}.jsonexistentes, divide suas chaves de nível superior em um dicionário por namespace e escreve as traduções de volta nos mesmos arquivos quando a CLI ou o CMS as atualiza. O fluxo de trabalho de seus tradutores permanece inalterado. - Call-site binding. A otimização do Intlayer (Babel ou SWC) reescreve
useTranslations("about")em uma chamada que recebe o dicionárioaboutdiretamente. O componente não alcança mais uma árvore de mensagens global; ele alcança seu próprio conteúdo.
Copiar o código para a área de transferência
Copiar o código para a área de transferência
Essa reescrita é por isso que as colunas de tamanho de componente e vazamento de página abaixo se movem: uma página apenas puxa os dicionários dos componentes que renderiza, e apenas na localidade sendo servida.
O que o adaptador mantém, ignora e não substitui
Abrir a tabela em um modal para ver todo o conteúdo claramente
next-intl API | Com @intlayer/next-intl |
|---|---|
useTranslations("ns") / getTranslations("ns") | ✅ Mantido. Vinculado ao dicionário ns em tempo de construção. As chaves são tipadas em relação ao seu conteúdo. |
getTranslations({ locale, namespace }) | ✅ Mantido |
t("key", { name }), t.rich(), t.markup(), t.raw() | ✅ Mantido. Plurais ICU, select, selectordinal, #, {ts, date, long} executados através do resolvedor ICU do Intlayer |
useLocale() / getLocale() / setRequestLocale() / setLocale | ✅ Mantido |
useFormatter() | ✅ Mantido. dateTime, number, relativeTime, list, dateTimeRange conectam ao Intl nativo |
NextIntlClientProvider | ✅ Mantido. Os props messages, timeZone e now são aceitos mas ignorados (um aviso de desenvolvimento o informa) |
getMessages() | ✅ Mantido para compatibilidade; não é mais necessário |
getRequestConfig() em src/i18n.ts | ⚠️ Não necessário. Os dicionários são compilados em tempo de build; não há carregamento de mensagens por requisição |
defineRouting() | ✅ Mantido. Os campos omitidos (locales, defaultLocale, localePrefix) são lidos de intlayer.config.ts |
createNavigation(), Link, redirect, usePathname, useRouter | ✅ Mantido. Re-implementado na configuração de roteamento do Intlayer; o argumento routing é aceito mas ignorado |
pathnames (nomes de rotas localizadas) | ❌ Aceito para digitação, não interpolado. Mantenha pathnames simples ou mova esse mapeamento para rewrite do Intlayer |
createMiddleware() | ✅ Mantido. Retorna o proxy do Intlayer; define o cookie NEXT_LOCALE para que useLocale() e seu comutador continuem funcionando |
NEXT_LOCALE cookie | ✅ Lido por padrão (a menos que você configure routing.storage por conta própria) |
Bare useTranslations() com nenhum namespace | ⚠️ Funciona, mas o local da chamada não está vinculado: ele se resolve através do registro de tempo de execução. Passe um namespace para obter os ganhos de bundle |
O benchmark
O que foi medido
A suite Benchmark Bloom constrói a mesma aplicação com cada setup: 10 páginas (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 locales (en, fr, es, de, it, pt, zh, ja, ko, ru), componentes idênticos e conteúdo idêntico. As páginas são medidas em en e fr.
next-intl foi construído em quatro estratégias de carregamento, da configuração ingênua (arquivo messages/{locale}.json carregado integralmente) até a ótima (um namespace por rota + pick() por página). O adapter foi construído sobre os mesmos componentes da configuração ingênua, com apenas next.config.ts e intlayer.config.ts alterados. Não possui uma variante "scoped": o compilador faz o escopo do conteúdo por componente, portanto suas linhas static e dynamic já estão no escopo.
Para cada build, a suite registra:
- Lib size: tamanho gzip da biblioteca i18n de um componente vazio que apenas importa a biblioteca. O custo fixo do runtime.
- Page JS: JavaScript gzip baixado por página, calculado em média em todas as páginas e locales.
- Locale leak %: compartilha de strings traduzidas encontradas no JS baixado que pertencem a uma locale que o usuário não está visualizando.
- Page leak %: compartilha de strings traduzidas encontradas no JS baixado que pertencem a uma página na qual o usuário não está.
- Component avg: tamanho gzip médio de cada componente compilado isoladamente. Mostra quanto runtime i18n e catálogo um único componente carrega.
- E2E reactivity: tempo de parede entre selecionar uma nova locale e
html[lang]atualizar no DOM (Playwright, 5 iterações). - Hydration: duração da fase de hydration do React.
Os números abaixo vêm da execução datada de 2026-09-12 comnext-intl/use-intl4.14.2 e@intlayer/*9.5.1. A aplicação de teste é deliberadamente pequena (algumas dezenas de strings por locale), então os percentuais de vazamento descrevem um padrão: eles crescem com seu conteúdo enquanto o custo de runtime permanece fixo.
Resultados no Next.js
Abrir a tabela em um modal para ver todo o conteúdo claramente
| Setup | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (sem i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
@intlayer/next-intl | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
next-intlayer (native) | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer (native) | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
Como ler
- Mesmos componentes, 6 KB menos por página. A compilação do adapter da aplicação ingênua chega a 147.5 KB, abaixo de todas as configurações
next-intl, incluindo a totalmente otimizada (153.6 KB). O próprio runtime é a diferença: 8.0 KB versus 14.7 KB, pagos em cada página. - Vazamento vai a 0% sem tocar em um componente. A configuração ingênua de
next-intlenvia ~90% de strings de páginas estrangeiras em cada página. Atingir 0% comnext-intlsignifica as configuraçõesscoped-*: um namespace por rota, epick(messages, [...])em cada página. O adapter atinge 0% a partir do código ingênuo porque a passagem de otimização vincula cadauseTranslations("ns")ao seu próprio dicionário. - Componentes encolhem 2.7x. Um componente compilado isoladamente tem média de 21.8 KB com
next-intl(ele atinge o provider e a árvore de mensagens) e 8.1 KB com o adapter. Na configuraçãoscoped-staticdonext-intlesse número sobe para 80 KB, porque o arquivo de namespace de cada rota fica acessível a partir da página que o seleciona. - Hidratação é 2 ms mais rápida (12.8 vs 14.7 ms): não há objeto de mensagem para desserializar do payload RSC antes do React poder hidratar.
- O adapter não é o runtime nativo.
next-intlayerfica em 141.3 KB, +0.3 KB sobre a app base, com um runtime de 5.5 KB. O adapter carrega a superfície de API donext-intl(useFormatter,t.rich, o resolver ICU) no topo do core do Intlayer, daí 8.0 KB e +6 KB por página. É a ponte, não o destino.
Resultados em TanStack Start (use-intl)
use-intl é o core agnóstico de framework do next-intl. Seu adapter, @intlayer/use-intl, segue o mesmo design com um plugin Vite (@intlayer/use-intl/plugin).
Abrir a tabela em um modal para ver todo o conteúdo claramente
| Configuração | Estratégia | Tamanho da biblioteca (gz) | JS médio da página (gz) | Vazamento de localidade | Vazamento de página | Componente médio (gz) | Reatividade E2E | Hidratação |
|---|---|---|---|---|---|---|---|---|
| base (sem i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms | 21.6 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms | 15.3 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms | 15.4 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms | 24.8 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms | 25.9 ms |
@intlayer/use-intl | static | 7.3 KB | 135.8 KB | 49.7% | 0.0% | 10.9 KB | 4.2 ms | 10.5 ms |
@intlayer/use-intl | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms | 16.1 ms |
intlayer (native) | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer (native) | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
Como ler
- Bytes por página são equivalentes ao
use-intlotimizado.@intlayer/use-intlem mododynamic(129.7 KB) está dentro de 1 KB doscoped-dynamic(128.7 KB) douse-intl, e 10 KB acima dodynamicsimples (119.4 KB) douse-intl. Essa linhadynamicsimples ainda vaza 90% das strings de páginas estrangeiras; a contagem de bytes é baixa porque o conteúdo da aplicação de teste é pequeno. O 0% do adapter é o que permanece constante conforme o conteúdo cresce. - Os componentes são 7-9x menores. Os componentes
use-intltêm em média 76-87 KB em todas as estratégias, porqueuseTranslationsestá vinculado ao objeto de mensagens completo do provedor. O adapter tem em média 9-11 KB. - A alternância de locale é mais rápida. As configurações otimizadas de
use-intllevam 13-21 ms para atualizarhtml[lang]; o adapter leva 4-9 ms. Menos componentes são re-renderizados, e nada é re-selecionado de uma árvore de mensagens. staticmantém cada locale. A linhastaticdo adapter mostra 49,7% de vazamento de locale, o mesmo que o Intlayer nativo em modostatic: todos os locales são agrupados, apenas os dicionários da página. Uma linha de configuração (importMode: 'dynamic') remove isso.
Por que os números mudam
Nada no componente mudou, então os ganhos vêm inteiramente do que useTranslations está vinculado.
Com next-intl, a vinculação é o provedor. NextIntlClientProvider recebe o objeto messages inteiro para a localidade; cada useTranslations("about") lê a partir dele. O bundler vê um componente importando um hook que lê um contexto, e não pode saber que apenas a ramificação about é usada. As rotas abaixo compartilham o mesmo objeto de mensagens, então a coluna page-leak lê ~90% até você dividir o arquivo você mesmo.
Copiar o código para a área de transferência
Com @intlayer/next-intl, a vinculação é o dicionário. syncJSON transforma messages/en.json em um dicionário por chave de nível superior; o compilador resolve qual componente chama useTranslations("about") e lhe passa about diretamente, na locale ativa, como um import que o bundler pode rastrear e dividir.
Copiar o código para a área de transferência
src/i18n.ts e a prop messages desaparecem. Tudo o resto é idêntico.
Migração em três passos
Instalar
bashCopiar códigoCopiar o código para a área de transferência
O comando detecta
next-intle instalaintlayer,next-intlayer,@intlayer/next-intle@intlayer/sync-json-plugin. Mantenhanext-intlinstalado: é uma dependência peer do adaptador e fornece os tipos.Aponte o Intlayer para suas mensagens
intlayer.config.tsCopiar códigoCopiar o código para a área de transferência
messages/{locale}.jsonpermanece onde está. Cada chave de nível superior torna-se um dicionário;useTranslations("about")mapeia para o dicionárioabout.Envolver next.config.ts
next.config.tsCopiar códigoCopiar o código para a área de transferência
createNextIntlPlugin()compõewithIntlayer(monitoramento de conteúdo, compilação de dicionário, a otimização) e os aliasesnext-intl→@intlayer/next-intlpara Webpack e Turbopack. Faça o build, e os números nas tabelas acima são seus.
O que você pode deletar depois
Abrir a tabela em um modal para ver todo o conteúdo claramente
| Arquivo / padrão | Por quê |
|---|---|
getRequestConfig em src/i18n.ts | Sem carregamento de mensagens por requisição. Mantenha o arquivo apenas se ele também exportar helpers createNavigation |
messages={...} no NextIntlClientProvider | O adapter lê a saída compilada; a prop é ignorada e registra um aviso em desenvolvimento |
await getMessages() em layouts | Mesmo motivo |
pick(messages, [...]) por página | O compilador faz a seleção, por componente |
O que você ganha além de bytes
- Chaves tipadas.
useTranslations("about")é tipado contra o dicionário compiladoabout.t("does.not.exist")é um erro TypeScript, não um fallback em tempo de execução. npx intlayer testfalha no CI quando um locale está faltando uma chave.npx intlayer filltraduz as chaves ausentes com o provedor de sua escolha (OpenAI, Anthropic, Mistral, Gemini...) usando sua própria chave, e escreve o resultado de volta emmessages/{locale}.json.- Visual Editor e CMS funcionam nos mesmos dicionários, portanto, não-desenvolvedores podem editar
messages/fr.jsonatravés de uma UI e o arquivo é atualizado. - Migração incremental para
.content.ts. Qualquer componente pode alternar deuseTranslations("about")parauseIntlayer("about")com um arquivo de conteúdo co-localizado, um de cada vez. Os dicionários JSON e.content.tscoexistem e se mesclam.
Limites a conhecer antes de começar
- Routing config move para
intlayer.config.ts.createNavigation(routing)ecreateMiddleware(routing)mantêm sua assinatura mas ignoram o argumento: locales, locale padrão e estratégia de prefixo vêm da configroutingdo Intlayer. Se você usapathnameslocalizados donext-intl(/about→/a-propos), o adapter não interpola; orouting.rewritedo Intlayer cobre esse caso mas é uma mudança separada. useTranslations()sem namespace não é vinculado. O passe de otimização precisa de um namespace estático para saber qual dicionário importar. Uma chamada simples ainda funciona, por meio de um registry de runtime que referencia cada dicionário, que é exatamente o vazamento que você estava tentando remover. Passe o namespace.- O adaptador não é gratuito. 8.0 KB de runtime versus 5.5 KB para
next-intlayer, e +6-7 KB por página em relação ao build nativo. Isso é o custo da surface API donext-intl. Se você chegar ao ponto em que todos os componentes foram movidos parauseIntlayer, descarte o adaptador. messages,timeZone,nowno provider são ignorados. Os formatadores são suportados peloIntlnativo e apenas a locale influencia sua saída; se você depender de um fuso horário forçado ou umnowfixo para datas estáveis em hidratação, trate isso no local da chamada.
Quando usar qual?
- Mantenha-se em
next-intlse sua app é pequena, seu bundle não é uma preocupação, e seu time está confortável em gerenciar namespaces epick()por página. - Use
@intlayer/next-intlif you are onnext-intltoday and want the bundle, leakage and hydration gains, typed keys and the CLI / CMS tooling without a rewrite. This is the recommended entry point for any existingnext-intlcodebase. - Go native (
next-intlayer) for new projects, or once the adapter has done its job. It is the lightest of the three (5.5 KB, +0.3 KB per page) and unlocks synchronous server components, per-component.content.tsfiles and the full feature set.
Related comparisons
- next-intl vs Intlayer (as bibliotecas, mesmo benchmark)
- i18next vs @intlayer/i18next (mesma série de adapter)
- Lingui vs @intlayer/lingui (mesma série de adaptador)
- vue-i18n vs @intlayer/vue-i18n (mesma série de adaptador)
- Guia de migração: next-intl para Intlayer
- Referência do adaptador de compatibilidade: next-intl
Conclusão
@intlayer/next-intl faz uma coisa: muda o que useTranslations está vinculado, de um provider que contém todas as mensagens para um dicionário compilado para esse componente. No mesmo app Next.js que vale 6 KB por página, componentes 2,7x menores, 0% de vazamento e 2 ms de hidratação, antes de qualquer pessoa abrir um arquivo de componente. Navigation e middleware mantêm sua API no topo da configuração de roteamento do Intlayer, e o runtime nativo next-intlayer permanece ainda mais leve.
Todos os dados brutos, os apps de teste e os scripts estão no repositório Benchmark Bloom. Execute você mesmo.
Consulte o doc 'Por que Intlayer?' para mais detalhes.
Comentários
Ainda sem comentários. Seja o primeiro a compartilhar seus pensamentos.
