我们需要开始为我们的计划添加国际化。值得庆幸的是,不仅仅是一点点,但我希望我们这样做的方式可以扩展到可能覆盖整个程序。问题是,我们的程序基于插件,因此并非所有字符串都属于同一个地方。
据我了解,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");
但是,我们需要的是将多个语言环境的结果合并到一个资源空间中。例如,插件需要能够覆盖已定义的字符串,并在代码查找字符串时返回新值。
我在这一切中有点迷失。有人可以帮忙吗?
更新:David Waters问道:
我已将答案放在最底层,但我很想知道你是如何解决这个问题的。
好吧,我们还没有走得太远 - 长期的WIBNI总是成为最新危机的受害者 - 但我们的基础是插件实现的接口,资源具有相同的完全限定名称的约定作为界面。
因此,接口UsersAPI
可能有各种不同的实现。默认情况下,该接口上的方法getBundle()
返回等效的ResourceBundle.get("...UsersAPI", locale)
。该文件可以替换,或者UsersAPI的实现可以覆盖该方法,如果他们需要更复杂的东西。
到目前为止,我们需要的是,但我们仍然在寻找基于插件的更灵活的解决方案。
答案 0 :(得分:2)
你没有拥有来将ResourceBundles实现为一系列类,每个语言环境有一个类(即一个名为MyProgramStrings
,MyProgramStrings_fr
,{{1}的类})。如果需要,ResourceBundle类将回退到使用属性文件:
MyProgramStrings_de
如果我在类路径上有一个名为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
至于设置捆绑包的层次结构,或者将它们“合并”在一起,我恐怕在那里帮不了多少,除了我知道Spring确实有hierchical MessageSources的概念({{ 3}})在java.util.ResourceBundle之上实现,所以也许你可以使用Spring的功能来实现你想要的东西?
顺便说一句,这是got bundle: java.util.PropertyResourceBundle@42e816
Value in bundle is: someValue
javadoc的相关部分,它解释了它的“搜索和实例化策略”:
link to API,java.util.Locale,java.lang.ClassLoader)
- 首先,它尝试使用候选包名称加载类。如果可以使用指定的类加载器找到并加载这样的类,与ResourceBundle兼容的赋值,可从ResourceBundle访问,并且可以实例化,则getBundle创建此类的新实例并将其用作结果资源包。
- 否则,getBundle会尝试查找属性资源文件。它通过替换所有“。”从候选包名称生成路径名。带有“/”的字符并附加字符串“.properties”。它尝试使用ClassLoader.getResource查找具有此名称的“资源”。 (请注意,getResource意义上的“资源”与资源包的内容无关,它只是数据的容器,例如文件。)如果找到“资源”,它会尝试创建来自其内容的新PropertyResourceBundle实例。如果成功,则此实例将成为结果资源包。
答案 1 :(得分:1)
我知道Struts和Spring有相关的东西。但是,假设您不能使用Struts或Spring,那么我要做的是创建ResourceBundle的子类并在此ResourceBundle中加载* .properties(每个插件一个)。然后你可以使用
ResourceBundle bundle = ResourceBundle.getBundle(“MyResources”);
就像通常使用属性文件一样。
当然,您应该缓存结果,这样您就不必每次都搜索每个属性。
答案 2 :(得分:1)
getBundle加载一组使用指定语言环境(如果有)和默认语言环境生成的候选包文件。
通常,没有语言环境信息的资源文件具有“默认”值(例如,可能en_US
值在MyResources.properties中),然后为不同的语言环境创建过度资源值(例如{{1 }}字符串在MyResources_ja.properties中。更具体的文件中的任何资源都会覆盖不太具体的文件属性。
现在,您希望为每个插件添加能力,以提供自己的属性文件集。目前尚不清楚插件是否能够修改主应用程序或其他插件的资源,但听起来你想拥有一个单例类,其中每个插件都可以注册其基本资源文件名。所有对属性值的请求都会通过该单例,然后查看每个插件的资源包(按某种顺序......),最后查看应用程序本身的属性值。
Netbeans NbBundle类也做类似的事情。
答案 3 :(得分:1)
我遇到了类似的问题,请参阅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;
}
}