基于插件的程序中的语言环境和资源包

发布于 2024-07-08 22:27:28 字数 1189 浏览 5 评论 0原文

我们需要开始为我们的计划添加国际化。 值得庆幸的是,还不是全部,只是一些部分,但我希望我们的方式能够扩大规模,以覆盖整个计划。 问题是,我们的程序基于插件,因此并非所有字符串都属于同一个位置。

据我了解,Java的ResourceBundle是这样工作的。 您创建一个扩展 ResourceBundle 的类,名为 MyProgramStrings 之类,以及名为 MyProgramStrings_frMyProgramStrings_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 的实现可以覆盖该方法。

到目前为止,这满足了我们的需要,但我们仍在寻找基于插件的更灵活的解决方案。

We need to start adding internationalisation to our program. Thankfully not the whole thing yet, just a few bits, but I want the way we do it to scale up to potentially cover the whole program. The thing is, our program is based on plugins, so not all strings belong in the same place.

As far as I understand it, Java's ResourceBundle work like this. You create a class that extends ResourceBundle, called something like MyProgramStrings, and also language-specific classes called MyProgramStrings_fr, MyProgramStrings_es etc. Each of these classes maps keys (strings) to values (any object). It's up to each of these classes where to get its data from, but a common place for them is a properties file.

You look up values in two stages: first you get the correct bundle, then you query it for the string you want.

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

However, what we need is to combine the results of several locales into a single resource space. For example, a plugin needs to be able to override a string that's already defined, and have that new value returned whenever code looks up the string.

I'm a little lost in all this. Can anybody help?


Update: David Waters asked:

I have put my answer at the bottom but I would be interested in hearing how you solved this problem.

Well, we haven't got very far yet - long term WIBNIs always fall victim to the latest crisis - but we're basing it on the interface that a plugin implements, with the convention that resources have the same fully qualified name as the interface.

So an interface UsersAPI may have various different implementations. A method getBundle() on that interface by default returns the equivalent of ResourceBundle.get("...UsersAPI", locale). That file can be replaced, or implementations of UsersAPI can override the method if they need something more complicated.

So far that does what we need, but we're still looking at more flexible solutions based on the plugins.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

故事未完 2024-07-15 22:27:28

您不必将 ResourceBundles 实现为一系列类,每个区域设置一个类(即名为 MyProgramStringsMyProgramStrings_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 实例。 如果成功,该实例将成为结果资源包。

You don't have to implement ResourceBundles as a series of classes, with one class per locale (i.e. a class named MyProgramStrings, MyProgramStrings_fr, MyProgramStrings_de). The ResourceBundle class will fall back to using properties files if need be:

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);
}

If I have a file named MyResources.properties on the classpath, then this method will result in:

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

As for setting up a hierarchy of bundles, or "merging" them together, I'm afraid I can't help much there, except that I know that Spring does have a concept of hierchical MessageSources (link to API) which are implemented on top of java.util.ResourceBundle, so perhaps you can use Spring's functionality to achieve what you want?

BTW, here is the relevant part of the ResourceBundle.getBundle() javadoc that explains it's "search and instantiation strategy":

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

  • First, it attempts to load a class using the candidate bundle name. If such a class can be found and loaded using the specified class loader, is assignment compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated, getBundle creates a new instance of this class and uses it as the result resource bundle.
  • Otherwise, getBundle attempts to locate a property resource file. It generates a path name from the candidate bundle name by replacing all "." characters with "/" and appending the string ".properties". It attempts to find a "resource" with this name using ClassLoader.getResource. (Note that a "resource" in the sense of getResource has nothing to do with the contents of a resource bundle, it is just a container of data, such as a file.) If it finds a "resource", it attempts to create a new PropertyResourceBundle instance from its contents. If successful, this instance becomes the result resource bundle.
只想待在家 2024-07-15 22:27:28

我知道 Struts 和 Spring 对此有一些东西。 但假设您不能使用 Struts 或 Spring,那么我要做的就是创建 ResourceBundle 的子类并在该 ResourceBundle 中加载 *.properties(每个插件一个)。 然后你可以使用

ResourceBundle 捆绑 = ResourceBundle.getBundle("MyResources");

就像您通常对属性文件所做的那样。

当然,您应该缓存结果,这样您就不必每次都搜索每个属性。

I know Struts and Spring have something for that. But let's say you can't use Struts or Spring then what I would do is to create a subclass of ResourceBundle and load the *.properties (one per plugin) in this ResourceBundle. Then you can use

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

Like you would normally do with a property file.

Of course, you should cached the result so you don't have to search each Properties every time.

来世叙缘 2024-07-15 22:27:28

getBundle 加载使用指定区域设置(如果有)和默认区域设置生成的一组候选捆绑包文件。

通常,没有区域设置信息的资源文件具有“默认”值(例如,en_US 值可能位于 MyResources.properties 中),然后为不同的区域设置创建覆盖资源值(例如 ja 字符串位于 MyResources_ja.properties 中)更具体的文件中的任何资源都会覆盖不太具体的文件属性。

现在您想要为每个插件添加提供其自己的属性文件集的功能。 目前尚不清楚该插件是否能够修改主应用程序或其他插件的资源,但听起来您想要一个单例类,其中每个插件都可以注册其基本资源文件名。 对属性值的所有请求都会通过该单例,然后该单例将查看每个插件的资源包(按某种顺序......),最后查看应用程序本身以获取属性值。

Netbeans NbBundle 类执行类似的操作。

getBundle loads a set of candidate bundle files generated using the specified locale (if any) and the default locale.

Typically, a resource file with no locale info has the "default" values (eg, perhaps en_US values are in MyResources.properties), then over riding resource values are created for different locales (eg ja strings are in MyResources_ja.properties) Any resource in the more specific file overrides the less specific file properties.

Now you want to add in the ability for each plug-in to provide it's own set of properties files. It isn't clear whether the plug-in would be able to modify the resources of the main app or other plug-ins, but it sounds like you want to have a singleton class where each plug-in can register its base resource file name. All requests for property values go through that singleton, which would then look through the resource bundles of each plug in (in some order ...) and finally the app itself for the property value.

The Netbeans NbBundle class does similar stuff.

爱格式化 2024-07-15 22:27:28

我遇到了一个稍微类似的问题,请参阅 问题 653682
我找到的解决方案是拥有一个扩展 ResourceBundle 的类,并检查我们是否重写该值(如果不委托给从文件生成的 PropertyResourceBundle)。

因此,如果您想存储 UI 的标签,您将有一个类 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;
    }    

}

I have had a slightly similar problem see question 653682 .
The solution I found was to have a class that extends ResourceBundle and, checks if we are overriding the value if not delegate to the PropertyResourceBundle generated from files.

So if you wanted to store Labels for the UI you would have a class com.example.UILabels and a properties file 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;
    }    

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文