InícioAmbiente de testeVitrineAppDocBlog
    • EnglishInglês
      EN
    • РусскийRusso
      RU
    • 日本語Japonês
      JA
    • françaisFrancês
      FR
    • 한국어Coreano
      KO
    • 中文Chinês
      ZH
    • EspañolEspanhol
      ES
    • DeutschAlemão
      DE
    • العربيةÁrabe
      AR
    • ItalianoItaliano
      IT
    • British EnglishInglês (Reino Unido)
      EN-GB
    • PortuguêsPortuguês
      PT
    • हिन्दीHíndi
      HI
    • TürkçeTurco
      TR
    • polskiPolonês
      PL
    • IndonesiaIndonésio
      ID
    • Tiếng ViệtVietnamita
      VI
    • УкраїнськаUcraniano
      UK
    /
    Alt+←
    O que é internacionalização (i18n)?
    SEO e i18n
    Guia
    • i18n com next-i18next
    • i18n com next-intl
    Use Intlayer na sua solução
    • Automatizar next-i18next
    • Automatizar react-i18next
    • Automatizar next-intl
    • Automatizar react-intl
    • Automatizar vue-i18n
    Comparações
    • next-i18next vs next-intl vs Intlayer
    • react-i18next vs react-intl vs Intlayer
    Documentação
    1. Blog
    2. Per component vs centralized i18n
    Creation:2025-09-10Last update:2025-09-10
    Referência esta documentação ao seu assistente AI favorito
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    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ês
    Edit this doc

    If you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.

    GitHub link to the documentation
    Copy

    Copy doc Markdown to clipboard

    i18n por componente vs. centralizado

    A abordagem por componente não é um conceito novo. Por exemplo, no ecossistema Vue, o vue-i18n suporta i18n SFC (Single File Component). O Nuxt também oferece traduções por componente, e o Angular emprega um padrão similar através dos seus Feature Modules.

    Mesmo em um app Flutter, muitas vezes encontramos este padrão:

    bash
    Copiar código

    Copiar o código para a área de transferência

    lib/└── features/    └── login/        ├── login_screen.dart        └── login_screen.i18n.dart  # <- As traduções ficam aqui
    lib/features/login/login_screen.i18n.dart
    Copiar código

    Copiar o código para a área de transferência

    import 'package:i18n_extension/i18n_extension.dart';extension Localization on String {  static var _t = Translations.byText("en") +      {        "Hello": {          "en": "Hello",          "fr": "Bonjour",        },      };  String get i18n => localize(this, _t);}

    No entanto, no mundo React, vemos principalmente abordagens diferentes, que agruparei em três categorias:

    Abordagem centralizada (i18next, next-intl, react-intl, lingui)

    • (sem namespaces) considera uma única fonte para recuperar conteúdo. Por padrão, você carrega o conteúdo de todas as páginas quando seu app é carregado.

    Abordagem granular (intlayer, inlang)

    • refina a recuperação de conteúdo por chave ou por componente.

    Neste blog, não vou focar em soluções baseadas em compilador, que já abordei aqui: Compiler vs Declarative i18n. Note que i18n baseado em compilador (por exemplo, Lingui) simplesmente automatiza a extração e o carregamento do conteúdo. Por baixo do capô, eles frequentemente compartilham as mesmas limitações que outras abordagens.

    Note que quanto mais você refina a forma como recupera seu conteúdo, maior o risco de inserir estado e lógica adicionais nos seus componentes.

    As abordagens granulares são mais flexíveis do que as centralizadas, mas frequentemente implicam um compromisso. Mesmo que o "tree shaking" seja divulgado por essas bibliotecas, na prática acaba por carregar uma página em todas as línguas.

    Portanto, de forma geral, a decisão resume-se assim:

    • Se a sua aplicação tem mais páginas do que línguas, deve favorecer uma abordagem granular.
    • Se tiver mais línguas do que páginas, deve optar por uma abordagem centralizada.

    Obviamente, os autores das bibliotecas estão cientes dessas limitações e oferecem soluções alternativas. Entre elas: dividir em namespaces, carregar ficheiros JSON dinamicamente (await import()), ou purgar conteúdo em build time.

    Ao mesmo tempo, deve saber que quando carrega dinamicamente o seu conteúdo, introduz pedidos adicionais ao seu servidor. Cada useState extra ou hook significa um pedido extra ao servidor.

    Para resolver este ponto, o Intlayer sugere agrupar múltiplas definições de conteúdo sob a mesma chave; o Intlayer irá então mesclar esse conteúdo.

    Mas, de todas essas soluções, fica claro que a abordagem mais popular é a centralizada.

    Então por que a abordagem centralizada é tão popular?

    • Primeiro, o i18next foi a primeira solução a tornar-se amplamente utilizada, seguindo uma filosofia inspirada nas arquiteturas PHP e Java (MVC), que se baseiam numa separação estrita de responsabilidades (mantendo o conteúdo separado do código). Chegou em 2011, estabelecendo os seus padrões mesmo antes da grande mudança para arquiteturas baseadas em Componentes (como o React).
    • Depois, uma vez que uma biblioteca é amplamente adotada, torna-se difícil migrar o ecossistema para outros padrões.
    • Usar uma abordagem centralizada também facilita as coisas em sistemas de gestão de traduções (Translation Management Systems) como Crowdin, Phrase ou Localized.
    • A lógica da abordagem por componente é mais complexa do que a centralizada e requer tempo extra de desenvolvimento, especialmente quando é necessário resolver problemas como identificar onde o conteúdo está localizado.

    Ok, mas por que não ficar apenas com uma abordagem Centralizada?

    Deixe-me explicar por que isso pode ser problemático para a sua app:

    • Dados não utilizados: Quando uma página carrega, costuma-se carregar o conteúdo de todas as outras páginas. (Numa app de 10 páginas, isso significa 90% de conteúdo carregado que não é utilizado). Você faz lazy load de um modal? A biblioteca i18n não se importa, ela carrega as strings primeiro de qualquer forma.
    • Desempenho: A cada re-render, todos os seus componentes são hidratados com um payload JSON massivo, o que impacta a reatividade da sua app à medida que ela cresce.
    • Manutenção: Manter ficheiros JSON grandes é doloroso. Você tem de saltar entre ficheiros para inserir uma tradução, garantindo que não faltam traduções e que não restam chaves órfãs.
    • Sistema de design: Isto cria incompatibilidade com design systems (por exemplo, um componente LoginForm) e restringe a duplicação de componentes entre diferentes apps.

    "Mas inventámos Namespaces!"

    Claro, e isso é um enorme avanço. Vamos ver a comparação do tamanho do bundle principal de uma stack Vite + React + React Router v7 + Intlayer. Simulámos uma aplicação com 20 páginas.

    O primeiro exemplo não inclui traduções lazy-loaded por locale nem divisão por namespaces. O segundo inclui purga de conteúdo + carregamento dinâmico das traduções.

    Mostrar todo o conteúdo da tabela

    Abrir a tabela em um modal para ver todo o conteúdo claramente

    Bundle otimizado Bundle não otimizado
    bundle não otimizado bundle otimizado

    Portanto, graças aos namespaces, mudámos desta estrutura:

    bash
    Copiar código

    Copiar o código para a área de transferência

    locale/├── en.json├── fr.json└── es.json

    To this one:

    bash
    Copiar código

    Copiar o código para a área de transferência

    locale/├── en/│   ├── common.json│   ├── navbar.json│   ├── footer.json│   ├── home.json│   └── about.json├── fr/│   └── ...└── es/    └── ...

    Agora tem de gerir com precisão que parte do conteúdo da sua app deve ser carregada e onde. Em conclusão, a grande maioria dos projetos simplesmente ignora esta etapa devido à complexidade (veja o guia do next-i18next, por exemplo, para ver os desafios que isso representa (apenas) ao seguir as boas práticas). Consequentemente, esses projetos acabam com o problema do carregamento massivo de JSON explicado anteriormente.

    Note que este problema não é específico do i18next, mas de todas as abordagens centralizadas listadas acima.

    No entanto, quero relembrar que nem todas as abordagens granulares resolvem isto. Por exemplo, as abordagens vue-i18n SFC ou inlang não fazem, por si só, lazy load das traduções por locale, pelo que você está simplesmente a trocar o problema do tamanho do bundle por outro.

    Além disso, sem uma separação de responsabilidades adequada, torna-se muito mais difícil extrair e fornecer as suas traduções aos tradutores para revisão.

    Como a abordagem por componente do Intlayer resolve isto

    O Intlayer procede em vários passos:

    1. Declaração: Declare o seu conteúdo em qualquer parte da sua codebase usando ficheiros *.content.{ts|jsx|cjs|json|json5|...}. Isto assegura separação de responsabilidades enquanto mantém o conteúdo co-localizado com o código. Um ficheiro de conteúdo pode ser por locale ou multilíngue.
    2. Processamento: Intlayer executa uma etapa de build para processar a lógica JS, tratar fallbacks de traduções ausentes, gerar tipos TypeScript, gerenciar conteúdo duplicado, buscar conteúdo do seu CMS e mais.
    3. Purgamento: Quando sua aplicação é construída, o Intlayer purga o conteúdo não utilizado (um pouco como o Tailwind gerencia suas classes) substituindo o conteúdo da seguinte forma:

    Declaração:

    tsx
    Copiar código

    Copiar o código para a área de transferência

    // src/MyComponent.tsxexport const MyComponent = () => {  const content = useIntlayer("my-key");  return <h1>{content.title}</h1>;};
    tsx
    Copiar código

    Copiar o código para a área de transferência

    // src/myComponent.content.tsexport const {  key: "my-key",  content: t({    en: { title: "My title" },    fr: { title: "Mon titre" }  })}

    Processamento: Intlayer constrói o dicionário com base no arquivo .content e gera:

    json5
    Copiar código

    Copiar o código para a área de transferência

    // .intlayer/dynamic_dictionary/en/my-key.json{  "key": "my-key",  "content": { "title": "My title" },}

    Substituição: intlayer extracta seu componente durante o build da aplicação.

    - Modo de Importação Estática:

    tsx
    Copiar código

    Copiar o código para a área de transferência

    // Representação do componente em sintaxe semelhante a JSXexport const MyComponent = () => {  const content = useDictionary({    key: "my-key",    content: {      nodeType: "translation",      translation: {        pt: { title: "Meu título" },        en: { title: "My title" },        fr: { title: "Mon titre" },      },    },  });  return <h1>{content.title}</h1>;};

    - Modo de Importação Dinâmica:

    tsx
    Copiar código

    Copiar o código para a área de transferência

    // Representação do componente em sintaxe semelhante a JSXexport const MyComponent = () => {  const content = useDictionaryAsync({    en: () =>      import(".intlayer/dynamic_dictionary/en/my-key.json", {        with: { type: "json" },      }).then((mod) => mod.default),    // O mesmo para outras línguas  });  return <h1>{content.title}</h1>;};
    useDictionaryAsync usa um mecanismo semelhante ao Suspense para carregar o JSON localizado apenas quando necessário.

    Principais vantagens desta abordagem por componente:

    • Manter a declaração do conteúdo próxima dos seus componentes permite uma melhor facilidade de manutenção (por exemplo, mover um componente para outra app ou design system. Eliminar a pasta do componente remove também o conteúdo relacionado, como provavelmente já faz com os seus .test, .stories)

    /// Uma abordagem por componente impede que agentes de IA precisem saltar entre todos os seus diferentes ficheiros. Trata todas as traduções num só lugar, limitando a complexidade da tarefa e a quantidade de tokens utilizados.

    Limitações

    Claro, esta abordagem implica compensações:

    • É mais difícil ligar a outros sistemas de l10n e a ferramentas adicionais.
    • Fica-se preso (o que basicamente já acontece com qualquer solução i18n devido à sua sintaxe específica).

    É por isso que o Intlayer tenta fornecer um conjunto completo de ferramentas para i18n (100% free and OSS), incluindo tradução por IA usando o seu próprio AI Provider e as suas chaves de API. O Intlayer também fornece ferramentas para sincronizar os seus ficheiros JSON, funcionando como formatadores de mensagens do ICU / vue-i18n / i18next para mapear o conteúdo para os seus formatos específicos.

    O que é internacionalização (i18n)?
    Alt+→

    Nesta página

      As discussões são anônimas e regularmente revisadas para resolver problemas comuns. Sinta-se à vontade para compartilhar ideias de funcionalidades, feedback sobre a documentação ou qualquer coisa relacionada ao Intlayer, usamos essas informações para moldar nosso roadmap e melhorar o produto.

      lib/└── features/    └── login/        ├── login_screen.dart        └── login_screen.i18n.dart  # <- As traduções ficam aqui
      import 'package:i18n_extension/i18n_extension.dart';extension Localization on String {  static var _t = Translations.byText("en") +      {        "Hello": {          "en": "Hello",          "fr": "Bonjour",        },      };  String get i18n => localize(this, _t);}
      locale/├── en.json├── fr.json└── es.json
      locale/├── en/│   ├── common.json│   ├── navbar.json│   ├── footer.json│   ├── home.json│   └── about.json├── fr/│   └── ...└── es/    └── ...
      // src/MyComponent.tsxexport const MyComponent = () => {  const content = useIntlayer("my-key");  return <h1>{content.title}</h1>;};
      // src/myComponent.content.tsexport const {  key: "my-key",  content: t({    en: { title: "My title" },    fr: { title: "Mon titre" }  })}
      // .intlayer/dynamic_dictionary/en/my-key.json{  "key": "my-key",  "content": { "title": "My title" },}
      // Representação do componente em sintaxe semelhante a JSXexport const MyComponent = () => {  const content = useDictionary({    key: "my-key",    content: {      nodeType: "translation",      translation: {        pt: { title: "Meu título" },        en: { title: "My title" },        fr: { title: "Mon titre" },      },    },  });  return <h1>{content.title}</h1>;};
      // Representação do componente em sintaxe semelhante a JSXexport const MyComponent = () => {  const content = useDictionaryAsync({    en: () =>      import(".intlayer/dynamic_dictionary/en/my-key.json", {        with: { type: "json" },      }).then((mod) => mod.default),    // O mesmo para outras línguas  });  return <h1>{content.title}</h1>;};