Um guia para o ResourceBundle
*1. Visão geral *
Muitos desenvolvedores de software, durante suas carreiras profissionais, enfrentam a oportunidade de desenvolver sistemas ou aplicativos multilíngues. Geralmente, eles são destinados a usuários finais de diferentes regiões ou diferentes idiomas.
É sempre um desafio manter e estender esses aplicativos. A capacidade de operar com vários dados específicos da localização ao mesmo tempo é geralmente crucial. A modificação dos dados do aplicativo deve ser o mais simples possível, sem a necessidade de recompilação. É por isso que geralmente evitamos nomes de etiquetas ou botões codificados.
Felizmente, podemos usar o Java que nos fornece essa classe, o que nos ajuda a resolver todos os problemas mencionados acima.
Simplificando, o ResourceBundle permite que nosso aplicativo carregue dados de arquivos distintos que contêm dados específicos de localidade.
====* 1.1 ResourceBundles *
A primeira coisa que devemos saber é que todos os arquivos dentro de* um pacote de recursos devem estar no mesmo pacote/diretório e ter um nome base * comum. Eles podem ter sufixos específicos do código de idioma, indicando idioma, país ou plataforma separados pelo símbolo de sublinhado.
É importante anexar o código do país, se já houver código de idioma, ou plataforma, se houver códigos de idioma e país.
Vejamos exemplos de nomes de arquivos:
-
ExampleResource
-
ExampleResource_en
-
ExampleResource_en_US *ExampleResource_en_US_UNIX
O arquivo padrão para cada pacote de dados é sempre um sem sufixos - ExampleResource. Como existem duas subclasses de ResourceBundle:* PropertyResourceBundle e ListResourceBundle *, podemos intercambiar os dados nos arquivos de propriedades e nos arquivos java.
Cada arquivo deve ter um nome específico do código do idioma e uma extensão de arquivo adequada , por exemplo, ExampleResource_en_US.properties ou Example_en.java.
1.2 Arquivos de propriedades - PropertyResourceBundle
Os arquivos de propriedades são representados por PropertyResourceBundle. Eles armazenam dados na forma de pares de valor-chave com distinção entre maiúsculas e minúsculas.
Vamos analisar um arquivo de propriedades de amostra:
# Buttons
continueButton continue
cancelButton=cancel
! Labels
helloLabel:hello
Como podemos ver, existem três estilos diferentes de definição de pares de valores-chave.
Todos eles são equivalentes, mas o primeiro é provavelmente o mais popular entre os programadores de Java. Vale a pena saber que também podemos colocar comentários nos arquivos de propriedades. Os comentários sempre começam com _ # _ ou _! _.
1.3 Arquivos Java - ListResourceBundle
Primeiro, para armazenar nossos dados específicos do idioma, precisamos criar uma classe que estenda ListResourceBundle e substitua o método _getContents () _. A convenção de nome da classe é a mesma dos arquivos de propriedades.
Para cada _Locale, _ precisamos criar uma classe Java separada.
Aqui está uma classe de amostra:
public class ExampleResource_pl_PL extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{"currency", "polish zloty"},
{"toUsdRate", new BigDecimal("3.401")},
{"cities", new String[] { "Warsaw", "Cracow" }}
};
}
}
Os arquivos Java têm uma grande vantagem sobre os arquivos de propriedades, que é a possibilidade de armazenar qualquer objeto que desejamos - não apenas Strings.
Por outro lado, cada modificação ou introdução de uma nova classe java específica do código do idioma requer a recompilação de um aplicativo, enquanto os arquivos de propriedades podem ser estendidos sem nenhum esforço adicional.
*2. Use pacotes de recursos *
Já sabemos como definir pacotes de recursos, por isso estamos prontos para usá-lo.
Vamos considerar o snippet de código curto:
Locale locale = new Locale("pl", "PL");
ResourceBundle exampleBundle = ResourceBundle.getBundle("package.ExampleResource", locale);
assertEquals(exampleBundle.getString("currency"), "polish zloty");
assertEquals(exampleBundle.getObject("toUsdRate"), new BigDecimal("3.401"));
assertArrayEquals(exampleBundle.getStringArray("cities"), new String[]{"Warsaw", "Cracow"});
Primeiramente, podemos definir nosso Locale, a menos que não desejemos usar o padrão.
Depois disso, vamos chamar um método estático de fábrica de ResourceBundle. Precisamos passar* o nome do pacote configurável com seu pacote/diretório *e o código do idioma como parâmetros.
Há também um método de fábrica que requer apenas um nome de pacote se o código do idioma padrão estiver correto. Assim que tivermos o objeto, podemos recuperar valores por suas chaves.
Além disso, o exemplo mostra que podemos usar _getString (String string) _, _getObject (String string), _ e _getStringArray (String string) _ para obter os valores desejados.
===* 3. Selecionando o recurso de pacote adequado *
*Se queremos usar um recurso de pacote configurável, é importante saber como _Java_ seleciona arquivos de pacote configurável.*
Vamos imaginar que trabalhamos com um aplicativo que precisa de rótulos em polonês, mas seu local padrão JVM é Locale.US.
No início, o aplicativo procurará os arquivos no caminho de classe adequado ao local que você solicitar. Começa com o nome mais específico, ou seja, um que contém uma plataforma, um país e um idioma.
Então, vai para mais geral. Se não houver correspondência, ele retornará ao código de idioma padrão sem verificação de plataforma neste momento.
*Caso não haja correspondência, ele tentará ler o pacote padrão.* Tudo deve ficar claro quando observarmos a ordem dos nomes de arquivos selecionados:
-
Label_pl_PL_UNIX
-
Label_pl_PL
-
Label_pl
-
Label_en_US
-
Label_en *Rótulo
Devemos ter em mente que cada nome representa os arquivos .java e .properties, mas o primeiro tem precedência sobre o último. Quando não há arquivo adequado, uma MissingResourceException é lançada.
===* 4. Herança *
Outra vantagem do conceito de pacote configurável de recursos é a herança de propriedades. Isso significa que os pares de valores-chave incluídos em arquivos menos específicos são herdados por aqueles que são mais altos na árvore de herança.
Vamos supor que temos três arquivos de propriedades:
#resource.properties
cancelButton = cancel
#resource_pl.properties
continueButton = dalej
#resource_pl_PL.properties
backButton = cofnij
O pacote de recursos recuperado para _Locale (“pl”, “PL”) _ retornaria todas as três chaves/valores no resultado. Vale ressaltar,* não há retorno ao pacote de códigos de idioma padrão *, na medida em que a herança de propriedade é considerada.
Além disso, ListResourceBundles e PropertyResourceBundles não estão na mesma hierarquia.
Portanto, se um arquivo de propriedades for encontrado no caminho de classe, os pares de valores-chave serão herdados apenas dos arquivos de propriedades. A mesma regra se aplica aos arquivos Java.
*5. Costumização *
Tudo o que aprendemos acima foi sobre a implementação padrão do ResourceBundle. No entanto, existe uma maneira de modificar seu comportamento.
Fazemos isso estendendo ResourceBoundle.Control e substituindo seus métodos.
Por exemplo, podemos alterar o tempo de manutenção de valores no cache ou determinar a condição em que o cache deve ser recarregado.
Para uma melhor compreensão, vamos preparar um método breve como exemplo:
public class ExampleControl extends ResourceBundle.Control {
@Override
public List<Locale> getCandidateLocales(String s, Locale locale) {
return Arrays.asList(new Locale("pl", "PL"));
}
}
O objetivo deste método é alterar uma maneira de selecionar arquivos no caminho de classe. Como podemos ver, ExampleControl retornará apenas Locale polonês, não importa qual seja o Locale padrão ou definido.
===* 6. UTF-8 *
Como ainda existem muitos aplicativos usando o JDK 8 ou versões mais antigas, vale a pena saber que* antes de Java_9 *_ListResourceBundles tinha mais uma vantagem sobre PropertyResourceBundles. Como os arquivos Java podem armazenar objetos String, eles podem armazenar qualquer caractere suportado pela codificação UTF-16.
Pelo contrário, PropertyResourceBundle carrega arquivos por padrão usando a codificação ISO 8859-1, que possui menos caracteres que UTF-8 (causando problemas nos nossos exemplos de idioma polonês).
Para salvar caracteres que estão além do UTF-8, podemos usar o conversor Native-To-ASCII - native2ascii. Ele converte todos os caracteres que não são compatíveis com a ISO 8859-1, codificando-os para a notação _ \ uxxxx_.
Aqui está um exemplo de comando:
native2ascii -encoding UTF-8 utf8.properties nonUtf8.properties
E vamos ver como as propriedades se parecem antes e depois de uma alteração na codificação:
#Before
polishHello=cześć
#After
polishHello=cze\u015b\u0107
Felizmente, esse inconveniente não existe mais no Java 9. JVM lê arquivos de propriedades na codificação UTF-8, e não há problema em usar caracteres não latinos.
===* 7. Conclusão*
BundleResource contém muito do que precisamos para desenvolver um aplicativo multilíngue. Os recursos que abordamos tornam bastante simples a manipulação de diferentes localidades.
Também evitamos valores codificados, o que nos permite expandir os Locales suportados simplesmente adicionando novos arquivos Locale, permitindo que nosso aplicativo seja modificado e mantido sem problemas.
Como sempre, o código de exemplo está disponível em over no GitHub.