Guia prático de i18n em Java com ResourceBundle e bundles .properties.
Internacionalização (i18n) em Java com ResourceBundle
Cada vez mais empresas exigem software que fale português na fábrica, inglês na matriz e espanhol na filial — às vezes no mesmo executável. Como desenvolvedor Java desde os tempos de Swing desktop e fundador da TEK Softwares, tratei internacionalização (i18n) não como feature de marketing, mas como redução de custo de manutenção: corrigir texto sem recompilar lógica, separar compliance de copy legal por país, acelerar homologação. Neste artigo, atualizo o guia prático com ResourceBundle, locale e armadilhas que vi em ERP fiscal.
Conceito: separar código de texto
Hardcode de string em botão, mensagem de erro ou coluna JTable parece rápido na sprint zero. Seis meses depois, traduzir vira caça ao "Salvar" espalhado em duzentos arquivos. i18n move textos para arquivos .properties (ou JSON/XML conforme stack), indexados por chave estável. Código referencia chave; tradutor edita valor por locale.
ResourceBundle na prática
Instancie java.util.ResourceBundle apontando base name do pacote:
ResourceBundle labels = ResourceBundle.getBundle(
"view.Bundle",
Locale.forLanguageTag("pt-BR")
);
frame.setTitle(labels.getString("app.title"));
Arquivo view/Bundle_pt_BR.properties:
app.title=Emissão de Notas
btn.save=Salvar
error.cfop.invalid=CFOP inválido para operação
Arquivo view/Bundle_en_US.properties espelha chaves com tradução. Chave faltante lança exceção em runtime — teste todos locales no CI.
Locale e fallback
Locale.getDefault() segue SO do usuário — útil desktop, perigoso se servidor assume locale do JVM. Em web, derive locale de sessão, header Accept-Language ou preferência explícita. Fallback chain: pt_BR → pt → en. Documente no README.
Formatação além de tradução
Número, moeda e data exigem NumberFormat e DateTimeFormatter com locale — traduzir "Total" não basta se decimal usa ponto errado. ERP fiscal brasileiro mistura regra Sefaz (fixa) com rótulo traduzível (variável).
Exemplo visual de formulário alternando idioma — imagens originais do blog:
NetBeans e build
IDEs com suporte i18n geram bundles automaticamente em formulários. Após adicionar chaves, Clean and Build evita cache de resource antigo — erro clássico "chave existe mas UI mostra ???". Em Maven/Gradle, resources ficam em src/main/resources; confirme encoding UTF-8 no pom.
i18n em SQL e relatórios
Algumas empresas externalizam fragmentos SQL ou nomes de view por locale — útil quando mesmo relatório Jasper muda coluna legal. Cuidado: SQL i18n complica auditoria. Prefiro relatório único com parâmetro locale que escolhe template .jrxml correto.
Testes automatizados
- Teste unitário carrega cada bundle e asserta chaves obrigatórias presentes.
- Snapshot de tela (TestFX, AssertJ Swing) por locale em componentes críticos.
- Integração: API REST retorna mensagem traduzida conforme header.
Microsserviços e front-end moderno
Backend Java expõe código de erro; front React/Vue mapeia para arquivo i18n JSON. Evite traduzir no servidor HTML legado se SPA já possui pipeline — fonte única de verdade. Artigo relacionado: microsserviços.
Acessibilidade e i18n
Texto alemão expande botões; árabe exige RTL. Layout flexível desde o início poupa refatoração. Teclas de atalho conflitam entre idiomas — documente.
Segurança
Bundle não é lugar para segredo. Mensagens de erro não devem vazar stack interno — tradução genérica para usuário, detalhe em log. Alinhado a segurança web 2026.
Manutenção operacional
Tradução via agência? Exporte CSV de chaves, importe revisado, diff no Git. Product owner valida pt_BR; parceiro valida en_US. Release note lista chaves novas para equipe de tradução.
Quando NÃO internacionalizar cedo
MVP só Brasil, prazo apertado, uma tela admin interna — adie com dívida consciente registrada em DECISIONS.md. Mas se contrato multinacional já assinado, pague i18n no sprint 1; retrofit custa 3–5×.
Pluralização e género
Português pluraliza diferente de inglês; MessageFormat com placeholders {0} ajuda: msg.format(new Object[]{count}). Evite concatenar string + número manualmente — quebra em polonês e árabe.
Properties UTF-8 nativo
JDK 9+ Properties.load Reader UTF-8. Não converta mais para \u escapes manuais exceto legado. Maven resource filtering cuidado — não filtre ${} acidentalmente em texto tradução.
CI multi-locale
Job matrix locale pt_BR en_US es_ES roda testes bundle. Falha cedo se chave nova esquecida tradução espanhol contrato assinado.
JavaFX e Swing legado
FXML loader resource bundle por locale file suffix. Troca idioma runtime reload stage strings bound Observable — pattern mais limpo que reiniciar app. Teste fonte CJK se mercado Ásia futuro.
Backend Spring MessageSource
messages_pt_BR.properties beans MessageSource basename. ControllerAdvice retorna ErrorResponse code + message resolvida locale Accept-Language. Front só exibe — separação clara.
RTL e layout fiscal
Nota fiscal Brasil LTR fixo; exportação UI árabe outro produto. Não force um layout universal sem QA nativo.
Contrato tradução com fornecedor
Envio planilha chave contexto screenshot max 500 chars por chave. Proíbo tradução literal "Save" como "Salvar" em botão destrutivo — glossário aprovado: Excluir Delete Remove. Revisão nativo pt_BR antes merge release. Multa contratual prazo vazio inaceitável release bloqueado.
Localização contínua pós-release
Tradução não termina no ship: cada sprint novas strings entram bundle. Gate CI falha se chave pt_BR existe en_US vazio. Product owner recebe diff properties semanal quinze minutos review copy evita vergonha homofono ingles portugues.
Conclusão
Internacionalização em Java com ResourceBundle continua sólida, previsível e compatível com legado Swing/JavaFX que ainda roda em clientes TEK. Separe chave e valor, teste locales, formate números/datas corretamente e integre i18n ao pipeline de build. Software maleável vende mais e quebra menos na filial argentina — sem reescrever regra fiscal, tema para CFOP e escrita fiscal.