Como implementar localização e internacionalização em APIs REST
1. Fundamentos de i18n e l10n em APIs REST
A internacionalização (i18n) e a localização (l10n) são conceitos fundamentais para construir APIs que atendam usuários globais. A internacionalização refere-se ao processo de projetar o sistema para suportar múltiplos idiomas e regiões sem exigir alterações no código-fonte. Já a localização é a adaptação concreta do conteúdo para um locale específico, incluindo traduções, formatos de data, moeda e regras culturais.
Em APIs REST, os desafios são particulares. Diferentemente de aplicações web tradicionais, onde o idioma pode ser detectado pelo navegador, as APIs precisam lidar com headers HTTP, parâmetros de query e até subdomínios para determinar o locale. A escolha mais comum e alinhada com os padrões REST é utilizar o header Accept-Language, que segue a especificação HTTP e permite que clientes indiquem suas preferências linguísticas com quality values.
// Exemplo de header Accept-Language
Accept-Language: pt-BR, pt;q=0.9, en;q=0.8, es;q=0.5
Alternativas incluem passar o locale como parâmetro de query (?lang=pt-BR) ou usar subdomínios (pt.api.exemplo.com). A abordagem via header é recomendada por ser semântica e não poluir a URL, mas cada caso deve ser avaliado conforme o ecossistema de clientes.
2. Estruturação de mensagens e recursos traduzíveis
A organização dos arquivos de tradução é o alicerce de uma implementação robusta. Formatos como JSON, YAML ou PO (usado em sistemas GNU gettext) são populares. Recomenda-se estruturar as mensagens por domínio e chave, separando arquivos por idioma.
// Estrutura de diretórios
locales/
en/
common.json
errors.json
validation.json
pt-BR/
common.json
errors.json
validation.json
es/
common.json
errors.json
validation.json
O suporte a pluralização é crítico. Idiomas como o inglês têm duas formas (singular/plural), enquanto o russo ou árabe possuem regras complexas. Uma boa prática é usar chaves com sufixos ou bibliotecas que implementem a CLDR (Common Locale Data Repository).
// Exemplo de arquivo de tradução com pluralização (en/common.json)
{
"items_count": {
"one": "1 item",
"other": "{count} items"
}
}
O gerenciamento de fallback é essencial. Defina um idioma padrão (geralmente inglês) e uma cadeia de fallbacks: se pt-BR não existir, tente pt; se pt não existir, use en. Isso garante que a API nunca retorne chaves não traduzidas.
3. Implementação do middleware de localização
O middleware de localização é responsável por interceptar cada requisição, extrair o locale preferido e disponibilizá-lo para os endpoints. Em Node.js com Express, por exemplo, podemos implementar:
// Middleware de localização
const supportedLocales = ['pt-BR', 'pt', 'en', 'es'];
const defaultLocale = 'en';
function localeMiddleware(req, res, next) {
const acceptLanguage = req.headers['accept-language'];
if (acceptLanguage) {
const parsed = parseAcceptLanguage(acceptLanguage, supportedLocales);
req.locale = parsed || defaultLocale;
} else {
req.locale = defaultLocale;
}
// Define o cabeçalho de resposta
res.setHeader('Content-Language', req.locale);
next();
}
O parsing de quality values deve respeitar a especificação HTTP, onde valores mais altos indicam maior preferência. Bibliotecas como accept-language-parser facilitam esse processo. A validação de códigos de idioma deve seguir ISO 639-1 (duas letras) ou BCP 47 (com variações regionais).
4. Tradução de respostas de erro e validação
Mensagens de erro padronizadas com suporte a múltiplos idiomas são cruciais para a experiência do desenvolvedor. Cada mensagem de erro deve ser referenciada por uma chave única, e o sistema de tradução deve ser invocado antes de retornar a resposta.
// Exemplo de resposta de erro internacionalizada
HTTP/1.1 422 Unprocessable Entity
Content-Language: pt-BR
Content-Type: application/json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "O campo 'email' é obrigatório",
"details": [
{
"field": "email",
"rule": "required",
"message": "O email deve ser fornecido"
}
]
}
}
A tradução de campos de validação exige cuidado com nomes de atributos. Em vez de hardcodar "email", use uma chave de tradução como field.email que pode ser localizada. O cabeçalho Content-Language na resposta informa ao cliente qual idioma foi utilizado, permitindo que ele ajuste sua interface se necessário.
5. Localização de dados dinâmicos e conteúdos do banco
Dados que variam conforme o locale (como descrições de produtos ou nomes de categorias) exigem estratégias específicas em bancos relacionais. Duas abordagens comuns são:
Tabelas separadas: Uma tabela principal com dados invariantes e uma tabela de traduções com chave estrangeira e coluna de idioma.
-- Modelo de tabelas separadas
CREATE TABLE products (
id SERIAL PRIMARY KEY,
sku VARCHAR(50) NOT NULL,
price DECIMAL(10,2) NOT NULL
);
CREATE TABLE product_translations (
product_id INTEGER REFERENCES products(id),
locale VARCHAR(10) NOT NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
PRIMARY KEY (product_id, locale)
);
Coluna JSON: Uma coluna JSONB que armazena todas as traduções em um único campo.
-- Modelo com coluna JSON
CREATE TABLE products (
id SERIAL PRIMARY KEY,
sku VARCHAR(50) NOT NULL,
price DECIMAL(10,2) NOT NULL,
translations JSONB NOT NULL
);
-- Exemplo de conteúdo da coluna translations
{
"en": {"name": "Wireless Mouse", "description": "Ergonomic design"},
"pt-BR": {"name": "Mouse sem fio", "description": "Design ergonômico"},
"es": {"name": "Ratón inalámbrico", "description": "Diseño ergonómico"}
}
Para performance, implemente cache de traduções em memória (Redis ou similar) e evite consultas repetitivas ao banco. Estratégias de fallback devem buscar o idioma mais específico primeiro e depois cair para o padrão.
6. Versionamento e testes de internacionalização
Arquivos de tradução devem ser versionados junto com o código, e mudanças devem seguir o mesmo fluxo de revisão. Ferramentas como Lokalise, Crowdin ou POEditor ajudam a gerenciar traduções com equipes distribuídas.
Testes automatizados são indispensáveis:
// Teste de cobertura de idiomas
describe('Localization Tests', () => {
it('should have all keys in all supported locales', () => {
const supportedLocales = ['en', 'pt-BR', 'es'];
const referenceKeys = getKeysForLocale('en');
supportedLocales.forEach(locale => {
const localeKeys = getKeysForLocale(locale);
const missingKeys = referenceKeys.filter(k => !localeKeys.includes(k));
expect(missingKeys).toHaveLength(0);
});
});
it('should respond in Portuguese when Accept-Language is pt-BR', async () => {
const response = await request(app)
.get('/api/products/1')
.set('Accept-Language', 'pt-BR');
expect(response.headers['content-language']).toBe('pt-BR');
expect(response.body.name).toBe('Mouse sem fio');
});
});
Simule diferentes headers de idioma em testes de integração para garantir que o middleware funciona corretamente e que as traduções são aplicadas em todos os endpoints.
7. Boas práticas e considerações de performance
O carregamento de traduções pode impactar a performance. Em vez de lazy loading (carregar sob demanda), que adiciona latência, considere carregar todas as traduções no startup da aplicação, especialmente se o número de idiomas for limitado (até 20). Para muitos idiomas, use um cache com TTL e carregamento sob demanda com fallback.
A Content Negotiation deve ir além do idioma. Para formatos de data, moeda e número, utilize bibliotecas como Intl (nativa do JavaScript) ou moment.js com locale configurado.
// Exemplo de formatação de data com locale
const dateFormatter = new Intl.DateTimeFormat('pt-BR', {
dateStyle: 'long',
timeStyle: 'short'
});
console.log(dateFormatter.format(new Date())); // "15 de janeiro de 2025 14:30"
Monitore métricas importantes: taxa de fallback (indica lacunas nas traduções), tempo médio de tradução por requisição e distribuição de idiomas usados. Ferramentas como Prometheus e Grafana podem capturar essas métricas via middleware.
Por fim, documente claramente como os clientes devem especificar o idioma e quais locales são suportados. Uma documentação clara reduz erros de integração e melhora a experiência dos desenvolvedores que consomem sua API.
Referências
- MDN Web Docs: Accept-Language — Documentação oficial do header Accept-Language, incluindo sintaxe e exemplos de quality values.
- BCP 47: Tags for Identifying Languages — Especificação oficial para identificação de idiomas, base para validação de códigos de locale.
- Express.js: Internationalization (i18n) Guide — Guia oficial do Express sobre práticas de internacionalização e performance.
- GNU gettext Manual — Documentação completa do sistema gettext, referência para formatos PO e pluralização.
- CLDR - Common Locale Data Repository — Repositório da Unicode para dados de locale, incluindo regras de pluralização e formatação.
- Lokalise: Best Practices for API Localization — Artigo prático sobre estratégias de localização em APIs REST com exemplos reais.
- Crowdin: Internationalization Testing Guide — Guia para testes automatizados de internacionalização em APIs.