【问题标题】:Locales and ResourceBundles in a plugin-based program基于插件的程序中的语言环境和资源包
【发布时间】:2010-09-21 22:11:51
【问题描述】:

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

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

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

【问题讨论】:

  • 我已将我的答案放在底部,但我很想听听您是如何解决这个问题的。

标签: java internationalization locale resourcebundle


【解决方案1】:

您没有将 ResourceBundles 实现为一系列类,每个语言环境有一个类(即名为 MyProgramStringsMyProgramStrings_frMyProgramStrings_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

至于设置bundle的层次结构,或者将它们“合并”在一起,恐怕我帮不上什么忙,除了我知道Spring确实有hierchical MessageSources的概念(link to 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 实例。如果成功,此实例将成为结果资源包。

【讨论】:

  • 所以他们的属性文件实际上是混合在源之间,因此在类之间,并通过完全限定的路径名​​查找?我刚试过,它奏效了。谢谢,这使它更清晰,可能是解决方案的开始......
  • 是的,这是你可以做到的一种方式——而且可能是最简单的。
  • 对于国际化,我们发现使用外部属性文件更容易,因此我们不必将源文件发送给翻译人员。
【解决方案2】:

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

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

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

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

【讨论】:

  • 我需要澄清一下。我什么时候实例化子类? ResourceBundle.getBundle 如何知道它的存在?
  • ResourceBundle.getBundle() 在类路径中搜索名称与“候选包名称”匹配的类...查看我在上面发布的 ResourceBundle javadoc 链接以获得完整和更完整的解释
【解决方案3】:

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

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

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

Netbeans NbBundle 类做了类似的事情。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2013-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-03
      • 1970-01-01
      相关资源
      最近更新 更多