Перейти к основному содержимому

Руководство по ResourceBundle

· 6 мин. чтения

1. Обзор

Многие разработчики программного обеспечения в ходе своей профессиональной карьеры сталкиваются с возможностью разрабатывать многоязычные системы или приложения. Обычно они предназначены для конечных пользователей из разных регионов или разных языковых областей.

Всегда сложно поддерживать и расширять эти приложения. Способность одновременно работать с различными специфическими для локализации данными обычно имеет решающее значение. Модификация данных приложения должна быть максимально простой без необходимости перекомпиляции. Вот почему мы обычно избегаем жестко запрограммированных названий меток или кнопок.

К счастью, мы можем положиться на Java, которая предоставляет нам этот класс, который помогает нам решить все проблемы, упомянутые выше.

Проще говоря, ResourceBundle позволяет нашему приложению загружать данные из отдельных файлов, содержащих данные, зависящие от локали.

1.1. ResourceBundles

Первое, что мы должны знать, это то, что все файлы в одном пакете ресурсов должны находиться в одном пакете/каталоге и иметь общее базовое имя . Они могут иметь локальные суффиксы, указывающие на язык, страну или платформу, разделенные символом подчеркивания.

Важно, чтобы мы могли добавить код страны, если уже есть код языка, или платформу, если коды языка и страны присутствуют.

Давайте посмотрим на примеры имен файлов:

  • ПримерРесурс
  • ПримерResource_ru
  • ПримерResource_en_US
  • ПримерResource_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"});

Во- первых, мы можем определить наш Locale , если только мы не хотим использовать его по умолчанию.

После этого давайте вызовем статический фабричный метод 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. Настройка

Все, что мы узнали выше, касалось реализации ResourceBundle по умолчанию . Однако есть способ изменить его поведение.

Мы делаем это, расширяя ResourceBoundle.Control и переопределяя его методы.

Например, мы можем изменить время хранения значений в кеше или определить условие, когда кеш должен быть перезагружен.

Для лучшего понимания подготовим краткий метод в качестве примера:

public class ExampleControl extends ResourceBundle.Control {

@Override
public List<Locale> getCandidateLocales(String s, Locale locale) {
return Arrays.asList(new Locale("pl", "PL"));
}
}

Цель этого метода — изменить способ выбора файлов в пути к классам. Как мы видим, ExampleControl будет возвращать только полированный Locale , независимо от того, какой Locale задан по умолчанию или определен .

6. УТФ-8

Поскольку многие приложения используют JDK 8 или более ранние версии, стоит знать, что до Java 9 у ListResourceBundles было еще одно преимущество перед PropertyResourceBundles . Поскольку файлы Java могут хранить объекты String, они могут содержать любой символ, поддерживаемый кодировкой UTF-16 .

Напротив, PropertyResourceBundle загружает файлы по умолчанию, используя кодировку ISO 8859-1 , которая содержит меньше символов, чем UTF-8 (вызывая проблемы с нашими примерами на польском языке).

Чтобы сохранить символы, выходящие за пределы UTF-8 , мы можем использовать конвертер Native-To-ASCIInative2ascii . Он преобразует все символы, не соответствующие стандарту ISO 8859-1, кодируя их в нотацию \uxxxx .

Вот пример команды:

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

И посмотрим, как выглядят свойства до и после смены кодировки:

#Before
polishHello=cześć

#After
polishHello=cze\u015b\u0107

К счастью, в Java 9 этого неудобства больше нет. JVM читает файлы свойств в кодировке UTF-8 , и нет проблем с использованием нелатинских символов.

7. Заключение

BundleResource содержит многое из того, что нам нужно для разработки многоязычного приложения. Функции, которые мы рассмотрели, упрощают работу с различными локалями.

Мы также избегаем жестко заданных значений, что позволяет нам расширять поддерживаемые локали , просто добавляя новые файлы локалей , что позволяет плавно изменять и поддерживать наше приложение.

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