0% concluído

Curso de Internacionalização (i18n)

Por que fazer, como fazer e como manter — com demos ao vivo rodando direto no navegador.

A tese deste curso: internacionalização (i18n) não é "traduzir o app". É a arquitetura que permite que o mesmo código sirva qualquer idioma, formato de data, número e moeda — sem reescrever nada. Quem faz i18n só quando precisa, paga caro depois. Quem faz desde o começo, escala com custo marginal.

Este curso é interativo: há demos ao vivo (mude o idioma da própria página, veja plurais e datas em várias línguas) e checklists com progresso salvo. Conteúdo em PT-BR, baseado em documentação oficial (Unicode CLDR, ICU, FormatJS) e guias de 2026.

M1

Por que internacionalizar

~25 min · motivação

Antes do "como", o "porquê". Internacionalizar é decisão de negócio, não só de engenharia.

i18n ≠ l10n ≠ tradução

TermoSignificadoQuem faz
Internacionalização (i18n)Preparar o código para suportar qualquer idioma/região (strings externalizadas, datas, RTL)Desenvolvedores
Localização (l10n)Adaptar o produto a um mercado específico (tradução + moeda + cultura)PM, UX writers, tradutores
TraduçãoConverter texto de um idioma para outroTradutores / máquinas

Regra: você não pode pular o i18n e esperar que o l10n dê certo. "i18n é a fundação; l10n são os cômodos." Strings hardcoded e frases montadas por concatenação tornam qualquer tradução impossível sem reescrita.

Motivos práticos

  • Mercado: bilhões de usuários não falam inglês. Localizar é a diferença entre "serve para mim" e "feito para mim".
  • Confiança e conversão: o usuário compra no próprio idioma. Checkout, preço e moeda errados quebram confiança na hora.
  • Requisito legal/regulatório: vários mercados (UE, Canadá, Brasil em certos contratos) exigem idioma e formatos locais.
  • SEO: conteúdo no idioma local é indexado e buscado no idioma local (hreflang).
  • Acessibilidade: lang correto afeta leitores de tela, hifenização e pronúncia.
  • Custo futuro: adicionar o 2º idioma em código i18n-ready custa frações do custo de retrofit em app legado.

Quando começar

Regra de ouro: comece a i18n no primeiro dia — ou, no mínimo, antes do segundo idioma. O maior erro das equipes é lançar 5 idiomas de uma vez. Comece com 1 idioma estrangeiro: ele força a resolver extração, handoff de tradução, QA e release — e os próximos idiomas ficam baratos.

Fig. M1-1 — Custo cumulativo: fazer i18n desde o início tem custo pequeno e estável; retrofit depois do lançamento custa uma ordem de grandeza a mais (e trava o produto durante a migração).

Exercícios M1

  • Ex.1.1 Explique a diferença entre i18n, l10n e tradução em 3 frases.
  • Ex.1.2 Liste 3 motivos para internacionalizar um app seu (ou hipotético).
  • Ex.1.3 Por que começar com um único idioma estrangeiro em vez de cinco?
M2

Conceitos fundamentais

~35 min · o vocabulário

Os tijolos de qualquer sistema i18n: locale, Unicode, CLDR, RTL e a API Intl do navegador.

Locale: idioma + região

Um locale identifica idioma e variante regional. O código pt-BR = português do Brasil; pt-PT = Portugal; en-US vs en-GB. Formato: lang-REGION (BCP 47). Usar apenas pt pode gerar formatos genéricos errados (ex.: moeda com símbolo regional incorreto).

LocaleDataNúmeroMoeda
pt-BR25/08/20261.234,56R$ 1.234,56
en-US08/25/20261,234.56$1,234.56
de-DE25.08.20261.234,561.234,56 €
ar-EG٢٥/٠٨/٢٠٢٦١٬٢٣٤٫٥٦١٬٢٣٤٫٥٦ ج.م

Unicode e UTF-8

Todo o texto deve viver em UTF-8 (padrão de fato). Sem isso: mojibake (caracteres quebrados), problemas com emoji, caracteres acentuados e scripts complexos. Use UTF-8 em banco, API, HTML e arquivos de tradução.

CLDR: as regras do mundo em dados

O CLDR (Unicode Common Locale Data Repository) é a fonte canônica de regras por idioma: formatos de data/número/moeda, plurais, ordem de classificação, nomes de meses. Todo sistema i18n sério usa CLDR (por baixo do Intl, do ICU, do i18next). Não invente regra: use os dados.

RTL: idiomas da direita para a esquerda

Árabe, hebraico, persa e urdu lêem da direita para a esquerda. O layout precisa espelhar (menu à direita, progresso invertido, ícones de "voltar" virados). Adicionar RTL depois do lançamento é caro — desenhe o layout com dir="rtl" em mente desde o início.

Demo ao vivo — formatação com Intl

Intl.NumberFormat / DateTimeFormat / NumberFormat(currency) — o mesmo valor em 5 locales

Exercícios M2

  • Ex.2.1 O que diferencia en-US de en-GB? Dê 2 exemplos práticos.
  • Ex.2.2 Por que usar CLDR em vez de "regras caseiras" de plural/data?
  • Ex.2.3 O que muda num layout quando o idioma é RTL?
M3

Arquitetura de strings

~40 min · o coração do i18n

A regra número um: nenhum texto visível hardcoded. Toda string visível sai do código e entra num arquivo de recursos.

Externalizar: o antes e o depois

// ❌ Antes (hardcoded + concatenação — o pesadelo)
<button>Você tem " + count + " mensagens novas</button>

// ✅ Depois (chave + interpolação)
<button>{t('inbox.newMessages', { count })}</button>

Abordagens de mensagens

AbordagemComo funcionaExemplos
Key-basedO código usa uma chave (inbox.newMessages); o texto fica nos arquivos de traduçãoi18next, react-i18next
Source-basedO código usa o texto em inglês como chave; extração automáticareact-intl (defaultMessage), gettext
ICU MessageFormatFormato padrão que embute plurais/selects na própria mensagemreact-intl, FormatJS, Lingui
MessageFormat 2 (MF2)Padrão novo da Unicode (candidato final 2025) — plurais por .match explícitoadoção ainda inicial

Regras de ouro para strings

  • Nunca concatene ("Você tem " + n + " itens") — cada idioma tem ordem de palavras diferente. Use interpolação {n}.
  • Não traduza código (variáveis, nomes de arquivos, mensagens de log) — só o que o usuário vê.
  • Dê contexto à chave (ex.: menu.save vs dialog.save) para o tradutor acertar o sentido.
  • Mantenha frases inteiras juntas — quebrar em pedacinhos impede boa tradução (gênero, ordem, concordância).
  • Idioma de fallback sempre — se faltar tradução, mostra o idioma base em vez de quebrar.
  • Interpolação tipada — as libs validadas em build (react-intl/FormatJS, Lingui) pegam erro de chave/variável na hora.

Plurais: o erro que todo mundo comete

Nunca use count === 1 ? singular : plural. O inglês tem 2 formas; o árabe tem 6 (zero, one, two, few, many, other); o russo 3; o japonês 1. Uma checagem binária produz texto gramaticalmente errado para 5 das 6 categorias do árabe. Use Intl.PluralRules ou uma lib CLDR-aware.

// en.json (react-intl / ICU)
{ "inbox.items": "{count, plural, one {# mensagem nova} other {# mensagens novas}}" }

// ar.json — precisa cobrir 6 categorias
{ "inbox.items": "{count, plural, zero {# mensajes} one {# mensaje} two {# mensajen} few {# mensajes} many {# mensaje} other {# mensaje}}" }

Demo ao vivo — plurais por idioma

Intl.PluralRules — quantas formas cada idioma tem 2

Exercícios M3

  • Ex.3.1 Reescreva "Você tem 3 mensagens" e "Você tem 1 mensagem" como uma única chave ICU com plural.
  • Ex.3.2 Por que concatenar frases quebra a tradução? Dê um exemplo de ordem de palavras que muda.
  • Ex.3.3 Teste a demo de plurais em ar e ru. Quantas formas diferentes aparecem?
M4

Datas, números e plurais na prática

~35 min · formatação

Formatar é tão i18n quanto traduzir — e é onde mais se erra. A regra: armazene sempre em UTC/ISO; formate só na hora de exibir, com a API Intl.

Datas, horas e fuso

  • Armazene em UTC (ISO 8601: 2026-08-25T20:30:00Z) em banco/API.
  • Converta para o fuso do usuário apenas na exibição — o servidor nunca deve "adivinhar" o fuso.
  • Nunca formate data com strings fixas ("DD/MM/YYYY") — use Intl.DateTimeFormat.
new Intl.DateTimeFormat('de-DE', { dateStyle: 'long', timeStyle: 'short', timeZone: 'Europe/Berlin' })
  .format(new Date())
// → "25. August 2026, 22:30"

Números e moeda

Intl.NumberFormat e Intl.NumberFormat(..., { style: 'currency', currency }) cuidam de separadores, símbolo e posição da moeda. Nunca hardcode o símbolo nem a posição. Guarde o valor numérico puro (não use "R$ 1.234,56" como string de dados).

Text expansion

Fig. M4-1 — Expansão de texto: alemão/finlandês/russo podem ficar até ~30% mais longos que o inglês. Layout com largura fixa corta o texto. Nunca use largura fixa — deixe ~20–30% de folga.

Bibliotecas (escolha consciente)

LibFormatoPontos fortesQuando usar
react-intl (FormatJS)ICU MessageFormatPadrão, tipado, RSC-ready (v10), plurais no textoReact/Next; quer padrão estrito
i18next / react-i18nextKey-based JSON (sufixos _one/_other)6,3M+ downloads/semana, plugins (detector, backend), <Trans>React; ecossistema + flexibilidade
LinguiICU (macros)Compilação e validação em buildTime que quer checagem estática forte
gettext / Fluent.po / FluentMaduro (gettext), Fluent legível p/ tradutoresApps desktop/CLI; Mozilla-style

Para quem usa TS puro (como a landing da issoai.com), uma tipagem de dicionário (interface de chaves por idioma) já dá segurança em tempo de compilação — ver M7.

Exercícios M4

  • Ex.4.1 Onde você armazena timestamps? Onde converte para o fuso do usuário?
  • Ex.4.2 Teste a demo do M2: o mesmo número em ar-EG (dígitos arábicos) vs en-US. O que muda além do separador?
  • Ex.4.3 Escolha uma lib para o seu stack e justifique (ICU vs key-based).
M5

Roteamento, SEO e RTL

~30 min · a camada web

Na web, o idioma também vive na URL, no HTML e nos metadados.

Roteamento por idioma

Três padrões comuns:

  • Prefixo de caminho: /pt, /en, /es (claro, padrão em Next.js multi-idioma).
  • Subdomínio: pt.issoai.com (separa mercados, útil para geo).
  • Negociação por cabeçalho Accept-Language: sem prefixo (mais simples, pior para SEO/manter link).

Metadados essenciais

<html lang="pt-BR" dir="ltr">          <!-- idioma + direção do documento -->
<link rel="alternate" hreflang="en" href="https://site.com/en/" />
<link rel="alternate" hreflang="pt-BR" href="https://site.com/pt/" />
<link rel="alternate" hreflang="x-default" href="https://site.com/" />
  • lang: correto para leitores de tela, hifenização e seletores CSS :lang().
  • dir="rtl": para árabe/hebraico; o CSS lógico (margin-inline-start, inset-inline) espelha sozinho.
  • hreflang: diz ao Google qual URL serve cada idioma/região (SEO).

Detecção e fallback

Não confie só no Accept-Language — usuários mudam de idioma o tempo todo. Dê sempre um seletor visível, respeite a escolha (cookie/localStorage) e use o header só como padrão inicial. Sempre tenha um fallback (idioma base) para chaves ausentes.

Exercícios M5

  • Ex.5.1 Para um site seu, qual roteamento escolheria (prefixo/subdomínio/header)? Por quê?
  • Ex.5.2 Monte os metadados lang/dir/hreflang para 2 idiomas.
  • Ex.5.3 Como você faz fallback de tradução? (chave ausente → idioma base).
M6

Workflow e qualidade

~35 min · do código à produção

Tradução boa é processo, não acaso. O pipeline se repete a cada release.

O pipeline contínuo

Fig. M6-1 — O ciclo: extrair → traduzir → revisar → integrar → publicar → medir. Automatize extração e integração; a revisão humana fica só onde importa.

Ferramentas e camadas

CamadaFerramentasPapel
ExtraçãoFormatJS CLI, i18next-parser, Lingui extract, babel-gettextVarrer o código e gerar o arquivo-base de strings
GerenciamentoCrowdin, Lokalise, POEditor, SimpleLocalize, Locize, TransifexTMS: tradutores, glossário, memória de tradução, contexto
TraduçãoHumana + IA (DeepL/Google) com revisãoUI/onboarding: IA + revisão; jurídico/marketing: humano
Integração/QACI (pull automático), pseudo-localização, checagem de chavesNunca lançar release com chave ausente/quebrada

Pseudo-localização (a técnica que salva)

A pseudo-localização substitui cada caractere por uma versão acentuada/alongada (Çǿńtìńùé) e testa em runtime: revela texto hardcoded que não foi externalizado e layouts que estouram com texto longo. Rode-a em QA antes de cada release.

Checklist de qualidade

  • Completude: nenhuma chave ausente em nenhum idioma (fallback cobre, mas reporte).
  • Formato: datas/números/moeda via Intl; nada hardcoded.
  • RTL: testar árabe/hebraico em todas as telas.
  • Expansão: telas com texto longo (30%+) não quebram.
  • Contexto: tradutor vê screenshot/contexto da chave.
  • CI: build falha se chave inválida ou tradução incompleta.

Exercícios M6

  • Ex.6.1 Desenhe o seu pipeline (extrair→…→publicar) e marque o que automatizar.
  • Ex.6.2 Rode pseudo-localização no seu app e anote 3 problemas que apareceram.
  • Ex.6.3 Escolha um TMS (ou fluxo manual) e justifique.
M7

Caso real: issoai.com (lib/i18n.ts)

~30 min · o que fizemos na prática

A landing da issoai.com é trilíngue (pt/en/es) e roda exatamente os conceitos deste curso — sem lib: um dicionário tipado em TypeScript. Vamos ver como e por quê.

A arquitetura

  • Uma única fonte de verdade: lib/i18n.ts com uma interface Dict (o "contrato" de chaves) e um objeto por idioma (pt, en, es).
  • ProjectCopy: o tipo de cada card de produto — name, tag, status, desc, f1, f2, f3, a1. Se uma chave faltar num idioma, o compilador quebra (segurança em tempo de build, sem runtime).
  • Roteamento: o componente recebe lang e o seletor troca t (o dicionário) — re-render, sem recarregar.
// lib/i18n.ts (simplificado)
export interface ProjectCopy {
  name: string; tag: string; status: string;
  desc: string; f1: string; f2: string; f3: string; a1: string;
}
export interface Dict {
  projects: { juice: ProjectCopy; sentinela: ProjectCopy; estrategias: ProjectCopy; /* ... */ };
  how: { eyebrow: string; title: string; steps: HowStep[] };
  /* ... */
}
export const pt: Dict = { projects: { juice: { name: "Juice", /* ... */ }, /* ... */ } };

Demo ao vivo — a landing você mesmo

Seletor de idioma (mesma mecânica da landing)

Lições do caso real

  • Tipar o dicionário evita a classe de bugs mais comum (chave errada/tradução faltando) sem pagar runtime de lib.
  • Escopo certo: trilíngue para a home (marketing, precisa de portabilidade); os cursos são PT-only por decisão de produto. Nem tudo precisa de N idiomas.
  • Conteúdo de marca fica fora do dicionário quando escrito igual nos idiomas (ex.: nomes de produtos no rodapé).
  • Armadilha real: texto embutido em componentes (aria-label, placeholder) também é string — precisa entrar no dicionário.

Armadilhas finais (resumo)

ArmadilhaComo evitar
Hardcode de texto/aria-label/placeholderExtração automática + pseudo-localização
Concatenação e plural binárioInterpolação + Intl.PluralRules/ICU
Data/número/moeda com formato fixoIntl.* sempre; UTC no storage
Largura fixa / layout en-US-onlyLayout fluido (+20–30%) e CSS lógico
RTL depois do lançamentoPlanejar dir/rtl desde o início
Tradução sem contextoTMS com screenshot/contexto + glossário
Confiar só em máquina em texto sensívelIA + revisão; humano em jurídico/marketing

Exercícios M7

  • Ex.7.1 No seu projeto, liste 3 strings que estão hardcoded hoje e onde deveriam ir.
  • Ex.7.2 Vale a pena tipar o dicionário (TS) no seu caso? Por quê?
  • Ex.7.3 Decida quantos idiomas o seu produto PRECISA (não "poderia ter") e justifique.
📚

Fontes e links

⚠️ Ecossistema muda rápido — verifique a documentação oficial da lib/framework escolhida (estado deste curso: ago/2026).