«1. Обзор
Многие разработчики программного обеспечения в ходе своей профессиональной карьеры сталкиваются с возможностью разрабатывать многоязычные системы или приложения. Обычно они предназначены для конечных пользователей из разных регионов или разных языковых областей.
Всегда сложно поддерживать и расширять эти приложения. Способность одновременно работать с различными специфическими для локализации данными обычно имеет решающее значение. Модификация данных приложения должна быть максимально простой без необходимости перекомпиляции. Вот почему мы обычно избегаем жестко запрограммированных названий меток или кнопок.
К счастью, мы можем рассчитывать на Java, которая предоставляет нам этот класс, который помогает нам решить все проблемы, упомянутые выше.
Проще говоря, ResourceBundle позволяет нашему приложению загружать данные из отдельных файлов, содержащих данные, зависящие от локали.
1.1. ResourceBundles
Первое, что мы должны знать, это то, что все файлы в одном пакете ресурсов должны находиться в одном пакете/каталоге и иметь общее базовое имя. Они могут иметь локальные суффиксы, указывающие на язык, страну или платформу, разделенные символом подчеркивания.
Важно, чтобы мы могли добавить код страны, если уже есть код языка, или платформу, если коды языка и страны присутствуют.
Давайте посмотрим на примеры имен файлов:
-
ExampleResource 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(). Соглашение об именах классов такое же, как и для файлов свойств.
Для каждой локали нам нужно создать отдельный класс 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 имеют одно важное преимущество перед файлами свойств, которое заключается в возможности хранения любого объекта, который мы хотим, а не только строк.
С другой стороны, каждая модификация или введение нового класса 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(строковый ключ), getObject(строковый ключ) и getStringArray(строковый ключ) для получения нужных нам значений.
3. Выбор правильного ресурса пакета
Если мы хотим использовать ресурс пакета, важно знать, как Java выбирает файлы пакета.
Давайте представим, что мы работаем с приложением, которому нужны метки на польском языке, но ваша локаль JVM по умолчанию — Locale.US.
«Вначале приложение будет искать файлы в пути к классам, подходящие для запрашиваемой вами локали. Он начинается с наиболее конкретного имени, то есть с указанием платформы, страны и языка.
Затем мы переходим к более общему. Если совпадения нет, он возвращается к локали по умолчанию без проверки платформы на этот раз.
В случае несоответствия он попытается прочитать пакет по умолчанию. Все должно быть ясно, когда мы смотрим на порядок имен выбранных файлов: последний. Когда подходящего файла нет, генерируется исключение MissingResourceException.
-
4. Наследование
Еще одним преимуществом концепции набора ресурсов является наследование свойств. Это означает, что пары ключ-значение, включенные в менее специфичные файлы, наследуются теми, которые находятся выше в дереве наследования.
Предположим, что у нас есть три файла свойств:
Пакет ресурсов, извлеченный для Locale(“pl”, “PL”), вернет в результате все три ключа/значения. Стоит отметить, что нет возможности вернуться к пакету локалей по умолчанию, поскольку рассматривается наследование свойств.
Более того, ListResourceBundles и PropertyResourceBundles не находятся в одной иерархии.
#resource.properties
cancelButton = cancel
#resource_pl.properties
continueButton = dalej
#resource_pl_PL.properties
backButton = cofnij
Таким образом, если файл свойств найден в пути к классам, пары ключ-значение наследуются только из файлов свойств. То же правило применяется к файлам Java.
5. Настройка
Все, что мы узнали выше, касалось реализации ResourceBundle по умолчанию. Однако есть способ изменить его поведение.
Мы делаем это, расширяя ResourceBoundle.Control и переопределяя его методы.
Например, мы можем изменить время хранения значений в кеше или определить условие, при котором кеш должен быть перезагружен.
Для лучшего понимания давайте в качестве примера подготовим короткий метод:
Целью этого метода является изменение способа выбора файлов в пути к классам. Как мы видим, ExampleControl будет возвращать только полированный Locale, независимо от того, какой Locale задан по умолчанию или определен.
6. UTF-8
public class ExampleControl extends ResourceBundle.Control {
@Override
public List<Locale> getCandidateLocales(String s, Locale locale) {
return Arrays.asList(new Locale("pl", "PL"));
}
}
Поскольку многие приложения используют JDK 8 или более ранние версии, стоит знать, что до Java 9 ListResourceBundles имели еще одно преимущество перед PropertyResourceBundles. Поскольку файлы Java могут хранить объекты String, они могут содержать любой символ, поддерживаемый кодировкой UTF-16.
Напротив, PropertyResourceBundle загружает файлы по умолчанию, используя кодировку ISO 8859-1, которая содержит меньше символов, чем UTF-8 (вызывая проблемы с нашими примерами на польском языке).
Чтобы сохранить символы, выходящие за пределы UTF-8, мы можем использовать конвертер Native-To-ASCII — native2ascii. Он преобразует все символы, не соответствующие стандарту ISO 8859-1, кодируя их в нотацию \\uxxxx.
Вот пример команды:
А давайте посмотрим, как выглядят свойства до и после изменения кодировки:
К счастью, в Java 9 этого неудобства больше нет. JVM читает файлы свойств в кодировке UTF-8, и нет проблем с использованием нелатинских символов.
native2ascii -encoding UTF-8 utf8.properties nonUtf8.properties
7. Заключение
#Before
polishHello=cześć
#After
polishHello=cze\u015b\u0107
BundleResource содержит многое из того, что нам нужно для разработки многоязычного приложения. Функции, которые мы рассмотрели, упрощают работу с различными локалями.
Мы также избегаем жестко заданных значений, что позволяет нам расширять поддерживаемые локали, просто добавляя новые файлы локалей, что позволяет плавно изменять и поддерживать наше приложение.
Как всегда, пример кода доступен на GitHub.
«
As always, the sample code is available in over on GitHub.