Autor:
    Criação:2026-09-13Última atualização:2026-09-13

    i18next VS Intlayer | Benchmark de internacionalização (i18n) para React e Next.js

    O i18next é o framework de i18n mais utilizado no ecossistema JavaScript. Através do react-i18next e next-i18next, sustenta grande parte das aplicações React e Next.js. O Intlayer é uma alternativa baseada em compilador, com escopo por componente.

    Este artigo compara ambas as soluções com base em medições reais e não em listas de recursos. Os números vêm do Benchmark Bloom, uma suíte de código aberto que constrói a mesma aplicação com cada biblioteca e registra o que o navegador realmente baixa.

    tl;dr: O i18next é o runtime mais pesado do benchmark: +77 KB gzip por página no Next.js na configuração padrão (naive), +22 KB após otimização completa de namespaces + lazy loading. O Intlayer adiciona apenas +0.3 KB. Todas as configurações do i18next, exceto a totalmente isolada (scoped), enviam ~90% de strings de outras páginas; o Intlayer envia 0% por padrão. A troca de idioma com backend carregado sob demanda levou 123-185 ms com o react-i18next contra 3-4 ms com o Intlayer. O adaptador @intlayer/next-i18next mantém a API do i18next e atingiu 150.7 KB por página contra 218.5 KB do original.

    Em resumo

    • i18next / react-i18next / next-i18next - Maduro, com ecossistema rico de plugins e agnóstico de framework. Namespaces, detectores de idioma, backends, ICU via plugin, <Trans> para conteúdo rico. Conteúdo centralizado em locales/{lng}/{ns}.json. Poderoso, mas cada otimização (divisão de namespaces, carregamento por página, segurança de tipos) exige configurações manuais contínuas.
    • Intlayer - Modelo de conteúdo centrado em componentes. Dicionários .content.ts ficam ao lado do componente que atendem, um compilador em tempo de build aplica tree-shaking e lazy loading por componente e por idioma, tipos TypeScript estritos são gerados a partir do seu conteúdo e traduções ausentes quebram o build. Oferece middleware, utilitários de SEO, Editor Visual / CMS e tradução assistida por IA.
    BibliotecaEstrelas no GitHubCommits TotaisÚltimo CommitPrimeira VersãoVersão no NPMDownloads no NPM
    aymericzip/intlayerGitHub Repo starsGitHub commit activityLast CommitAbril de 2024npmnpm downloads
    i18next/i18nextGitHub Repo starsGitHub commit activityLast CommitJaneiro de 2012npmnpm downloads
    i18next/react-i18nextGitHub Repo starsGitHub commit activityLast CommitDezembro de 2015npmnpm downloads
    i18next/next-i18nextGitHub Repo starsGitHub commit activityLast CommitNovembro de 2018npmnpm downloads
    Os badges são atualizados automaticamente. As capturas variam ao longo do tempo.

    Comparativo direto de recursos

    RecursoIntlayer (react-intlayer / next-intlayer)i18next (react-i18next / next-i18next)
    Traduções próximas aos componentes✅ Sim, .content.ts junto a cada componente❌ Não, pastas centralizadas locales/{lng}/{ns}.json
    Integração com TypeScript✅ Tipos estritos gerados automaticamente do conteúdo⚠️ Básico; chaves estritas exigem ampliação de CustomTypeOptions e tipagem
    Detecção de traduções ausentes✅ Erro TypeScript + erro/aviso no momento do build⚠️ Fallback em runtime (saveMissing, eco de chave)
    Conteúdo rico (JSX / Markdown)✅ Suporte nativo⚠️ <Trans> com placeholders indexados
    Suporte ICU⚠️ Em andamento⚠️ Via plugin (i18next-icu)
    Pluralização✅ Padrões baseados em enumerações✅ Sufixos _one / _other (Intl.PluralRules)
    Formatação (datas, números, moedas)useNumber, useDate, ... (Intl nativo)⚠️ Formatadores de interpolação ou chamadas diretas a Intl.*
    Roteamento localizado e middleware✅ Proxy/middleware integrado, getMultilingualUrls⚠️ Não é nativo; middleware manual ou de terceiros
    Utilitários de SEO (hreflang, sitemap)✅ Utilitários integrados❌ Manual
    Componentes de servidor síncronosuseIntlayer de next-intlayer/server utilizável em qualquer Server Component⚠️ getFixedT na página e passar t via props
    Tree-shaking (apenas conteúdo utilizado)✅ Por componente, por idioma, automatizado pelo compilador⚠️ Manual: namespaces + lista ns por página + backend
    Lazy loadingimportMode: 'dynamic' (uma linha de configuração)✅ Via plugins backend (i18next-resources-to-backend, i18next-http-backend)
    Limpeza de conteúdo não utilizado✅ Dicionários órfãos são descartados no build❌ Não integrado
    Testar traduções ausentes (CLI / CI)npx intlayer content test⚠️ i18next-parser / ferramentas de terceiros
    Tradução assistida por IA✅ Integrada, utiliza suas próprias chaves de API❌ Não (Locize é um serviço pago à parte)
    Editor Visual / CMS✅ Editor Visual gratuito + CMS opcional❌ Não (Locize / plataformas externas)
    Servidor MCP e Agent Skills✅ Sim❌ Não
    Ecossistema e comunidade⚠️ Mais recente, porém em rápido crescimento✅ Maior e mais maduro

    O benchmark

    O que foi medido

    A suíte Benchmark Bloom constrói a mesma aplicação com cada biblioteca: 10 páginas (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 idiomas (en, fr, es, de, it, pt, zh, ja, ko, ru), componentes e conteúdos idênticos. As páginas são medidas em en e fr. Cada biblioteca é testada em até quatro estratégias de carregamento:

    EstratégiaDescriçãoQuem costuma usar
    staticTodos os idiomas e páginas empacotados juntos (resources embutidos em init())Protótipos rápidos, código gerado por IA
    dynamicApenas o idioma ativo é carregado via backend, mas todos os namespaces simultaneamenteA maioria dos projetos
    scoped-staticUm namespace por rota, todos embutidos antecipadamenteRaro
    scoped-dynamicUm namespace por rota + lazy loading via backend. Apenas a página e idioma atuaisAplicações com orçamento de performance rígido

    O Intlayer não possui variante "scoped": o compilador isola o conteúdo por componente automaticamente, logo suas linhas static e dynamic já são otimizadas.

    Para cada build, a suíte registra:

    • Lib size: tamanho gzip de um componente vazio que apenas importa a biblioteca i18n. O custo fixo do runtime.
    • Page JS: JavaScript gzip baixado por página, com média calculada sobre todas as páginas e idiomas.
    • Locale leak %: proporção de strings traduzidas no JS baixado que pertencem a um idioma que o usuário não está visualizando.
    • Page leak %: proporção de strings traduzidas no JS baixado pertencentes a páginas nas quais o usuário não está.
    • Component avg: tamanho médio gzip de cada componente compilado isoladamente.
    • E2E reactivity: intervalo real entre a seleção de um novo idioma e a atualização de html[lang] no DOM (Playwright, 5 iterações).
    • Hydration: duração da fase de hidratação do React.
    Os dados abaixo provêm da execução de 2026-09-12 com next-i18next 16.3.0, react-i18next 17.0.13 e intlayer 9.5.1. A aplicação de teste é deliberadamente enxuta (poucas dezenas de strings por idioma), portanto as porcentagens de vazamento expressam um padrão: crescem com o volume do conteúdo enquanto o custo do runtime permanece constante.

    Resultados no Next.js (next-i18next)

    BibliotecaEstratégiaLib size (gz)Page JS avg (gz)Locale leakPage leakComponent avg (gz)Reatividade E2EHydration
    base (sem i18n)-0.0 KB141.0 KB0.0%0.0%0.9 KB13.4 ms11.8 ms
    next-i18nextstatic19.7 KB218.5 KB0.0%89.8%78.5 KB16.4 ms15.6 ms
    next-i18nextdynamic19.7 KB169.5 KB50.0%89.8%26.1 KB15.4 ms27.7 ms
    next-i18nextscoped-static19.7 KB220.1 KB0.0%89.8%78.9 KB16.4 ms14.7 ms
    next-i18nextscoped-dynamic19.7 KB163.4 KB0.0%0.0%27.1 KB15.9 ms15.1 ms
    next-intlayerstatic5.5 KB141.3 KB0.0%0.0%8.5 KB15.5 ms16.9 ms
    next-intlayerdynamic5.5 KB141.3 KB0.0%0.0%6.9 KB15.3 ms15.9 ms
    @intlayer/next-i18next (compat)static9.4 KB150.7 KB0.0%0.0%9.7 KB10.7 ms11.3 ms
    @intlayer/next-i18next (compat)dynamic9.4 KB150.7 KB0.0%0.0%9.7 KB11.9 ms10.6 ms

    Como interpretar os dados

    • Custo do runtime. O core do i18next somado ao react-i18next representa o maior runtime medido: 19.7 KB gzip para um componente vazio, contra 5.5 KB do next-intlayer.
    • A configuração inicial é onerosa. Embutir resources em init() gera 218.5 KB por página, +77.5 KB acima da aplicação base. Cada página carrega todos os namespaces.
    • Otimizar exige muito esforço. Adotar um backend (dynamic) reduz 49 KB mas ainda vaza 90% de strings de outras páginas e, nesta configuração, metade das strings pertence ao idioma errado. Dividir em namespaces por rota (scoped-dynamic) atinge 0% de vazamento com 163.4 KB, permanecendo +22.4 KB por página acima do Intlayer (141.3 KB), que não exigiu configuração manual alguma.
    • Tamanho dos componentes. Um componente invocando useTranslation() compila entre 26 e 79 KB; o mesmo componente com useIntlayer() compila em 6.9 KB.
    • A hidratação sobe para 27.7 ms na configuração dynamic: a instância do i18next inicializa e resolve o backend no cliente antes de o React conseguir hidratar a página.

    Resultados no TanStack Start (react-i18next)

    A mesma aplicação no TanStack Start com react-i18next puro, isolando os efeitos específicos do Next.js.

    BibliotecaEstratégiaLib size (gz)Page JS avg (gz)Locale leakPage leakComponent avg (gz)Reatividade E2EHydration
    base (sem i18n)-0.0 KB111.0 KB0.0%0.0%0.7 KB8.1 ms21.6 ms
    react-i18nextstatic18.4 KB180.3 KB50.0%89.8%24.3 KB12.9 ms85.1 ms
    react-i18nextdynamic18.4 KB136.4 KB23.1%89.8%24.8 KB123.1 ms32.9 ms
    react-i18nextscoped-static18.4 KB184.2 KB50.7%89.8%25.3 KB185.1 ms25.2 ms
    react-i18nextscoped-dynamic18.4 KB127.2 KB0.0%0.0%26.7 KB17.6 ms11.3 ms
    intlayerstatic5.0 KB125.8 KB50.0%0.0%8.1 KB3.2 ms11.5 ms
    intlayerdynamic5.0 KB118.6 KB0.0%0.0%6.3 KB3.6 ms14.1 ms

    Como interpretar os dados

    • A aplicação react-i18next básica entrega +69 KB por página em relação à aplicação base, e a hidratação consome 85 ms (4 vezes a base) porque toda a árvore de recursos é processada e registrada no cliente antes do primeiro render.
    • A troca de idioma evidencia o atraso do lazy loading. Quando os recursos são carregados sob demanda, alternar de idioma exige uma viagem de rede antes da atualização do html[lang]: 123 ms em dynamic, 185 ms em scoped-static. O Intlayer atualiza o DOM em 3-4 ms em ambos os modos: a troca é instantânea e não fica bloqueada em requisições de rede.
    • A configuração totalmente otimizada scoped-dynamic atinge 0% de vazamento com 127.2 KB, ainda +8.6 KB acima da linha dynamic do Intlayer, e demandou mapeamento de rotas para namespaces, backend de recursos e limites Suspense por rota.
    • A linha static do Intlayer já apresenta 0% de vazamento de página porque somente os dicionários importados pelos componentes daquela página são empacotados. Ativar importMode: 'dynamic' elimina também o vazamento de idioma.
    • Tamanho por componente: 24-27 KB com react-i18next contra 6-8 KB com Intlayer. O useTranslation() conecta cada componente à instância global do i18next.

    De onde vem a diferença? Instância global vs. dicionários compilados

    O i18next foi projetado em 2012 como um runtime: uma instância global mantém os recursos, plugins a expandem e t() busca chaves em tempo de renderização. Isso proporciona grande flexibilidade (qualquer framework, backend ou formato), mas impõe custo de peso:

    bash
    .
    ├── i18n.ts                      # createInstance().use(...).use(...).init({...})
    └── src
        ├── locales
       ├── en
       ├── common.json
       ├── home.json
       └── about.json
       └── fr
           ├── common.json
           ├── home.json
           └── about.json
        ├── components
       └── Counter.tsx          # useTranslation("about") + t("counter.label")
        └── app
            └── [locale]
                └── about
                    └── page.tsx     # precisa saber que depende de ["common", "about"]
    

    A instância não tem como prever quais chaves um componente solicitará. Otimizar exige que você divida catálogos em namespaces, você liste os namespaces de cada página e você mantenha essa lista sincronizada ao mover componentes. Como destacam as notas do benchmark: "manter a segurança de tipos e saber com precisão qual namespace incluir em cada página é um pesadelo".

    O Intlayer remove a instância global. O conteúdo é declarado ao lado do componente e o compilador resolve o grafo de dependências no momento do build:

    bash
    .
    ├── intlayer.config.ts
    └── src
        ├── components
       └── Counter
           ├── index.tsx        # useIntlayer("counter")
           └── index.content.ts
        └── app
            └── [locale]
                └── about
                    ├── page.tsx
                    └── page.content.ts
    

    O @intlayer/swc / @intlayer/babel identifica qual componente importa qual dicionário, inclui apenas esses, apenas para o idioma ativo, e descarta o que não for utilizado. O padrão "scoped-dynamic" passa a ser o resultado natural do build, e não uma disciplina manual mantida pela equipe.

    Para reproduzir os números da linha dynamic, configure dictionary.importMode: 'dynamic' em intlayer.config.ts. Veja a documentação de otimização de bundle.

    Experiência de desenvolvimento

    Configuração

    next-i18next (App Router)

    src/app/i18n/server.ts
    import { createInstance } from "i18next";
    import { initReactI18next } from "react-i18next/initReactI18next";
    import resourcesToBackend from "i18next-resources-to-backend";
    import { defaultLocale } from "@/i18n.config";
    
    const backend = resourcesToBackend(
      (locale: string, namespace: string) =>
        import(`../../locales/${locale}/${namespace}.json`)
    );
    
    export const initI18next = async (
      locale: string,
      namespaces: string[] = ["common"]
    ) => {
      const i18n = createInstance();
      await i18n
        .use(initReactI18next)
        .use(backend)
        .init({
          lng: locale,
          fallbackLng: defaultLocale,
          ns: namespaces,
          defaultNS: "common",
          interpolation: { escapeValue: false },
          react: { useSuspense: false },
        });
      return i18n;
    };
    

    Adiciona-se a isso um I18nProvider client-side que recria a instância com as mesmas configurações, generateStaticParams e uma lista de namespaces em cada página.

    Intlayer

    intlayer.config.ts
    import { type IntlayerConfig, Locales } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [Locales.ENGLISH, Locales.FRENCH],
        defaultLocale: Locales.ENGLISH,
      },
    };
    
    export default config;
    
    src/app/[locale]/layout.tsx
    import { getHTMLTextDir } from "intlayer";
    import { IntlayerClientProvider, type NextLayoutIntlayer } from "next-intlayer";
    
    const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
      const { locale } = await params;
    
      return (
        <html lang={locale} dir={getHTMLTextDir(locale)}>
          <body>
            <IntlayerClientProvider locale={locale}>
              {children}
            </IntlayerClientProvider>
          </body>
        </html>
      );
    };
    
    export default LocaleLayout;
    

    Componente de cliente

    react-i18next

    src/locales/en/about.json
    {
      "counter": {
        "label": "Counter",
        "increment": "Increment"
      }
    }
    
    src/components/Counter.tsx
    "use client";
    
    import { useState } from "react";
    import { useTranslation } from "react-i18next";
    
    export const Counter = () => {
      const { t, i18n } = useTranslation("about");
      const [count, setCount] = useState(0);
      const numberFormat = new Intl.NumberFormat(i18n.language);
    
      return (
        <div>
          <p>{numberFormat.format(count)}</p>
          <button
            aria-label={t("counter.label")}
            onClick={() => setCount((c) => c + 1)}
          >
            {t("counter.increment")}
          </button>
        </div>
      );
    };
    
    A página que renderiza este componente precisa carregar o namespace about, e t("counter.label") é uma string simples a menos que você estenda CustomTypeOptions.

    Intlayer

    src/components/Counter/index.content.ts
    import { t, type Dictionary } from "intlayer";
    
    const counterContent = {
      key: "counter",
      content: {
        label: t({ en: "Counter", fr: "Compteur" }),
        increment: t({ en: "Increment", fr: "Incrémenter" }),
      },
    } satisfies Dictionary;
    
    export default counterContent;
    
    src/components/Counter/index.tsx
    "use client";
    
    import { useState } from "react";
    import { useIntlayer } from "next-intlayer";
    import { useNumber } from "next-intlayer/format";
    
    export const Counter = () => {
      const { label, increment } = useIntlayer("counter");
      const number = useNumber();
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>{number(count)}</p>
          <button aria-label={label} onClick={() => setCount((c) => c + 1)}>
            {increment}
          </button>
        </div>
      );
    };
    

    label e increment são estritamente tipados; erros de digitação tornam-se erros de TypeScript e uma tradução ausente causa erro na compilação.

    Componente de servidor síncrono

    next-i18next

    src/components/ServerCounter.tsx
    type ServerCounterProps = {
      t: (key: string) => string;
      locale: string;
      count: number;
    };
    
    export const ServerCounter = ({ t, locale, count }: ServerCounterProps) => (
      <div>
        <p>{new Intl.NumberFormat(locale).format(count)}</p>
        <button aria-label={t("counter.label")}>{t("counter.increment")}</button>
      </div>
    );
    

    A página invoca i18n.getFixedT(locale, "about") e propaga t e locale via props.

    Intlayer

    src/components/ServerCounter.tsx
    import { useIntlayer } from "next-intlayer/server";
    import { useNumber } from "next-intlayer/server/format";
    
    export const ServerCounter = ({ count }: { count: number }) => {
      const { label, increment } = useIntlayer("counter");
      const number = useNumber();
    
      return (
        <div>
          <p>{number(count)}</p>
          <button aria-label={label}>{increment}</button>
        </div>
      );
    };
    

    Mantenha a API do i18next, aproveite o desempenho do Intlayer

    Você não precisa reescrever componentes para obter os números do benchmark. @intlayer/i18next, @intlayer/react-i18next e @intlayer/next-i18next são adaptadores compatíveis: useTranslation, t(), <Trans>, {{interpolation}}, plurais _one / _other, sufixos de contexto e returnObjects continuam operacionais, alimentados pelos dicionários compilados pelo Intlayer.

    next.config.ts
    import type { NextConfig } from "next";
    import { createNextI18nPlugin } from "@intlayer/next-i18next/plugin";
    
    const withIntlayer = createNextI18nPlugin();
    
    const nextConfig: NextConfig = {};
    
    export default withIntlayer(nextConfig);
    
    vite.config.ts
    import { defineConfig } from "vite";
    import { reactI18nextVitePlugin } from "@intlayer/react-i18next/plugin";
    
    export default defineConfig({
      plugins: [reactI18nextVitePlugin()],
    });
    

    No benchmark, a versão adaptada da mesma aplicação Next.js caiu de 218.5 KB para 150.7 KB por página, de 78.5 KB para 9.7 KB por componente, de ~90% de vazamento para 0%, e a hidratação passou de 15.6 ms para 11.3 ms, mantendo o código da aplicação intacto. Seus arquivos locales/{lng}/{ns}.json existentes podem continuar sendo a fonte da verdade via plugin de sincronização JSON.

    Consulte os guias de migração: i18next, react-i18next, next-i18next.

    Quando escolher cada um?

    • Escolha o i18next se você depende de seu ecossistema de plugins (detectores, backends, ICU, Locize), traduz também fora do React (serviços Node, vanilla JS, outros frameworks), seu time já possui domínio ou uma plataforma externa exige locales/{lng}/{ns}.json. Reserve tempo para separar catálogos em namespaces, conectar um backend e manter o mapa de rotas se a performance for prioritária.
    • Escolha o Intlayer se busca conteúdo com escopo por componente, TypeScript estrito, detecção de chaves ausentes no build, tree-shaking e lazy loading automáticos, troca instantânea de idioma, componentes de servidor síncronos e ferramentas editoriais nativas (Editor Visual, CMS, tradução com IA, servidor MCP). Ideal para bases de código modulares e design systems.
    • Escolha os adaptadores @intlayer/*-i18next se você já utiliza o i18next e quer os ganhos de bundle e reatividade sem precisar reescrever seus componentes.

    Comparações relacionadas

    Estrelas no GitHub

    As estrelas no GitHub são um indicador sólido da popularidade, confiança da comunidade e relevância de longo prazo de um projeto. Embora não meçam diretamente a qualidade técnica, refletem o engajamento dos desenvolvedores e o potencial de adoção.

    Gráfico de histórico de estrelas

    Conclusão

    O i18next conquistou sua posição de destaque: roda em qualquer lugar, conta com plugins para tudo e possui mais de dez anos de manutenção contínua. O benchmark demonstra o custo de um design centrado no runtime. A configuração padrão da maioria dos times adiciona +70-77 KB gzip por página, vaza ~90% do conteúdo de outras páginas e leva mais de 100 ms para trocar de idioma com lazy loading. Chegar a 0% de vazamento é viável, mas requer backend, namespaces por rota e mapeamento manual, permanecendo ainda +9-22 KB acima do Intlayer.

    O Intlayer transfere essa responsabilidade para o compilador. Dicionários por componente, lazy loading por idioma e eliminação de conteúdo inútil tornam-se saídas automáticas do build. Na mesma aplicação: +0.3 KB por página, 0% de vazamento, componentes 3 a 10 vezes menores e troca de idioma em 3-4 ms.

    Todos os dados brutos, aplicações de teste e scripts estão disponíveis no repositório Benchmark Bloom. Execute e confira por conta própria.

    Consulte a documentação 'Por que o Intlayer?' para obter mais detalhes.

    Comentários

    Ainda sem comentários. Seja o primeiro a compartilhar seus pensamentos.

    Artigos relacionados

    Últimos artigos