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

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 () . Соглашение об именах классов такое же, как и для файлов свойств.

Для каждого 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.

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

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) , getObject (ключ String), и getStringArray (ключ String) , чтобы получить нужные значения.

3. Выбор правильного набора ресурсов

  • Если мы хотим использовать пакетный ресурс, важно знать, как Java выбирает пакетные файлы. **

Давайте представим, что мы работаем с приложением, которому нужны метки на польском языке, но по умолчанию JVM locale Locale.US

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

Затем идет к более общему. Если совпадения не найдено, он возвращается к стандартному языку без проверки платформы на этот раз.

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

  • 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. UTF-8

Поскольку во многих приложениях используется 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 .

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

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

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

#Before
polishHello=cześć

#After
polishHello=cze\u015b\u0107

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

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

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

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

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