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.

Tags: ,

13 Comentários

  1. David 03/10/2007 at 15:05 #

    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.

  2. Ironlynx 05/10/2007 at 23:38 #

    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.

  3. Nico Steppat 09/10/2007 at 13:37 #

    David, qual é a sua dúvida referente do banco e internacionalização?

    Ironlynx, vlw pela dica!

  4. Ironlynx 16/10/2007 at 13:16 #

    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.

  5. Ironlynx 29/10/2007 at 14:23 #

    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;
    }

  6. Tonelli 08/02/2008 at 12:32 #

    adoraria saber como faço para internacionalizar exceções?

  7. Marco 10/06/2008 at 17:42 #

    Gostaria de saber se você poderia disponibilizar o projeto.

  8. Hebert 11/10/2012 at 08:57 #

    Muito bom. [=

  9. Eduardo Frazão 25/07/2013 at 15:38 #

    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.

  10. Samara 17/09/2015 at 14:41 #

    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.

Deixe uma resposta