Ръководство за ResourceBundle

1. Общ преглед

По време на професионалната си кариера много разработчици на софтуер се сблъскват с възможност да разработват многоезични системи или приложения. Те обикновено са предназначени за крайни потребители от различни региони или различни езикови области.

Винаги е предизвикателство да поддържате и разширявате тези приложения. Способността да се работи с различни локализирани данни едновременно обикновено е от решаващо значение. Модификацията на данните на приложението трябва да бъде възможно най-опростена, без необходимост от прекомпилация. Ето защо обикновено избягваме твърдо кодиране на имена на етикети или бутони.

За щастие можем да разчитаме на Java, която ни предоставя този клас, който ни помага да решим всички проблеми, споменати по-горе.

Просто казано, ResourceBundle дава възможност на нашето приложение да зарежда данни от отделни файлове, съдържащи специфични за локала данни.

1.1. ResourceBundles

Първото нещо, което трябва да знаем, е, че всички файлове в един пакет ресурси трябва да са в един и същ пакет / директория и да имат общо базово име . Те могат да имат специфични за локала суфикси, указващи език, държава или платформа, разделени със символа за подчертаване.

Важно е да можем да добавим код на държава, ако вече има код на език, или платформа, ако има кодове на език и държава.

Нека разгледаме примерни имена на файлове:

  • ПримерРесурс
  • ExampleResource_en
  • ExampleResource_en_US
  • ExampleResource_en_US_UNIX

Файлът по подразбиране за всеки пакет данни винаги е такъв без никакви суфикси - ExampleResource . Тъй като има два подкласа на ResourceBundle : PropertyResourceBundle и ListResourceBundle , ние можем взаимно да съхраняваме данни в файлове с имоти, както и в java файлове.

Всеки файл трябва да има специфично за локала име и подходящо разширение на файла , например ExampleResource_en_US.properties или Example_en.java .

1.2. Файлове на имоти - PropertyResourceBundle

Файловете с имоти са представени от PropertyResourceBundle. Те съхраняват данни под формата на чувствителни към регистъра двойки ключ-стойност.

Нека анализираме примерен файл със свойства:

# Buttons continueButton continue cancelButton=cancel ! Labels helloLabel:hello 

Както виждаме, има три различни стила на дефиниране на двойки ключ-стойност.

Всички те са еквивалентни, но първият е може би най-популярният сред Java програмистите. Струва си да се знае, че можем да поставяме коментари и в файлове с имоти. Коментарите винаги започват с # или ! .

1.3. Java файлове - ListResourceBundle

На първо място, за да съхраняваме специфичните за езика ни данни, трябва да създадем клас, който разширява ListResourceBundle и замества метода getContents () . Конвенцията за името на класа е същата като за файловете със свойства.

За всеки Locale трябва да създадем отделен Java клас.

Ето примерен клас:

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

Java файловете имат едно основно предимство пред файловете със свойства, което е възможност да съхраняваме всеки обект, който искаме - не само Strings.

От друга страна, всяка модификация или въвеждане на нов специфичен за локал java клас изисква рекомпилация на приложение, докато файловете с свойства могат да бъдат разширени без никакви допълнителни усилия.

2. Използвайте пакети ресурси

Вече знаем как да дефинираме пакети ресурси, така че сме готови да го използваме.

Нека разгледаме краткия кодов фрагмент:

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

Първо, можем да дефинираме нашия локал , освен ако не искаме да използваме този по подразбиране.

След това нека извикаме статичен фабричен метод на ResourceBundle . Трябва да предадем името на пакета с неговия пакет / директория и локала като параметри.

Има и фабричен метод, който изисква име на пакет само ако локалът по подразбиране е наред. Веднага след като имаме обекта, можем да извлечем стойности чрез техните ключове.

Освен това примерът показва, че можем да използваме getString (String key) , getObject (String key) и getStringArray (String key), за да получим стойности, които искаме.

3. Избор на правилния ресурс на пакета

Ако искаме да използваме пакетни ресурси, важно е да знаем как Java избира пакетни файлове.

Нека си представим, че работим с приложение, което се нуждае от етикети на полски, но локалът ви по подразбиране на JVM е Locale.US .

В началото приложението ще търси файловете в пътя на класа, подходящи за локала, който искате. Започва с най-конкретното име, т.е. такова, съдържащо платформа, държава и език.

След това преминава към по-общо. Ако няма съвпадение, то се връща към локала по подразбиране без проверка на платформа този път.

В случай на липса на съвпадение, той ще се опита да прочете пакета по подразбиране. Всичко трябва да е ясно, когато разгледаме реда на избраните имена на файлове:

  • Label_pl_PL_UNIX
  • Label_pl_PL
  • Label_pl
  • Label_en_US
  • Label_en
  • Етикет

Трябва да имаме предвид, че всяко име представлява както файловете .java, така и .properties , но първото има предимство пред второто. Когато няма подходящ файл, се изхвърля MissingResourceException .

4. Наследяване

Друго предимство на концепцията за пакет от ресурси е наследяването на собственост. Това означава, че двойките ключ-стойности, включени в по-малко специфични файлове, се наследяват от тези, които са по-високи в дървото за наследяване.

Да приемем, че имаме три файла със свойства:

#resource.properties cancelButton = cancel #resource_pl.properties continueButton = dalej #resource_pl_PL.properties backButton = cofnij

Пакетът ресурси, извлечен за Locale (“pl”, “PL”), ще върне и трите ключа / стойности в резултата. Струва си да се спомене, няма връщане към снопа по подразбиране по отношение на наследяването на собствеността.

Нещо повече, ListResourceBundles и PropertyResourceBundles не са в една и съща йерархия.

Така че, ако файл с свойства се намери в пътя на класа, тогава двойките ключ-стойност се наследяват само от файлове с свойства. Същото правило се отнася за Java файлове.

5. Персонализиране

All we've learned above was about the default implementation of ResourceBundle. However, there's a way we can modify its behavior.

We do this by extending ResourceBoundle.Control and overriding its methods.

For example, we can change the time of keeping values in cache or determine the condition when the cache should be reloaded.

For a better understanding, let's prepare a short method as an example:

public class ExampleControl extends ResourceBundle.Control { @Override public List getCandidateLocales(String s, Locale locale) { return Arrays.asList(new Locale("pl", "PL")); } }

The purpose of this method is to change a manner of selecting files in the classpath. As we can see, ExampleControl will return only polish Locale, no matter what the default or defined Locale is.

6. UTF-8

Since there're still many applications using JDK 8 or older versions, it's worth to know that prior to Java 9ListResourceBundles had one more advantage over PropertyResourceBundles. As Java files can store String objects, they are able to hold any character supported by UTF-16 encoding.

On the contrary, PropertyResourceBundle loads files by default using ISO 8859-1 encoding, which has fewer characters than UTF-8 (causing problems for our Polish language examples).

In order to save characters which are beyond UTF-8, we can use the Native-To-ASCII converter – native2ascii. It converts all characters that aren't compliant with ISO 8859-1 by encoding them to \uxxxx notation.

Here's an example command:

native2ascii -encoding UTF-8 utf8.properties nonUtf8.properties

And let's see how properties look like before and after a change of encoding:

#Before polishHello=cześć #After polishHello=cze\u015b\u0107

Fortunately, this inconvenience exists no longer in Java 9. JVM reads property files in UTF-8 encoding, and there's no problem in using non-Latin characters.

7. Conclusion

BundleResource contains much of what we need to develop a multilingual application. The features we've covered make manipulation of different locales pretty straightforward.

Ние също така избягваме стойности на твърдо кодиране, което ни позволява да разширяваме поддържаните локали, като просто добавяме нови файлове с локали, което позволява на нашето приложение да бъде плавно модифицирано и поддържано.

Както винаги, примерният код е достъпен в над на GitHub.