Communitygithub.com

Web i18n

Internacionalização de nível profissional para aplicações web: suporte a múltiplos idiomas, tradução de UI, extração de strings, tratamento de plurais/gênero/ICU, roteamento de locale, layouts RTL, formatação localizada de datas/números/moedas, hreflang/SEO e validação de catálogos de tradução. Orientações agnósticas de framework para React, Next.js, Vue, Svelte, Astro, Remix e JavaScript puro, com exemplos para next-intl, react-i18next, FormatJS e Lingui.

O que é Web i18n?

Esta habilidade fornece um guia completo e de nível profissional para implementar internacionalização (i18n) e localização (l10n) em aplicações web modernas. Diferente de uma simples 'passada de tradução', o foco está em projetar e arquitetar o aplicativo desde o início para suportar qualquer localidade, evitando casos especiais e retrabalho. A habilidade cobre desde a auditoria de código existente para encontrar strings hardcoded até a configuração de catálogos de tradução, roteamento baseado em locale, formatação de dados sensíveis ao locale (datas, números, moedas, listas) e tratamento correto de plurais, gêneros e concordância gramatical. Também aborda tópicos avançados como suporte a RTL (direita-para-esquerda), SEO com hreflang e sitemaps, e integração com sistemas de gerenciamento de tradução (TMS). A habilidade é agnóstica de framework, com exemplos práticos para as principais bibliotecas e frameworks do ecossistema JavaScript/TypeScript, além de scripts e referências auxiliares localizados no diretório da habilidade.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Nelsutro/agent-skills/tree/HEAD/web-i18n

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

i18n para Web

Internacionalização é arquitetura, não uma passada de tradução no final. O objetivo é uma interface que renderize corretamente para qualquer localidade sem casos especiais: sem sentenças concatenadas, sem formatos fixos, sem layout que quebre quando o alemão é 35% mais longo ou o árabe inverte a direção.

Quando Usar Esta Habilidade

Use quando o usuário pedir para:

  • Adicionar um idioma, seletor de idioma ou roteamento de locale
  • Traduzir um aplicativo, extrair strings ou configurar um fluxo de tradução
  • Corrigir bugs de pluralização, gênero ou "1 item / 2 items"
  • Formatar datas, números, moeda, tempo relativo ou listas por localidade
  • Suportar RTL (árabe, hebraico, persa, urdu)
  • Adicionar hreflang, metadados localizados ou sitemaps multilíngues
  • Revisar uma configuração de i18n existente, chaves ausentes ou desvio de tradução
  • Escolher entre next-intl, react-i18next, FormatJS, Lingui ou um TMS

Referências relacionadas estão em references/ e scripts em scripts/. Leia apenas o que a tarefa precisa. Os caminhos são relativos ao diretório desta habilidade (o agente imprime o caminho quando a habilidade é carregada).

Inegociáveis

  1. Nunca construa uma frase concatenando fragmentos traduzidos. Traduza a mensagem inteira com placeholders: "{count} novas mensagens", não t('novas') + ' ' + count + ' ' + t('mensagens').
  2. Nunca use texto em inglês como chave. Chaves são identificadores estáveis (inbox.unreadCount), para que alterações de texto não quebrem as buscas nem invalidem todas as traduções.
  3. Toda string voltada ao usuário é externalizada, incluindo aria-label, alt, title, placeholder, erros de validação, estados vazios, toasts, e-mails, notificações push e saída de PDF.
  4. Nunca codifique formatos. Sem toFixed(2) + '$', sem MM/DD/YYYY manual, sem nomes de meses em inglês, sem suposições de 24h. Use Intl.
  5. Um locale é uma tag BCP 47, não um idioma. pt-BR e pt-PT, zh-Hans e zh-Hant, es-MX e es-ES são produtos diferentes. Armazene a tag completa.
  6. O estado do locale deve ter escopo de requisição no servidor. Nunca coloque o locale atual em uma variável mutável no nível do módulo durante SSR; requisições concorrentes vazarão idiomas entre si.
  7. Traduções são entrada não confiável se vierem de um TMS, comunidade ou CMS. Nunca as passe para innerHTML/dangerouslySetInnerHTML sem sanitizar.

Fluxo de Trabalho

1. Audite antes de alterar o código

  • Detecte a stack, o roteador e a biblioteca de i18n existente antes de propor qualquer coisa.
  • Encontre strings hardcoded:
    node scripts/scan-hardcoded.mjs src
    
    Trate a saída como candidatos, não como verdade absoluta: pule fixtures de teste, constantes, URLs e logs voltados ao desenvolvedor.
  • Faça um inventário de todas as localidades atualmente enviadas e onde os catálogos residem.
  • Identifique as áreas de maior risco primeiro: autenticação, checkout, erros, e-mails e textos legais.

2. Decida a arquitetura (escreva-a)

Responda a estas perguntas antes de escrever código:

DecisãoPadrãoNotas
Estratégia de localePrefixo de caminho /es/Melhor SEO e capacidade de cache. Subdomínios/ccTLDs apenas com um motivo.
Locale padrãoIdioma de origemNão redirecione automaticamente visitantes de primeira viagem para um locale adivinhado sem uma opção de substituição.
Detecção de localeURL → cookie → Accept-Language → padrãoA URL vence para que links compartilhados nunca mudem de idioma.
Formato do catálogoJSON por localidade por namespaceCarregue namespaces sob demanda; nunca envie todas as localidades para o cliente.
Sintaxe de mensagemICU MessageFormatPortátil entre bibliotecas e TMSs; suporta plural/select/ordinal.
Estilo da chavefeature.component.elementEstável, pesquisável, sem texto em inglês.
Fonte da verdade da traduçãoTMS ou repositório, não ambosEscolha um; sincronize em uma direção no CI.
FallbackCadeia de locale es-MX → es → enRegistre fallbacks em desenvolvimento para detectar lacunas.

3. Externalize e estruture as mensagens

  • Substitua strings por chaves semânticas e mova o texto para o catálogo de locale base.
  • Use uma chave por frase completa. Se uma frase contiver um link ou palavra em negrito, use interpolação rich-text em vez de dividi-la.
  • Mantenha os placeholders nomeados ({nomeUsuario}), nunca posicionais ({0}).
  • Não coloque HTML em catálogos, a menos que a biblioteca suporte explicitamente tags tipadas.
  • Agrupe por superfície do produto, não por página: checkout.json, auth.json, errors.json.

Consulte references/icu.md para regras de plural, select, ordinal e rich-text.

4. Configure o roteamento e a renderização de locale

  • Derive o locale da rota, valide-o contra a lista de suportados e retorne 404 para locales desconhecidos em vez de cair silenciosamente no fallback.
  • Forneça um seletor de idioma que preserve o caminho atual e a query string.
  • Defina <html lang="..."> e <html dir="..."> no documento renderizado no servidor.
  • Carregue apenas o catálogo do locale ativo mais o fallback padrão.
  • Torne o mesmo catálogo disponível para renderização no servidor e no cliente; evite lógica de tradução duplicada ou divergente.

Configuração e armadilhas específicas de framework: references/frameworks.md. Roteamento, detecção, hreflang e sitemaps: references/routing-seo.md.

5. Lidere com valores sensíveis ao locale

Use Intl (ou o wrapper do framework), nunca formatação manual:

new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)
new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeZone }).format(date)
new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(-3, 'day')
new Intl.ListFormat(locale, { type: 'conjunction' }).format(items)
new Intl.PluralRules(locale).select(count)
new Intl.Collator(locale).compare(a, b)
  • Armazene timestamps em UTC; renderize no fuso horário do usuário, não no do servidor.
  • Moeda não é o mesmo que locale: um usuário francês pode pagar em USD.
  • Ordene/pesquise com Intl.Collator, não com < ou localeCompare() sem um locale.
  • Conversão de maiúsculas/minúsculas: toLocaleUpperCase(locale), porque o i turco se torna İ.

6. Projete para expansão e RTL

  • Considere +30–40% de comprimento de texto (alemão, finlandês, russo) e evite botões de largura fixa ou rótulos de linha única.
  • Deixe os contêineres crescerem, permita quebra de linha e teste com pseudo-localização:
    node scripts/pseudo-localize.mjs locales/en.json --out locales/en-XA.json
    
  • Use propriedades CSS lógicas (margin-inline-start, padding-inline, inset-inline) em vez de left/right.
  • Espelhe ícones direcionais (setas, chevrons, progresso), não logotipos, relógios, controles de mídia ou marcas de verificação.
  • Envolva conteúdo inline gerado pelo usuário em <bdi> ou dir="auto".

Lista de verificação completa de RTL: references/rtl.md.

7. Configure a garantia de qualidade da tradução no CI

No mínimo, falhe a construção quando os catálogos forem estruturalmente inválidos:

node scripts/check-locales.mjs locales --base en

Ele verifica chaves ausentes/extras, valores vazios, incompatibilidades de placeholder e sintaxe ICU quebrada. Adicione a compilação ICU via CLI da sua biblioteca quando disponível:

npx formatjs compile-folder --ast locales/en locales/compiled/en

Adicione testes para categorias de plural, saída de data/número com um fuso horário fixo e renderização RTL. Consulte references/testing.md.

Decisões e Armadilhas Principais

Seleção de biblioteca

NecessidadeEscolha
Next.js App Router, componentes de servidornext-intl
React SPA, ecossistema maduro, muitos pluginsreact-i18next
ICU-first, agnóstico de framework, extração CLIFormatJS (react-intl)
Catálogos em tempo de compilação, runtime pequeno, segurança de tiposLingui
Vuevue-i18n
Sveltesvelte-i18n ou paraglide
Astroroteamento i18n integrado + sua biblioteca de mensagens
Vanilla / mínimoIntl + um carregador de catálogo pequeno

Não introduza uma segunda biblioteca de i18n em um aplicativo que já tem uma. Estenda a configuração existente.

Pluralização

As categorias de plural são específicas do idioma. O inglês tem one/other; o árabe tem zero/one/two/few/many/other; o japonês tem apenas other. Nunca escreva count === 1 ? ... : ....

"inbox.unread": "{count, plural, =0 {Nenhuma mensagem não lida} one {# mensagem não lida} other {# mensagens não lidas}}"

Gênero e concordância gramatical

Use select em uma variável de gênero e mantenha o valor da variável neutro em relação ao idioma (female, male, neutral, unknown), não palavras localizadas:

"profile.greeting": "{gender, select, female {Bem-vinda, {name}} male {Bem-vindo, {name}} other {Bem-vindo/a, {name}}}"

Se um idioma precisar de concordância em adjetivos, substantivos ou artigos, exponha o gênero/caso gramatical como um argumento em vez de hackear a string.

Rich text

Não divida "Leia nossos " + link + " termos" em três chaves. Use interpolação de tags:

"signup.terms": "Eu concordo com os <link>Termos de Serviço</link>"
t.rich('signup.terms', { link: (chunks) => <a href="/terms">{chunks}</a> })

SEO

  • Uma URL indexável por localidade; nunca sirva conteúdo traduzido por máquina por trás da mesma URL.
  • hreflang auto-referente mais x-default; URLs absolutas; inclua todas as localidades.
  • Localize <title>, meta description, tags OG e dados estruturados.
  • Não combine hreflang com noindex em páginas localizadas.

Detalhes em references/routing-seo.md.

Cache

  • O locale no caminho torna o cache do CDN direto. Se você negociar por cabeçalho, envie Vary: Accept-Language e espere fragmentação de cache.
  • Não armazene em cache a preferência de idioma personalizada em uma resposta de CDN compartilhada.
  • Mantenha Content-Language consistente com o locale renderizado.

Lista de verificação de revisão

Antes de declarar o i18n concluído:

  • Nenhuma string voltada ao usuário hardcoded em componentes, e-mails ou erros
  • Nenhuma concatenação de sentenças; cada mensagem é uma unidade traduzível completa
  • Chaves são semânticas e estáveis; nenhum texto em inglês como chave
  • Todo placeholder existe em todas as localidades; a sintaxe ICU compila
  • Plurais usam Intl.PluralRules/ICU, não count === 1
  • Gênero/concordância tratados via select, não sufixos "/a"
  • Datas, números, moeda, tempo relativo usam Intl com locale/fuso horário explícitos
  • <html lang> e dir estão corretos na primeira pintura (SSR), não definidos em um efeito
  • Seletor de locale preserva caminho/query e funciona com links profundos
  • Locale desconhecido retorna 404, não um fallback silencioso
  • Layout sobrevive a +35% de expansão e RTL
  • hreflang, canônico, sitemap e metadados localizados estão corretos
  • Validação de catálogo é executada no CI
  • Comportamento de fallback é intencional e observável

Anti-padrões

  • t('ola') + ', ' + nome — sentenças fragmentadas
  • chave={textoEmIngles} — texto traduzido como chave
  • count === 1 ? 'item' : 'itens' — lógica de plural apenas em inglês
  • new Date().toLocaleDateString() sem locale ou fuso horário
  • value.toFixed(2) para dinheiro
  • locale === 'ar' && { direction: 'rtl' } espalhado pelos componentes em vez de um dir no nível do documento
  • Definir <html lang> dentro de useEffect — causa um flash e quebra crawlers
  • Carregar todas as localidades no pacote inicial
  • Redirecionar automaticamente por IP sem forma de substituir
  • Traduzir nomes próprios, nomes de produtos, identificadores de código ou URLs
  • Strings traduzidas por máquina e enviadas sem revisão
  • Assumir que um arquivo de catálogo escala além de algumas centenas de chaves

Habilidades Relacionadas