Pular para o conteúdo
~/.primo-academy.sh
☰ Aulas · i18n / l10n: Intl APIs, ICU MessageFormat, RTL, translation workflow e pseudo-localization · 0/7
Recomendado: essencial

i18n vs l10n: o que e cada, separacao codigo/conteudo, Locale vs Language

6 min de leitura

fonte

Voce ja' mandou o app pra staging e o product manager pede: "agora pra ingles, espanhol e japones". Ate aqui, seu codigo tem strings hardcoded em PT-BR. Voce troca o texto e acha que terminou. Ate o usuario japones reclamar que "10,5" e' 10.5 (separador decimal invertido), o plural ta errado ("1 item" / "2 items" em ingles funciona, em polones e' 1 forma, "5 items" e' outra forma, "22 items" e' outra forma), e o layout quebra porque o arabes le da direita pra esquerda. Esse no cobre a diferenca entre "traduzir" e "internacionalizar de verdade".

i18n (internacionalizacao) e' o processo de preparar o codigo pra servir multiplas linguas e regioes. l10n (localizacao) e' o ato de adaptar o conteudo pra uma regiao especifica. i18n vem primeiro (no codigo); l10n vem depois (no conteudo). Trocar texto e' l10n - mas se o codigo nao foi preparado (i18n), l10n quebra.

Voce sai de "trocar strings pra ingles" pra "arquitetura que serve qualquer locale sem retrabalho".

O essencial 🟢

i18n vs l10n em uma frase:

  • i18n (internacionalizacao): o codigo suporta multiplas linguas/regioes (abstracoes, sem strings hardcoded, formatacao locale-aware).
  • l10n (localizacao): o conteudo especifico de uma regiao (traducoes, imagens locais, formato de data/moeda).

Analogia: i18n e' construir uma casa com encanamento que aguente agua quente e fria (infraestrutura). l10n e' escolher a temperatura em cada comodo (conteudo). Casa sem i18n = so' agua fria, e' impossivel "localizar" pra agua quente sem quebrar tudo.

Exemplos praticos:

i18n (codigo)l10n (conteudo)
Extrair strings pra arquivos de traducaoTraduzir "Salvar" pra "Save"
Usar Intl.NumberFormat em vez de templateEscolher formato USD vs BRL
Implementar pluralizacao com ICUEscrever "1 item" / "2 items"
Suporte a RTL via CSS logical propertiesAdicionar layout arabe/hebraico
Detectar locale do userEscolher qual dos 12 idiomas servir

Codigo com i18n ruim vs bom:

// ❌ i18n ruim: hardcoded
const mensagem = `Voce tem ${count} items no carrinho`;
// Quebra em polones (1 forma vs 5 vs 22),
// arabe (RTL), japones (sem plural simples)

// ✅ i18n bom: extraido, formatado, plural-aware
const mensagem = t("cart.items", { count });
// ICU decide plural correto pro locale
// t() carrega de arquivo de traducao

Locale vs Language - a diferenca que causa bug. Language = idioma (pt, en, ja). Locale = idioma + regiao + variacoes (pt-BR, en-US, en-GB, zh-Hant-TW). pt-BR usa R$, dd/MM/yyyy. pt-PT usa €, dd-MM-yyyy. Ingles americano e britanico divergem em datashort, numero (1,000.50 vs 1.000,50), e ate ortografia (color vs colour).

Language tags seguem BCP 47: <lang>-<script>-<region>-<variant>.

pt-BR          # portugues, Brasil
pt-PT          # portugues, Portugal
en-US          # ingles, EUA
en-GB          # ingles, Reino Unido
zh-Hans-CN     # chines simplificado, China
zh-Hant-TW     # chines tradicional, Taiwan
ar             # arabe (sem regiao, default Egypt)
sr-Latn-RS     # serbio, script latino, Serbia

Por que importa? Quando o user define Accept-Language: pt-BR, o servidor negocia e serve o locale mais proximo. Quando o codigo cliente formata datas, usa pt-BR pra escolher Intl, e o usuario ve "25/12/2026" - e nao "12/25/2026".

Separacao de concerns: 3 camadas.

1. Codigo: extrai strings, usa Intl, formata com ICU
2. Conteudo: arquivos JSON/YAML com traducoes por locale
3. Cultura: imagens, cores, simbolos (nao traduziveis, mas locais)

Exemplo de extracao:

// ❌ Antes: string hardcoded
function botaoSalvar() {
  return <button>Salvar</button>;
}

// ✅ Depois: chave de traducao
function botaoSalvar() {
  const { t } = useTranslation();
  return <button>{t("common.save")}</button>;
}

// Arquivo: locales/pt-BR/common.json
// { "common": { "save": "Salvar" } }

// Arquivo: locales/en/common.json
// { "common": { "save": "Save" } }

Quando NAO precisa de i18n:

  • App interno de uma empresa so' em portugues, sem plano de expansao.
  • Landing page de evento unico.
  • Demo/MVP descartavel.

Mesmo assim, separar strings do codigo e' boa pratica. i18n mal implementado custa 10x mais pra adicionar depois do que fazer certo desde o inicio.

A linha do tempo real de um projeto i18n-ready:

1. Dia 1: estrutura de pastas, Intl desde o inicio, t() everywhere
2. Dia 30: PM pede novo idioma → adiciona en.json, 1 commit
3. Dia 60: PM pede formato japones → ICU ja' cobre plural
4. Dia 90: usuario arabe → RTL ja' funciona, so' traduzir

Vs. o caminho sem i18n (real, dolorido):

1. Dia 1: strings hardcoded em PT-BR
2. Dia 60: PM pede "ingles" → 6h grep+replace, quebra testes
3. Dia 120: "e arabe" → reescrever plural, layout quebra
4. Dia 180: "e chines com data local" → 3 semanas de trabalho

Aprofundamento 🟡

Os 3 problemas que i18n resolve que "trocar texto" nao resolve. Sao esses:

  1. Pluralizacao. Ingles: 1 item / 2 items. Polones: 1 forma / 2-4 / 5-21 / 22-25 / 26+... 5 formas de plural diferentes. Arabe: 6 formas. Ingles so' tem 2 (one / other). Trocar texto nao resolve - precisa de ICU ou Intl.PluralRules.
  2. Formatacao nao-trivial. Datas, numeros, moedas, listas, ordem de sort ("ä" vem depois de "z" em alemao, mas na logica de sorting de Unicode). Intl resolve, mas voce precisa usar Intl em vez de template literals.
  3. Direcao de layout. LTR (ingles, PT) vs RTL (arabe, hebraico). CSS direction: rtl nao basta - precisa de logical properties (margin-inline-start em vez de margin-left), e icones espelhados (seta pra direita → seta pra esquerda).

Locale negotiation - como o servidor escolhe o locale. Quando o browser manda Accept-Language: pt-BR,en-US;q=0.9,en;q=0.8, o servidor faz quality value matching:

User quer: pt-BR (q=1.0), en-US (q=0.9), en (q=0.8)
Server tem: pt-BR, en-GB, es
Match: pt-BR (perfeito), en-GB (q=0.8 do en)
Servido: pt-BR

Quality values (q=0.X) vao de 0 a 1. Default e' 1.0. Quanto menor, menos o user prefere. Server-side i18n libraries (Next.js i18n, Rails i18n) fazem isso automaticamente.

CLDR - Unicode Common Locale Data Repository. Onde vem os dados de "como formatar data em japones" ou "como pluralizar em arabe". O CLDR e' o database usado pelo Intl.* do JS, Java, Python, etc. Atualizado anualmente - alguma coisa muda entre versoes (ex: novo locale, regra de plural nova).

i18n nao e' "traducao". Traducao e' o conteudo de uma l10n. i18n e' o codigo que suporta trocar o conteudo sem mudar o codigo. Confundir os 2 e' comum, mas a distincao importa: voce internacionaliza primeiro (extrai strings, usa Intl), depois localiza (traduz).

Cultura nao se traduz - se adapta. Maozinha de "OK" (👌) e' ofensiva no Brasil (mesmo sentido), e em partes da Europa = "zero" (insulto). Bandeira americana em 1776 e em 2026 sao diferentes. Cores tem significado cultural (branco = luto no Japao, branco = pureza no Brasil). i18n cobre texto; adaptacao cultural e' um trabalho separado (design review por mercado).

Pra quem quer ir mais alem 🔴

CLDR data por tras do Intl. Quando voce faz new Intl.NumberFormat("ar-EG"), o JS consulta o CLDR embutido no runtime (V8/SpiderMonkey). Cada versao do Node/browser traz uma versao diferente do CLDR - se voce precisa de consistencia exata entre runtime diferentes (Node 18 vs Node 22), use full-icu ou polyfills. Em 2026, CLDR 47 e' o atual.

MessageFormat 2 (MF2) - o futuro. ICU MessageFormat 1.x (o que i18next usa) tem limitacoes (sintaxe verbosa, dificil de aninhar). MF2 (2024+) e' a nova versao: sintaxe mais limpa, type-safe, compilador. Em 2026, ainda em rollout - use MF2 so' em projetos novos se quiser; migracao e' trabalhoso.

Intl.Segmenter - avancado. Quebra texto em grafemas, palavras, frases de forma locale-aware. Use pra word count correto em chines (caractere vs palavra) ou truncamento ("Ler mais...") sem cortar no meio de emoji ou caracter composto.

Intl.RelativeTimeFormat - "5 minutos atras". Sabe aquela formatacao que todo app faz errado (5min ago, há 5 min)? RelativeTimeFormat faz certo pra qualquer locale, com pluralizacao automatica.

Intl.DisplayNames - nomes de linguas/paises. DisplayNames.of({type: "language"}).of("pt-BR") → "Brazilian Portuguese" (en) ou "portugues brasileiro" (pt-BR). Use pra mostrar "esse conteudo esta' disponivel em: Ingles, Espanhol, Japones".

Leitura recomendada:

Dica: o erro mais comum em i18n e' "traduzir" e achar que terminou. i18n vai muito alem de texto: formatacao de numeros, pluralizacao (5 formas em polones), direcao de layout (RTL), e adaptacao cultural. Comece com Intl + ICU desde o dia 1 do projeto - custa 5% a mais de effort inicial, mas evita 80% de retrabalho no futuro.

No proximo no, vamos Intl APIs: o namespace Intl.* do JS (nativo, zero-dependency) pra formatar numeros, datas, listas, e fazer sort correto em qualquer locale. Cobre a parte de formatacao locale-aware.

// Quiz

Qual a diferenca fundamental entre i18n e l10n?

Escolha uma alternativa

// recursos

// avaliação da trilha

—
ainda sem avaliações