4

我们需要开始为我们的程序添加国际化。谢天谢地,还不是全部,只是一点点,但我希望我们这样做的方式可以扩大到可能覆盖整个程序。问题是,我们的程序是基于插件的,所以并不是所有的字符串都属于同一个地方。

据我了解,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 的实现可以覆盖该方法。

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

4

4 回答 4

2

您不必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 确实有一个在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 实例。如果成功,此实例将成为结果资源包。
于 2008-11-12T03:21:33.263 回答
1

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

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

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

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

于 2008-11-12T03:30:15.787 回答
1

getBundle 加载使用指定语言环境(如果有)和默认语言环境生成的一组候选捆绑文件。

通常,没有语言环境信息的资源文件具有“默认”值(例如,en_US值可能在 MyResources.properties 中),然后为不同的语言环境创建覆盖资源值(例如ja,字符串在 MyResources_ja.properties 中)更具体的文件会覆盖不太具体的文件属性。

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

Netbeans NbBundle 类做类似的事情。

于 2008-11-12T03:38:47.783 回答
1

我有一个稍微类似的问题,请参阅问题 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;
    }    

}
于 2009-03-18T09:33:51.853 回答