Internacionalização no código Java
Já falamos neste blog sobre i18n, o Fabio postou um artigo como internacionalizar as suas aplicações web usando JSTL.
fmt dá todo suporte, mas fora do ambiente web precisamos procurar outra solução. Por exemplo, numa aplicação swing, ou mesmo web tem casos que precisamos mostrar uma mensagem já internacionalizada. Temos que saber com i18n / L10n também programaticamente fora do contêiner web. Isto é útil no dia-a-dia e também faz parte da certificação SCJP 😉
java.util.Locale
Esta classe influencia fortemente o trabalho de outras classes referente à formatação e internacionalização. Com ela será definida uma região, por exemplo:
Locale ptBr = new Locale("pt", "BR"); //Locale para o Brasil
Definimos um Locale com o idioma português (iso-code pt) e o país Brasil (iso-code BR). Vocês poderiam reclamar que está óbvio que o Brasil fala português, mas isso não aplica para todos os países, Veja o exemplo:
Locale enCA = new Locale("en","CA"); Locale frCA = new Locale("fr","CA");
Definimos o mesmo pais Canada mas com dois idiomas diferentes (uma região para francês outra para inglês).
Também podemos pegar o Locale da maquina virtual:
Locale vmLocale = Locale.getDefault(); //pergunta de certificação
java.text.DateFormat e java.text.NumberFormat
A formatação das duas classes é baseada em um Locale. Elas servem para formatar Data ou Números/Moeda respectivamente. Para receber um instância das classes é preciso usar um dos métodos getInstance() delas:
DateFormat dateFormat = DateFormat.getInstance(); NumberFormat numberFormat = NumberFormat.getInstance();
Mas aonde está o Locale? Nesse caso DateFormat/NumberFormat são baseado no Locale padrão, ou seja o da máquina virtual. Você pode passar o Locale no método, por exemplo:
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL, ptBR); NumberFormat numberFormat = NumberFormat.getNumberInstance(ptBR);
Agora você recebe uma instância para formatar datas ou numeros referente do Brasil. Existem outros métodos para receber uma instância, aqui algumas variações:
Locale ptBR = new Locale("pt", "BR"); DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL, ptBR); System.out.println(dateFormat.format(new Date())); DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM, ptBR); System.out.println(timeFormat.format(new Date())); NumberFormat numberFormat = NumberFormat.getNumberInstance(ptBR); //para números System.out.println(numberFormat.format(13.23)); NumberFormat moedaFormat = NumberFormat.getCurrencyInstance(ptBR); //para moedas System.out.println(moedaFormat.format(13.23));
Dependo do dia, o resultado seria:
Sexta-feira, 21 de Setembro de 2007
00:51:05
13,23
R$ 13,23
As duas classes, junto com o Locale dão muito poder e não servem somente para formatação, mas também para fazer parsing de uma String para um Date/Number, veja só:
System.out.println(dateFormat.parse( "Sexta-feira, 21 de Setembro de 2007")); System.out.println(timeFormat.parse("00:58:16")); System.out.println(numberFormat.parse("13,23")); System.out.println(moedaFormat.parse("R$ 13,23"));
Imprime (Dependo da data):
Fri Sep 21 00:00:00 BRT 2007
Thu Jan 01 00:58:16 BRT 1970
13.23
13.23
ResourceBundle
A classe ResourceBundle é aquela que acessa seu arquivo de internacionalização. Supondo que temos dois arquivos de propriedades no classpath, um para as mensagens em português, outro para as em alemão e um padrão, com o conteúdo seguinte:
messages_pt_BR.properties
welcome=Bem vindo no Brasil
messages_de_DE.properties
welcome=Hallo in Deutschland
messages.properties
welcome=Welcome in default
logout=Leaving default
A nomenclatura segue o padrão, messages_’iso-code-language’_’iso-code-country’. Parece familiar? Para carregar o arquivo “messages” baseada na Locale pt-BR basta usar:
Locale ptBR = new Locale("pt","BR"); ResourceBundle bundle = ResourceBundle.getBundle("messages", ptBR); System.out.println(bundle.getString("welcome"));
O resultado será:
Bem vindo no Brasil
Para carregar a mensagem em alemão, só precisamos mudar o Locale. Mas se eu chamo uma mensagem com a chave “logout”? Por exemplo:
System.out.println(bundle.getString("logout"));
O bundle vai procurar de maneira seguinte:
messages_pt_BR.properties //não acha
messages_pt.properties //não acha
messages.properties
e finalmente acha o valor da chave ‘logout’ no arquivo messages.properties e imprime na tela:
Leaving default
MessageFormat
Falta mencionar a classe que ajuda substituir parâmetros na mensagem.
String mensagemParametrizada = "Isto foi um post sobre {0} e {1}."; String mensagem = MessageFormat.format(mensagemParametrizada, "i18n", "formatação"); System.out.println(mensagem);
Imprime:
Isto foi um post sobre i18n e formatação.
Programação, Mobile, Front-end, Design & UX, Infraestrutura e Business
Muito bom, porém sempre tive uma dúvida em relação sobre como ficaria a arquitetura de um DB de um sistema multilingüe e como seria a comunicação de um framework de persistência, como o Hibernate, como o banco.
Legal o texto,mas é bom lembrar aos incautos que o locale para o Brasil não serve para tudo.Se alguem lida com cálculos de IPTU e algumas transações bancárias específicas, que lidam com 4 casas decimais, melhor fazer na mão mesmo, pois o number instance só vai até a 3ªcasa, e o Currency, até a segunda.
David, qual é a sua dúvida referente do banco e internacionalização?
Ironlynx, vlw pela dica!
Nico, erro meu:
É o que dá falar as coisas sem ler a API(Tava sem acesso á ela aqui).É só usar o .setMinimumFractionDigits(4); que funciona ok, SEM perdas(e eu reescrevendo a roda com BDecimal).Ou seja:
Locale ptBR = new Locale(“pt”, “BR”);
NumberFormat numberFormat =
NumberFormat.getNumberInstance(ptBR); //para números
numberFormat.setMinimumFractionDigits(4);
System.out.println(numberFormat.format(13.231212));
NumberFormat moedaFormat =
NumberFormat.getCurrencyInstance(ptBR); //para moedas
moedaFormat.setMinimumFractionDigits(4);
System.out.println(moedaFormat.format(1312.231212));
No códifo acima, será impresso:
13,2312
R$ 1.312,2312
Um []´ção no Paulo.
Algo que eu esqueci de comentar, foi que apartir do java5, pode se usar o método setParseBigDecimal(boolean), para converter um DecimalFormat para BigDecimal.É muito tranquilo e não trunca!(Muito útil quando vc lida com TextFields personalizados, mas tem que fazer operações precisas com BigDecimal)
Exemplo:
public BigDecimal convertToBigDecimal(String value){
Locale.setDefault(new Locale(“pt”,”BR”));
DecimalFormat df = new DecimalFormat(“#,##0.00”);
try{
df.setParseBigDecimal(true);
BigDecimal bd =(BigDecimal)df.parse(value);
return bd;
}catch(ParseException pe){
pe.printStackTrace();
}
return BigDecimal.ZERO;
}
adoraria saber como faço para internacionalizar exceções?
Gostaria de saber se você poderia disponibilizar o projeto.
Muito bom. [=
Interessante.
Aliei esse conceito de um dicionário de traduções ( não usando bundles, mas XML’s com mensagens) com AOP, e consegui montar uma estrutura simples para i18n.
@I18N
private String mensagem;
private Label nomeLabel;
Strings são traduzidas com a presença da anotação, e componentes visuais são traduzidos com a anotação opcional.
Olá.
Como faço para ter a opção de login somente em uma Tela, por exemplo ao iniciar o programa, na tela de Login eu coloco um combo box pra pessoa escolher o idioma. Ao escolher, todas as telas do projeto deverão ser traduzidas de uma vez.