quarta-feira, 30 de abril de 2008

Internacionalização com solução "caseira"

Ola pessoal,

Depois de um "longo e tenebroso inverno", consegui um tempinho para voltar a escrever algumas coisas que acho interessante sobre o desenvolvimento de aplicativos para dispositivos móveis, e principalmente utilizando Java ME! :)
E já que o espirito desse blog é passar para vocês algumas das minhas experiências e também soluções que encontro para algumas situações, vou mostrar hoje como resolvi um "problema" de internacionalização para aplicativos em Java ME.

** ATTENTION: This is a portuguese version of http://weblogs.java.net/blog/netomarin **

O problema:
Queria fazer um aplicativo que chegasse no maior número de usuários, mas antes da aplicação em si eu tinha uma barreira: Idioma!
Não gostaria de ter uma versão diferente para cada idioma que eu fosse  distribuir minha aplicação. Então, minha primeira idéia foi utilizar a JSR 238 (Mobile Internationalization API), porém ao consultar a matriz de dispositivos que implementam essa API, adivinhem só ? Poucos aparelhos e somente da Sony Ericsson.
Nesse caso, a saída então era utilizar algum "walk around" que permitisse o usuário ter a opção de escolher entre diversos idiomas, dentro de uma única distribuição do aplicativo, o que com certeza, iria facilitar a minha vida.

Solução "caseira":
Sei que a JSR 238 trata a internacionalização como um todo, e não somente a questão do idioma, mas como no meu caso o problema era SOMENTE o idioma da aplicação, minha solução caseira é com foco no suporte a diversos idiomas.
Então, criei uma classe LanguageManager que possui dois métodos estáticos para o uso dos idiomas, a interface MessagesKey que contém todas as chaves para as mensagens utilizadas no aplicativo e, claro, arquivos properties para cada idioma que se deseja disponibilizar.

Arquivos de properties:
Esse arquivo não tem nenhum mistério. Trata-se do clássico formato "CHAVE=VALOR", de forma que cada par deve estar em uma linha própria. Para facilitar a leitura desse arquivo, ele pode ter comentários em linhas iniciadas com "#".
Além da regra de "composição" do conteúdo do arquivo, o nome do arquivo também deve seguir uma regra: Ele deve estar em um diretório separado chamado "properties" e seu nome deve ser composto do código do idioma com a extensão ".properties", como por exemplo "pt-br.properties". Abaixo, exemplo de um trecho de um propertie que utilizo:

pt-br.properties
#COMMANDS
CMD_CONTINUE=Continuar
CMD_EXIT=Sair
CMD_SEARCH=Pesquisar

A interface MessagesKey
Para que o seja possível encontrar a mensagem desejada, essa classe deve conter a chave desejada, pois é através de uma referência para uma constante dessa classe que será buscada a mensagem desejada. Cada chave existente no arquivo de properties deve ter o seu correspondente nessa classe. Abaixo, exemplo da classe contendo as chaves indicadas no trecho acima do properties:

public interface MessagesKey {
    /**
     * Command Label
     */
    public static final String CMD_CONTINUE = "CMD_CONTINUE";
    public static final String CMD_EXIT = "CMD_EXIT";
    public static final String CMD_SEARCH = "CMD_SEARCH";
}

A classe LanguageManager
Essa classe é a responsável pela carga do idioma desejado e também é a classe que deverá ser utilizada pelo desenvolvedor para obter o texto desejado de acordo com a chave informada. Para fazer esse "trabalho", ela tem dois métodos:
  • public static boolean loadLanguage(String language) throws LanguageNotFoundException
Esse método é o responsável for fazer a "carga" do idioma desejado. O parâmetro que ele recebe é o prefixo do idioma (no caso do português do Brasil, seria pt-br), que é utilizado para carregar o propertie correspondente (pt-br.properties, no nosso exemplo). Essa carga é feita percorrendo cada linha do arquivo texto e então atribuindo o par chave/valor a um Hashtable stático (privado) da classe.
Atenção: Esse método deve ser executado ao menos uma vez antes de você tentar usar as mensagens internacionalizadas, pois ele é o responsável por carregar o Hashtable com as mensagens, senão, você com certeza terá uma exceptions de NullPointer !!! =)
  • public static String getMessageText (String messageKey)
Para acessar o Hashtable que possui as mensagens que devem ser exibidas, o acesso é feito por esse método que irá simplesmente devolver a String que tiver associada a chave fornecida no parâmetro. Para evitar erros de execução e simplesmente facilitar o tratamento, esse método não possui nenhum excessão, apenas retorna null caso não haja valor nenhum para a chave indicada.

Exemplo de como usar:

Abaixo, vai exemplo de como utilizar a solução indicada acima:

  • Carregando o idioma: Esse passo deve ser feito de preferência quando você carrega a aplicação pela primeira vez. Para facilitar meu trabalho, eu criei um parâmetro no meu JAD que indica o idioma padrão, e lá coloquei pt-br. Exemplo:
LanguageManager.loadLanguage(this.getAppProperty("DefaultLanguage"));

ou usando o idioma "explicitamente":

LanguageManager.loadLanguage("pt-br");

  • Criando um novo Command, onde o label dele é "internacionalizado":
Command continueCmd = new Command(LanguageManager.getMessageText(MessagesKey.CMD_CONTINUE), Command.OK, 1);

Bom pessoal, espero que tenha ficado fácil e que isso possa ajudar a alguém!! :)
Código fonte exemplo do que utilizo na minha aplicação (apenas o pacote internationalization): internationalization.zip

Abraços a todos!
Neto Marin