Locales и ResourceBundles в основанной на плагине программе - PullRequest
4 голосов
/ 12 ноября 2008

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

Насколько я понимаю, Java ResourceBundle работает так. Вы создаете класс, который расширяет ResourceBundle, называемый чем-то вроде MyProgramStrings, а также языковые классы, называемые MyProgramStrings_fr, MyProgramStrings_es и т. Д. Каждый из этих классов отображает ключи (строки) в значения (любой объект). Каждый из этих классов может получить свои данные, но общим местом для них является файл свойств.

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

Locale locale = Locale.getDefault(); // or = new Locale("en", "GB");
ResourceBundle rb = ResourceBundle.getBundle("MyProgramStrings", locale);
String wotsitName = rb.getString("wotsit.name");

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

Я немного растерялся во всем этом. Кто-нибудь может помочь?


Обновление: Дэвид Уотерс спросил:

Я поставил свой ответ внизу, но мне было бы интересно услышать, как вы решили эту проблему.

Ну, мы еще не очень далеко - долгосрочные WIBNI всегда становятся жертвами последнего кризиса - но мы основываем это на интерфейсе, который реализует плагин, с условием, что ресурсы имеют одно и то же полностью определенное имя в качестве интерфейса.

Таким образом, интерфейс UsersAPI может иметь различные реализации. Метод getBundle() в этом интерфейсе по умолчанию возвращает эквивалент ResourceBundle.get("...UsersAPI", locale). Этот файл можно заменить или реализации UsersAPI могут переопределить метод, если им нужно что-то более сложное.

Пока что это делает то, что нам нужно, но мы все еще ищем более гибкие решения на основе плагинов.

Ответы [ 4 ]

2 голосов
/ 12 ноября 2008

У вас нет для реализации ResourceBundles как серии классов, по одному классу на локаль (то есть класс с именем MyProgramStrings, MyProgramStrings_fr, MyProgramStrings_de). При необходимости класс ResourceBundle будет использовать файлы свойств:

public static void main(String[] args) {

    ResourceBundle bundle = ResourceBundle.getBundle("MyResources");
    System.out.println("got bundle: " + bundle);

    String valueInBundle = bundle.getString("someKey");
    System.out.println("Value in bundle is: " + valueInBundle);
}

Если у меня есть файл с именем MyResources.properties на пути к классам, то этот метод приведет к:

got bundle: java.util.PropertyResourceBundle@42e816  
Value in bundle is: someValue

Что касается настройки иерархии пакетов или их «слияния», я боюсь, что не могу там сильно помочь, кроме того, что я знаю, что у Spring действительно есть понятие иероглифические источники сообщений ( ссылка на API ), которые реализованы поверх java.util.ResourceBundle, так что, возможно, вы можете использовать функциональность Spring для достижения того, что вы хотите?

Кстати, вот соответствующая часть ResourceBundle.getBundle() javadoc, объясняющая, что это «стратегия поиска и создания экземпляров»:

http://java.sun.com/j2se/1.5.0/docs/api/java/util/ResourceBundle.html#getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader)

  • Сначала он пытается загрузить класс, используя имя пакета-кандидата. Если такой класс может быть найден и загружен с использованием указанного загрузчика классов, совместим по присваиванию с ResourceBundle, доступен из ResourceBundle и может быть создан, getBundle создает новый экземпляр этого класса и использует его в качестве результирующего пакета ресурсов.
  • В противном случае getBundle пытается найти файл ресурса свойства. Он генерирует путь из имени пакета кандидата, заменяя все "." символы с "/" и добавлением строки ".properties". Он пытается найти «ресурс» с этим именем, используя ClassLoader.getResource. (Обратите внимание, что «ресурс» в смысле getResource не имеет ничего общего с содержимым пакета ресурсов, это просто контейнер данных, например файл.) Если он находит «ресурс», он пытается создать новый экземпляр PropertyResourceBundle из его содержимого. В случае успеха этот экземпляр становится результирующим пакетом ресурсов.
1 голос
/ 18 марта 2009

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

Таким образом, если вы хотите сохранить метки для пользовательского интерфейса, у вас будет класс com.example.UILabels и файл свойств com.example.UILabelsFiles.

package com.example;

public class UILabels extends ResourceBundle{
    // plugins call this method to register there own resource bundles to override
    public static void addPluginResourceBundle(String bundleName){
        extensionBundles.add(bundleName);
    }

    // Find the base Resources via standard Resource loading
    private ResourceBundle getFileResources(){
        return ResourceBundle.getBundle("com.example.UILabelsFile", this.getLocale());
    }
    private ResourceBundle getExtensionResources(String bundleName){
        return ResourceBundle.getBundle(bundleName, this.getLocale());
    }

    ...
    protected Object handleGetObject(String key){
        // If there is an extension value use that
        Object extensionValue = getValueFromExtensionBundles(key);
        if(extensionValues != null)
            return extensionValues;
        // otherwise use the one defined in the property files
        return getFileResources().getObject(key);
    }

    //Returns the first extension value found for this key, 
    //will return null if not found    
    //will return the first added if there are multiple.
    private Object getValueFromExtensionBundles(String key){
        for(String bundleName : extensionBundles){
            ResourceBundle rb = getExtensionResources(bundleName);
            Object o = rb.getObject(key);
            if(o != null) return o;
        }
        return null;
    }    

}
1 голос
/ 12 ноября 2008

getBundle загружает набор файлов-кандидатов, сгенерированных с использованием указанной локали (если есть) и локали по умолчанию.

Как правило, файл ресурса без информации о локали имеет значения «по умолчанию» (например, возможно, значения en_US находятся в MyResources.properties), тогда для различных локалей создаются значения ресурса перегрузки (например, * 1004 в MyResources_ja.properties) Любой ресурс в более конкретном файле переопределяет менее конкретные свойства файла.

Теперь вы хотите добавить возможность для каждого плагина предоставлять свой собственный набор файлов свойств. Не ясно, сможет ли плагин изменять ресурсы основного приложения или других плагинов, но похоже, что вы хотите иметь одноэлементный класс, в котором каждый плагин может зарегистрировать свое имя файла базового ресурса. , Все запросы значений свойств проходят через этот синглтон, который затем просматривает пакеты ресурсов каждого плагина (в некотором порядке ...) и, наконец, само приложение для значения свойства.

Класс Netbeans NbBundle делает подобные вещи.

1 голос
/ 12 ноября 2008

Я знаю, что у Struts и Spring есть кое-что для этого. Но допустим, что вы не можете использовать Struts или Spring, тогда я бы создал подкласс ResourceBundle и загрузил * .properties (по одному на каждый плагин) в этот ResourceBundle. Тогда вы можете использовать

ResourceBundle bundle = ResourceBundle.getBundle ("MyResources");

Как вы обычно делаете с файлом свойств.

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

...