ResourceBundle 指南
1.概述
许多软件开发人员,在他们的职业生涯中,面临着开发多语言系统或应用程序的机会。这些通常是为来自不同地区或不同语言领域的终端用户准备的。
维护和扩展这些应用程序总是具有挑战性。能够同时操作各种特定的本地化数据通常是至关重要的。对应用程序数据的修改应该尽可能简单,不需要重新编译。这就是为什么我们通常避免硬编码标签或按钮名称。
幸运的是,我们可以依靠Java为我们提供的这个类,它可以帮助我们解决上面提到的所有问题。
简单地说,ResourceBundle使我们的应用程序能够从包含本地特定数据的不同文件中加载数据。
1.1. 资源包
我们应该知道的第一件事是,一个资源包中的所有文件必须在同一个包/目录中,并且有一个共同的基本名称。它们可能有特定于本地的后缀,表示语言、国家或平台,用下划线符号分开。
重要的是,如果已经有了语言代码,我们可以追加国家代码,如果有了语言和国家代码,我们也可以追加平台。
让我们看一下文件名的例子:
- ExampleResource
- ExampleResource_en
- ExampleResource_en_US
- ExampleResource_en_US_UNIX
每个数据包的默认文件始终是一个没有任何后缀的文件 – ExampleResource。由于有两个ResourceBundle的子类:PropertyResourceBundle和ListResourceBundle,我们可以在属性文件和java文件中互换地保存数据。
每个文件都必须有特定于语言的名称和适当的文件扩展名,例如,ExampleResource_en_US.properties或Example_en.java。
1.2.属性文件 – PropertyResourceBundle
属性文件由PropertyResourceBundle表示,它们以区分大小写的键值对的形式存储数据。
让我们来分析一个属性文件的样本:
# Buttons
continueButton continue
cancelButton=cancel
! Labels
helloLabel:hello
正如我们所看到的,有三种不同的定义键值对的风格。
所有这些都是等价的,但第一个可能是Java程序员中最流行的。值得一提的是,我们也可以在属性文件中加入注释。注释总是以#或!开头。
1.3. java文件 –ListResourceBundle
首先,为了存储我们特定的语言数据,我们需要创建一个扩展ListResourceBundle并重写getContents()方法的类。类的名称约定与属性文件相同。
对于每一个Locale,,我们都需要创建单独的Java类。
这是一个示例类:
public class ExampleResource_pl_PL extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{"currency", "polish zloty"},
{"toUsdRate", new BigDecimal("3.401")},
{"cities", new String[] { "Warsaw", "Cracow" }}
};
}
}
Java文件与属性文件相比有一个主要的优势,那就是可以容纳任何我们想要的对象 – 而不仅仅是字符串的对象。
另一方面,每次修改或引入一个新的特定于本地的java类都需要重新编译应用程序,而属性文件可以在不需要任何额外努力的情况下进行扩展。
2.使用资源包
我们已经知道如何定义资源捆绑,所以我们已经准备好使用它了。
让我们考虑一下这个简短的代码片段:
Locale locale = new Locale("pl", "PL");
ResourceBundle exampleBundle = ResourceBundle.getBundle("package.ExampleResource", locale);
assertEquals(exampleBundle.getString("currency"), "polish zloty");
assertEquals(exampleBundle.getObject("toUsdRate"), new BigDecimal("3.401"));
assertArrayEquals(exampleBundle.getStringArray("cities"), new String[]{"Warsaw", "Cracow"});
首先,我们可以定义我们的Locale,除非我们不想使用默认的Locale。
之后,让我们调用ResourceBundle的一个静态工厂方法。我们需要把捆绑包的名称和它的包/目录以及locale作为参数传给它。
还有一个工厂方法,如果默认的locale没有问题,它只需要一个bundle名称。一旦我们有了这个对象,我们就可以通过它们的键来检索值了。
此外,这个例子显示,我们可以使用getString(String key)、getObject(String key)、和getStringArray(String key)来获得我们想要的值。
3.选择适当的捆绑资源
如果我们想使用捆绑资源,那么了解Java如何选择捆绑文件就很重要了。
让我们想象一下,我们工作的应用程序需要波兰语的标签,但你的默认JVM地区设置是Locale.US。
开始时,应用程序将在classpath中寻找适合你要求的locale的文件。它从最具体的名称开始,即包含一个平台、一个国家和语言的名称。
然后,它转到更一般的地方。如果没有匹配,它就会返回到默认的语言环境,这次没有平台检查。
如果没有匹配,它将尝试读取默认的捆绑文件。当我们看一下所选文件名的顺序时,一切都应该很清楚:
- Label_pl_PL_UNIX
- Label_pl_PL
- Label_pl
- Label_en_US
- Label_en
- Label
我们应该记住,每个名字都代表.java和.properties文件,但前者优先于后者。当没有合适的文件时,会抛出一个MissingResourceException。
4.继承
资源包概念的另一个优点是属性继承。这意味着包含在不太具体的文件中的键值对被那些在继承树中较高的文件所继承。
让我们假设我们有三个属性文件:
#resource.properties
cancelButton = cancel
#resource_pl.properties
continueButton = dalej
#resource_pl_PL.properties
backButton = cofnij
为Locale(“pl”, “PL”)检索的资源包将在结果中返回所有三个键/值。值得一提的是,就属性继承而言,没有退回到默认的locale bundle。
此外,ListResourceBundles 和 PropertyResourceBundles 不在同一层次结构中。
因此,如果在classpath中找到一个属性文件,那么键值对就只能从属性文件中继承。这条规则也适用于Java文件。
5.定制
上面我们所学的都是关于ResourceBundle的默认实现。但是,我们有办法修改它的行为。
我们通过扩展ResourceBoundle.Control并重写其方法来做到这一点。
例如,我们可以改变在缓存中保留数值的时间,或者确定在什么情况下应该重新加载缓存。
为了更好地理解,让我们准备一个简短的方法作为例子:
public class ExampleControl extends ResourceBundle.Control {
@Override
public List<Locale> getCandidateLocales(String s, Locale locale) {
return Arrays.asList(new Locale("pl", "PL"));
}
}
这个方法的目的是改变一种在classpath中选择文件的方式。我们可以看到,ExampleControl将只返回优化的Locale,不管默认或定义的Locale是什么。
6.UTF-8
由于仍有许多应用程序使用JDK 8或旧版本,因此值得一提的是,在Java 9 ListResourceBundles比PropertyResourceBundles还有一个优势。由于Java文件可以存储字符串对象,它们能够容纳UTF-16编码所支持的任何字符。
相反,PropertyResourceBundle默认使用ISO 8859-1编码加载文件,该编码的字符数比UTF-8少(对我们的波兰语例子造成了问题)。
为了保存超出UTF-8的字符,我们可以使用Native-To-ASCII转换器 – native2ascii。它通过将所有不符合ISO 8859-1的字符编码为\uxxxx符号来进行转换。
下面是一个命令的例子:
native2ascii -encoding UTF-8 utf8.properties nonUtf8.properties
让我们看看改变编码前后的属性是什么样子的:
#Before
polishHello=cześć
#After
polishHello=cze\u015b\u0107
幸运的是,这种情况在Java 9中不再存在了。JVM以UTF-8编码读取属性文件,使用非拉丁字符也没有问题。
7.结语
BundleResource包含了我们开发一个多语言应用程序所需的大部分内容。我们所涉及的功能使得对不同地区的操作变得非常简单。
我们还避免了硬编码值,允许我们通过简单地添加新的Locale文件来扩展支持的Locales,使我们的应用程序能够顺利地被修改和维护。
一如既往,样本代码可在GitHub上找到。