Internacionalização (i18n) e localização (l10n) em apps

1. Fundamentos de i18n e l10n: terminologia e escopo

A internacionalização (i18n) e a localização (l10n) são disciplinas complementares no desenvolvimento de aplicações globais. i18n refere-se ao processo de arquitetar o software para suportar múltiplos idiomas e regiões sem alterações no código-fonte. l10n é a adaptação cultural e linguística propriamente dita, incluindo tradução de textos, formatação de dados e adequação a normas locais. A tradução (t9n) é apenas um subconjunto da localização.

O ciclo de vida da localização compreende cinco etapas: planejamento (definição de locales-alvo), extração (coleta de strings do código), tradução (realizada por tradutores humanos ou automatizada), integração (incorporação ao build) e manutenção (atualização contínua). Um mito comum é acreditar que localizar resume-se a traduzir textos — na verdade, envolve adaptar formatos de data, moeda, pluralização, símbolos, cores e até imagens.

2. Arquitetura de strings e arquivos de tradução

Arquivos de tradução podem usar formatos como JSON, YAML, PO/MO ou ICU MessageFormat. A organização por namespace (ex: common, errors, checkout) facilita o carregamento sob demanda e evita arquivos monolíticos. É recomendável usar chaves semânticas ao invés de IDs numéricos:

// Exemplo de arquivo JSON com chaves semânticas
{
  "checkout.title": "Finalizar compra",
  "checkout.subtotal": "Subtotal",
  "checkout.shipping": "Frete",
  "errors.required": "Campo obrigatório",
  "errors.invalidEmail": "E-mail inválido"
}

Para suporte a contexto e gênero, a estrutura deve prever variantes:

{
  "greeting.male": "Bem-vindo, {name}",
  "greeting.female": "Bem-vinda, {name}",
  "greeting.neutral": "Bem-vinde, {name}"
}

3. Pluralização, gênero e regras linguísticas complexas

O ICU MessageFormat é o padrão mais robusto para pluralização. Cada locale define suas próprias regras (CLDR):

// Português: regras one/other
"items": "{count, plural, one {# item} other {# itens}}"

// Russo: regras one/few/many/other
"items_ru": "{count, plural, one {# товар} few {# товара} many {# товаров} other {# товара}}"

// Árabe: regras zero/one/two/few/many/other
"items_ar": "{count, plural, zero {# عنصر} one {# عنصر} two {# عنصران} few {# عناصر} many {# عنصرًا} other {# عنصر}}"

Para gênero em francês:

"member": "{gender, select, male {membre} female {membre} other {membre}}"

4. Detecção de idioma e fallback estratégico

A negociação de locale pode ser feita via cabeçalho Accept-Language, geolocalização (API do navegador) ou preferências salvas. A estratégia de fallback deve criar uma cadeia: pt-BRpten:

// Exemplo de lógica de fallback
function resolveLocale(preferred, supported) {
  const fallbackChain = [preferred, preferred.split('-')[0], 'en'];
  for (const locale of fallbackChain) {
    if (supported.includes(locale)) return locale;
  }
  return 'en';
}

A persistência da escolha deve usar cookie (com consentimento LGPD/GDPR), localStorage ou perfil do usuário no backend. Nunca confie apenas em geolocalização, pois usuários podem estar viajando ou usar VPN.

5. Formatação de dados localizados (datas, números, moedas)

As APIs nativas Intl oferecem formatação consistente sem dependências externas:

// Formatação de data
const dateFormatter = new Intl.DateTimeFormat('pt-BR', {
  dateStyle: 'long',
  timeStyle: 'short'
});
console.log(dateFormatter.format(new Date())); // "15 de janeiro de 2025 14:30"

// Formatação de moeda
const currencyFormatter = new Intl.NumberFormat('ja-JP', {
  style: 'currency',
  currency: 'JPY'
});
console.log(currencyFormatter.format(1234)); // "¥1,234"

// Formatação de número com pluralização relativa
const relativeFormatter = new Intl.RelativeTimeFormat('pt-BR', { numeric: 'auto' });
console.log(relativeFormatter.format(-3, 'day')); // "há 3 dias"

Para fusos horários, use o parâmetro timeZone:

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Tokyo',
  dateStyle: 'full',
  timeStyle: 'full'
});

6. Internacionalização no backend (Node.js/Express)

Um middleware de locale deve extrair o idioma do header, validar contra a lista de suporte e injetar no objeto req:

const supportedLocales = ['pt-BR', 'en', 'es'];
const i18nMiddleware = (req, res, next) => {
  const header = req.headers['accept-language'] || 'en';
  const preferred = header.split(',')[0].trim();
  req.locale = supportedLocales.includes(preferred) ? preferred : 'en';
  next();
};

Usando i18next com backend HTTP:

const i18next = require('i18next');
const Backend = require('i18next-http-backend');

i18next.use(Backend).init({
  fallbackLng: 'en',
  backend: {
    loadPath: '/locales/{{lng}}/{{ns}}.json'
  }
});

app.use((req, res, next) => {
  req.t = i18next.getFixedT(req.locale);
  next();
});

Cuidados: cache de traduções em produção, carregamento lazy de namespaces e SSR com locale correto para SEO.

7. Internacionalização no frontend (React, Vue, Angular)

Em React com react-i18next:

import { useTranslation, Trans } from 'react-i18next';

function Welcome() {
  const { t } = useTranslation('common');
  return (
    <div>
      <h1>{t('welcome.title')}</h1>
      <Trans i18nKey="welcome.description">
        Você tem <strong>{{ count }} notificações</strong> não lidas.
      </Trans>
    </div>
  );
}

Carregamento assíncrono de namespaces:

const { t, ready } = useTranslation(['checkout', 'errors'], { useSuspense: true });

Para roteamento amigável (Next.js, Nuxt, Angular Universal):

// URLs: /pt/produto, /en/product
// Meta tags hreflang
<link rel="alternate" hreflang="pt-BR" href="https://exemplo.com/pt/produto" />
<link rel="alternate" hreflang="en" href="https://exemplo.com/en/product" />

8. Testes, manutenção e ferramentas de automação

Testes unitários devem verificar chaves ausentes, placeholders quebrados e pluralização incorreta:

// Teste com Jest
test('todas as chaves em pt-BR existem em en', () => {
  const pt = require('./locales/pt-BR/common.json');
  const en = require('./locales/en/common.json');
  Object.keys(pt).forEach(key => {
    expect(en).toHaveProperty(key);
  });
});

test('placeholders não quebrados', () => {
  const en = require('./locales/en/common.json');
  Object.values(en).forEach(value => {
    const placeholders = value.match(/\{\{(\w+)\}\}/g);
    // Verificar se placeholders são consistentes
  });
});

Ferramentas de gerenciamento: Crowdin (colaboração em equipe), Lokalise (API robusta), POEditor (simplicidade), Weblate (open source). Na CI/CD, adicione etapas de lint de arquivos, validação de formato ICU e sincronização automática com repositórios Git.


Referências