Статьи

Советы и рекомендации по ресурсам

Сегодня день ресурсов. Это самый известный механизм интернационализации (i18n) в Java в целом. Работа с ним должна быть легкой. Но есть много маленьких вопросов, которые возникают, когда ваши руки пачкаются этим. Если вы чувствуете то же самое, этот пост для вас.

основы

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

Самый базовый пример использует следующее семейство:
Messages.properties
Messages_de.properties
Messages_en.properties

Если вам нужно запросить пакет в вашем приложении, просто позвоните

1
ResourceBundle bundle = ResourceBundle.getBundle("Messages");

Метод и запрос возвращенного пакета:

1
bundle.getString("welcome.message");

Если вам интересно, какой язык будет использоваться здесь, вы правы. Конструктор String неявно использует Locale.getDefault () для разрешения языка. Это может быть не то, что вы хотите. Так что вы должны ResourceBundle bundle =

1
ResourceBundle.getBundle("Messages", locale);

Вы не можете установить языковой стандарт после получения пакета. Каждый ResourceBundle имеет одну определенную локаль.

Нейминг
 
Несколько мыслей о наименовании. Назовите свойства пакета после их содержимого. Вы можете пойти по более общему пути, просто назвав их «Сообщения» и «Ошибки» и т. Д., Но также возможно иметь пакет для каждой подсистемы или компонента. Все, что подходит для ваших нужд. Поддерживать содержимое нелегко с большим количеством записей. Таким образом, любой вид контекстного разделения делает разработчиков счастливыми. Файлы свойств пакета эквивалентны классам; Назовите их соответственно. И в дальнейшем вы должны найти общую систему именования ваших ключей. В зависимости от разделения, которое вы выбрали для файлов свойств, вы также можете ввести некоторую подсистему или пространство имен компонентов со своими ключами. Префиксы страниц также возможны. Подумайте об этом с умом и поиграйте с этим. Вы стремитесь иметь как можно меньше дубликатов в своих ключах.

Инкапсуляция
 
Как вы уже видели, вы часто используете строковое представление пакетов. Тот факт, что это на самом деле имена файлов (или лучше имена классов), вам будет проще с простым перечислением, которое инкапсулирует все для вас:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public enum ResourceBundles {
    MESSAGES("Messages"),
    ERRORS("Errors");
    private String bundleName; 
 
    ResourceBundles(String bundleName) {
        this.bundleName = bundleName;
    }
 
    public String getBundleName() {
        return bundleName;
    }
 
    @Override
    public String toString() {
        return bundleName;
    }
}

Имея это, вы просто можете написать

1
ResourceBundle bundle = ResourceBundle.getBundle(MESSAGES.getBundleName());

Java Server Faces и ResourceBundles
 
Чтобы использовать пакеты ресурсов в приложении, основанном на jsf, вам просто нужно определить их в вашем Face-config.xml и использовать ярлыки в ваших файлах xhtml.

1
2
3
<resource-bundle>
<base-name>Messages</base-name>
<var>msgs</var>
1
<h:outputLabel value="#{msgs['welcome.general']}" />

JSF позаботится обо всем остальном. Как насчет замены параметров? Подумайте о паре ключ-значение, как показано ниже:

1
welcome.name=Hi {0}! How are you?

Вы можете передать параметр через тег f: param:

1
2
3
<h:outputFormat value="#{msgs['welcome.name']}">
        <f:param value="Markus" />
</h:outputFormat>

Чтобы изменить язык, вы должны установить конкретную локаль для текущего экземпляра FacesContext. Лучше всего сделать это через слушателя изменения значения:

01
02
03
04
05
06
07
08
09
10
public void countryLocaleCodeChanged(ValueChangeEvent e) {
    String newLocaleValue = e.getNewValue().toString();
    //loop country map to compare the locale code
    for (Map.Entry<String, Object> entry : countries.entrySet()) {
        if (entry.getValue().toString().equals(newLocaleValue)) {
            FacesContext.getCurrentInstance()
                    .getViewRoot().setLocale((Locale) entry.getValue());
        }
    }
}

Пакеты ресурсов в EJB
 
JSF очевидно очень легко интегрируется. Как насчет использования этих пакетов в EJB? Это в основном то же самое. У вас есть те же механизмы, чтобы взять руку на связку и использовать ее. Есть одна вещь, которую вы должны иметь в виду. Вы, вероятно, не хотите всегда использовать локаль по умолчанию. Таким образом, вы должны найти способ передать локаль из пользовательского интерфейса. Если вы думаете о @ Injecting the MessageBundle с помощью аннотации @Produces, вам нужно подумать не один раз. Особенно если вы работаете с @Stateless EJB. Эти экземпляры объединяются, и вы должны передать языковой стандарт любому бизнес-методу, который должен знать о текущем языковом стандарте. Обычно вы делаете это с помощью объекта параметра или какого-либо профиля пользовательского сеанса. Не добавляйте Locale как подпись метода.

Пакеты ресурсов из БД
 
В большинстве случаев я вижу, что вам нужно вытащить ключи из БД. Учитывая внутреннюю работу ResourceBundle (один «класс» на локаль), вам в конечном итоге придется реализовать логику в собственной реализации ResourceBundle. Большинство примеров, которые вы найдете в Интернете, делают это путем переопределения метода handleGetObject (String key). Мне не нравится этот подход, тем более что у нас есть гораздо лучший способ использования механизма ResourceBundle.Control. Теперь вы можете переопределить метод newBundle () и вернуть собственную реализацию ResourceBundle. Все, что вам нужно сделать, это установить свой собственный элемент управления в качестве родителя с вашим DatabaseResourceBundle:

1
2
3
4
public DatabaseResourceBundle() {
        setParent(ResourceBundle.getBundle(BUNDLE_NAME,
        FacesContext.getCurrentInstance().getViewRoot().getLocale(), new DBControl()));
    }

DBControl возвращает MyResourceBundle, который является ListResourceBundle:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
protected class DBControl extends Control {
 
        @Override
        public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
                throws IllegalAccessException, InstantiationException, IOException {
            return new MyResources(locale);
        }
 
        /**
         * A simple ListResourceBundle
         */
        protected class MyResources extends ListResourceBundle {
 
            private Locale locale;
 
            /**
             * ResourceBundle constructor with locale
             *
             * @param locale
             */
            public MyResources(Locale locale) {
                this.locale = locale;
            }
 
            @Override
            protected Object[][] getContents() {
                TypedQuery<ResourceEntity> query = _entityManager.createNamedQuery("ResourceEntity.findForLocale", ResourceEntity.class);
                query.setParameter("locale", locale);
 
                List<ResourceEntity> resources = query.getResultList();
                Object[][] all = new Object[resources.size()][2];
                int i = 0;
  for (Iterator<ResourceEntity> it = resources.iterator(); it.hasNext();) {
  ResourceEntity resource = it.next();
  all[i] = new Object[]{resource.getKey(), resource.getValue()};
  values.put(resource.getKey(), resource.getValue());
   i++;
  }
                return all;
            }
        }
    }

Как видите, это поддерживается сущностным менеджером и простым ResourceEntity, который имеет все поля и NamedQueries, необходимые для построения различных пакетов.

1
2
3
4
5
6
7
8
9
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "i18n_key")
private String key;
@Column(name = "i18n_value")
private String value;
@Column(name = "i18n_locale")
private Locale locale;

Поместив комплекты в закрытое Map <String, String> values ​​= new HashMap <String, String> (); у вас также есть хороший способ кеширования результатов после первого создания пакетов.

Это все еще не лучшее решение, поскольку у ResourceBundles есть способ кэширования. Но я мог бы углубиться в это более подробно позже. До сих пор этот пакет кэшируется навсегда (или, по крайней мере, до следующего перераспределения).

Переписать как переключатель языка
 
И последнее, что стоит упомянуть, это то, что у вас также могут быть здесь некоторые модные дополнения. Если у вас уже есть магия переключения языка JSF, просто добавьте переписывание ocpsoft в ваше приложение. Это простой способ кодирования языка в URL-адресах, например, http://yourhost.com/Bundle-Provider-Tricks/en/index.html
Все, что вам нужно сделать, это добавить переписать в игру, добавив две простые зависимости:

01
02
03
04
05
06
07
08
09
10
<dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-servlet</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-integration-faces</artifactId>
            <version>1.1.0.Final</version>
        </dependency>

Для переписывания необходимо, чтобы вы добавили свой собственный ConfigurationProvider, который является центральным местом для хранения ваших правил переписывания. Реализуйте следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class BundleTricksProvider extends HttpConfigurationProvider {
 
    @Override
    public Configuration getConfiguration(ServletContext context) {
        return ConfigurationBuilder.begin()
                // Locale Switch
                .addRule(Join.path("/{locale}/{page}.html").to("/{page}.xhtml")
                .where("page").matches(".*")
                .where("locale").bindsTo(PhaseBinding.to(El.property("#{languageSwitch.localeCode}")).after(PhaseId.RESTORE_VIEW)));
    }
 
    @Override
    public int priority() {
        return 10;
    }
}

Далее следует добавить файл с именем «org.ocpsoft.rewrite.config.ConfigurationProvider» в папку META-INF / services и поместить туда полное имя вашей реализации ConfigurationProvider. И последнее, что нужно настроить — это логика в компоненте LanguageSwitch. Rewrite не может вызвать ValueChangeEvent (насколько я знаю :)), поэтому вы должны добавить магию, чтобы изменить Locale, пока вызывается сеттер. Вот и все .. очень просто!

Ссылка: Советы и рекомендации по ресурсам от нашего партнера JCG Маркуса Эйзела (Markus Eisele) из блога « Разработка программного обеспечения для предприятий с использованием Java» .