Java 中文官方教程 2022 版(二十九)(1)https://developer.aliyun.com/article/1486886
区域的范围
Java 平台不要求您在整个程序中使用相同的Locale
。如果愿意,您可以为程序中的每个区域敏感对象分配不同的Locale
。这种灵活性使您能够开发多语言应用程序,可以在多种语言中显示信息。
然而,大多数应用程序不是多语言的,它们的区域敏感对象依赖于默认的Locale
。当 Java 虚拟机启动时由其设置,默认的Locale
对应于主机平台的区域设置。要确定您的 Java 虚拟机的默认Locale
,请调用Locale.getDefault
方法。
注意:
可以独立设置两种用途的默认区域设置:format设置用于格式化资源,display设置用于菜单和对话框。在 Java SE 7 版本中引入的Locale.getDefault(Locale.Category)
方法接受一个Locale.Category
参数。将FORMAT
枚举传递给getDefault(Locale.Category)
方法会返回用于格式化资源的默认区域设置。类似地,传递DISPLAY
枚举会返回 UI 使用的默认区域设置。相应的setDefault(Locale.Category, Locale)
方法允许设置所需类别的区域设置。无参数的getDefault
方法返回DISPLAY
的默认值。
在 Windows 平台上,这些默认值根据 Windows 控制面板中的“标准和格式”和“显示语言”设置进行初始化。
你不应该通过编程方式设置默认的Locale
,因为它被所有区域敏感类共享。
分布式计算引发了一些有趣的问题。例如,假设您正在设计一个应用服务器,该服务器将接收来自各个国家的客户端的请求。如果每个客户端的Locale
不同,那么服务器的Locale
应该是什么?也许服务器是多线程的,每个线程设置为服务的客户端的Locale
。或者也许服务器和客户端之间传递的所有数据都应该是与区域设置无关的。
你应该采取哪种设计方法?如果可能的话,服务器和客户端之间传递的数据应该是与地区无关的。这样可以简化服务器的设计,让客户端负责以地区敏感的方式显示数据。然而,如果服务器必须以特定地区的形式存储数据,这种方法就行不通了。例如,服务器可能会在不同的数据库列中存储相同数据的西班牙语、英语和法语版本。在这种情况下,服务器可能希望查询客户端的Locale
,因为Locale
可能在上次请求之后发生了变化。
区域敏感服务 SPI
原文:
docs.oracle.com/javase/tutorial/i18n/locale/services.html
此功能使得可以插入依赖于区域的数据和服务。通过这种方式,第三方能够提供 java.text
和 java.util
包中大多数区域敏感类的实现。
SPIs (服务提供者接口) 的实现基于由服务提供者实现的抽象类和 Java 接口。在运行时,Java 类加载机制用于动态定位和加载实现 SPI 的类。
您可以使用区域敏感的服务 SPI 来提供以下区域敏感的实现:
BreakIterator
对象Collator
对象Locale
类的语言代码、国家代码和变体名称- 时区名称
- 货币符号
DateFormat
对象DateFormatSymbol
对象NumberFormat
对象DecimalFormatSymbols
对象
相应的 SPI 包含在 java.text.spi
和 java.util.spi
包中:
java.util.spi |
java.text.spi |
|
CurrencyNameProvider
LocaleServiceProvider
TimeZoneNameProvider
CalendarDataProvider
|
BreakIteratorProvider
CollatorProvider
DateFormatProvider
DateFormatSymbolsProvider
DecimalFormatSymbolsProvider
NumberFormatProvider
|
例如,如果您想为新的区域提供一个 NumberFormat
对象,您必须实现 java.text.spi.NumberFormatProvider
类。您需要扩展此类并实现其方法:
getCurrencyInstance(Locale locale)
getIntegerInstance(Locale locale)
getNumberInstance(Locale locale)
getPercentInstance(Locale locale)
Locale loc = new Locale("da", "DK"); NumberFormat nf = NumberFormatProvider.getNumberInstance(loc);
这些方法首先检查 Java 运行时环境是否支持请求的区域;如果支持,则使用该支持。否则,方法调用已安装的提供程序的适当接口的 getAvailableLocales()
方法,以找到支持请求的区域的提供程序。
课程:隔离特定于区域设置的数据
原文:
docs.oracle.com/javase/tutorial/i18n/resbundle/index.html
特定于区域设置的数据必须根据最终用户语言和地区的惯例进行定制。用户界面显示的文本是最明显的区域设置数据示例。例如,在美国的应用程序中,取消按钮将在德国显示为 “Abbrechen” 按钮。在其他国家,此按钮将具有其他标签。显然,您不希望硬编码此按钮标签。如果您可以自动获取给定 Locale
的正确标签,那不是很好吗?幸运的是,只要将特定于区域设置的对象隔离在 ResourceBundle
中,您就可以做到这一点。
在本课程中,您将学习如何创建和访问 ResourceBundle
对象。如果您急于查看一些编码示例,请继续查看本课程中的最后两节。然后您可以回到前两节获取有关 ResourceBundle
对象的一些概念信息。
关于 ResourceBundle 类
ResourceBundle
对象包含特定于区域设置的对象。当您需要特定于区域设置的对象时,您可以从 ResourceBundle
中获取,它会返回与最终用户的 Locale
匹配的对象。本节解释了 ResourceBundle
与 Locale
的关系,并描述了 ResourceBundle
的子类。
准备使用 ResourceBundle
在创建 ResourceBundle
对象之前,您应该进行一些规划。首先,识别程序中特定于区域设置的对象。然后将它们组织成类别,并根据不同的类别存储在不同的 ResourceBundle
对象中。
使用属性文件支持 ResourceBundle
如果您的应用程序包含需要翻译成各种语言的 String
对象,您可以将这些 String
对象存储在 PropertyResourceBundle
中,该对象由一组属性文件支持。由于属性文件是简单的文本文件,可以由翻译人员创建和维护。您无需更改源代码。在本节中,您将学习如何设置支持 PropertyResourceBundle
的属性文件。
使用 ListResourceBundle
ListResourceBundle
类是 ResourceBundle
的子类,使用列表管理特定于区域设置的对象。ListResourceBundle
由一个类文件支持,这意味着每次需要支持额外的 Locale
时,您必须编写和编译一个新的源文件。但是,ListResourceBundle
对象很有用,因为与属性文件不同,它们可以存储任何类型的特定于区域设置的对象。通过逐步执行示例程序,本节演示了如何使用 ListResourceBundle
。
自定义 ResourceBundle 加载
本节代表了改进ResourceBundle.getBundle
工厂灵活性的新功能。ResourceBundle.Control
类与加载资源包的工厂方法合作。这允许将资源包加载过程的每个重要步骤及其缓存控制视为单独的方法。
关于 ResourceBundle 类
原文:
docs.oracle.com/javase/tutorial/i18n/resbundle/concept.html
如何 ResourceBundle 与 Locale 相关联
从概念上讲,每个 ResourceBundle
都是一组相关的子类,共享相同的基本名称。以下列表显示了一组相关的子类。ButtonLabel
是基本名称。基本名称后面的字符表示 Locale
的语言代码、国家代码和变体。例如,ButtonLabel_en_GB
匹配由英语语言代码 (en
) 和英国国家代码 (GB
) 指定的 Locale
。
ButtonLabel ButtonLabel_de ButtonLabel_en_GB ButtonLabel_fr_CA_UNIX
要选择适当的 ResourceBundle
,请调用 ResourceBundle.getBundle
方法。以下示例选择与法语语言、加拿大国家和 UNIX 平台匹配的 ButtonLabel
ResourceBundle
。
Locale currentLocale = new Locale("fr", "CA", "UNIX"); ResourceBundle introLabels = ResourceBundle.getBundle( "ButtonLabel", currentLocale);
如果指定 Locale
的 ResourceBundle
类不存在,getBundle
将尝试找到最接近的匹配项。例如,如果期望的类是 ButtonLabel_fr_CA_UNIX
,默认 Locale
是 en_US
,getBundle
将按以下顺序查找类:
ButtonLabel_fr_CA_UNIX ButtonLabel_fr_CA ButtonLabel_fr ButtonLabel_en_US ButtonLabel_en ButtonLabel
请注意,在选择基类(ButtonLabel
)之前,getBundle
会根据默认 Locale
查找类。如果 getBundle
在前述类列表中找不到匹配项,则会抛出 MissingResourceException
。为避免抛出此异常,您应始终提供没有后缀的基类。
ListResourceBundle 和 PropertyResourceBundle 子类
抽象类 ResourceBundle
有两个子类:PropertyResourceBundle
和 ListResourceBundle
。
PropertyResourceBundle
由属性文件支持。属性文件是包含可翻译文本的纯文本文件。属性文件不是 Java 源代码的一部分,它们只能包含 String
对象的值。如果需要存储其他类型的对象,请改用 ListResourceBundle
。章节 使用属性文件支持 ResourceBundle 展示了如何使用 PropertyResourceBundle
。
ListResourceBundle
类使用方便的列表管理资源。每个 ListResourceBundle
都由一个类文件支持。您可以在 ListResourceBundle
中存储任何特定于区域设置的对象。要为其他 Locale
添加支持,您需要创建另一个源文件并将其编译为类文件。章节 使用 ListResourceBundle 中有一个您可能会发现有用的编码示例。
ResourceBundle
类是灵活的。如果您首先将特定于区域设置的 String
对象放入 PropertyResourceBundle
中,然后稍后决定改用 ListResourceBundle
,则对您的代码没有影响。例如,对 getBundle
的以下调用将检索适当 Locale
的 ResourceBundle
,无论 ButtonLabel
是由类支持还是由属性文件支持:
ResourceBundle introLabels = ResourceBundle.getBundle( "ButtonLabel", currentLocale);
键-值对
ResourceBundle
对象包含一组键值对。当您想要从ResourceBundle
中检索值时,您需要指定键,该键必须是一个String
。该值是特定于区域设置的对象。以下示例中的键是OkKey
和CancelKey
字符串:
class ButtonLabel_en extends ListResourceBundle { // English version public Object[][] getContents() { return contents; } static final Object[][] contents = { {"OkKey", "OK"}, {"CancelKey", "Cancel"}, }; }
要从ResourceBundle
中检索OK
String
,您需要在调用getString
时指定适当的键:
String okLabel = ButtonLabel.getString("OkKey");
属性文件包含键值对。键位于等号的左侧,值位于右侧。每对位于单独的一行。值只能表示String
对象。以下示例显示了名为ButtonLabel.properties
的属性文件的内容:
OkKey = OK CancelKey = Cancel
准备使用 ResourceBundle
原文:
docs.oracle.com/javase/tutorial/i18n/resbundle/prepare.html
识别与地区相关的对象
如果你的应用程序有用户界面,它包含许多与地区相关的对象。要开始,你应该查看源代码,寻找随Locale
变化的对象。你的列表可能包括从以下类实例化的对象:
字符串
图像
颜色
AudioClip
你会注意到这个列表不包含代表数字、日期、时间或货币的对象。这些对象的显示格式随Locale
变化,但对象本身不会变化。例如,你根据Locale
格式化一个Date
,但无论Locale
如何,你都使用相同的Date
对象。你不需要将这些对象隔离在ResourceBundle
中,而是使用特殊的区域敏感格式化类对它们进行格式化。你将在日期和时间部分的格式化课程中学习如何做到这一点。
通常情况下,存储在ResourceBundle
中的对象是预定义的,并随产品一起提供。这些对象在程序运行时不会被修改。例如,你应该将Menu
标签存储在ResourceBundle
中,因为它是与地区相关的,在程序会话期间不会更改。然而,你不应该将用户在TextField
中输入的String
对象隔离在ResourceBundle
中。这样的String
数据可能每天都会有所变化。它是特定于程序会话的,而不是程序运行的Locale
。
通常,你需要在ResourceBundle
中隔离的大多数对象都是String
对象。然而,并非所有的String
对象都是与地区相关的。例如,如果一个String
是进程间通信使用的协议元素,它就不需要本地化,因为最终用户永远不会看到它。
是否本地化某些String
对象的决定并不总是明确的。日志文件是一个很好的例子。如果一个日志文件由一个程序编写并由另一个程序读取,那么两个程序都将使用日志文件作为通信的缓冲区。假设最终用户偶尔检查此日志文件的内容。那么日志文件应该本地化吗?另一方面,如果最终用户很少检查日志文件,则翻译的成本可能不值得。你是否本地化此日志文件的决定取决于许多因素:程序设计、易用性、翻译成本和可支持性。
组织 ResourceBundle 对象
你可以根据包含的对象的类别组织你的ResourceBundle
对象。例如,你可能希望将订单输入窗口的所有 GUI 标签加载到一个名为OrderLabelsBundle
的ResourceBundle
中。使用多个ResourceBundle
对象有几个优点:
- 你的代码更易于阅读和维护。
- 你将避免巨大的
ResourceBundle
对象,这可能需要太长时间加载到内存中。 - 当需要时,您可以通过仅加载每个
ResourceBundle
来减少内存使用量。
使用属性文件支持 ResourceBundle
原文:
docs.oracle.com/javase/tutorial/i18n/resbundle/propfile.html
本节将逐步介绍一个名为PropertiesDemo
的示例程序。
1. 创建默认属性文件
属性文件是一个简单的文本文件。您可以使用几乎任何文本编辑器创建和维护属性文件。
您应该始终创建一个默认属性文件。此文件的名称以您的ResourceBundle
的基本名称开头,并以.properties
后缀结尾。在PropertiesDemo
程序中,基本名称为LabelsBundle
。因此,默认属性文件称为LabelsBundle.properties
。此文件包含以下行:
# This is the default LabelsBundle.properties file s1 = computer s2 = disk s3 = monitor s4 = keyboard
请注意,在前面的文件中,注释行以井号(#)开头。其他行包含键值对。键位于等号的左侧,值位于右侧。例如,s2
是对应于值disk
的键。键是任意的。我们可以将s2
命名为其他名称,比如msg5
或diskID
。但一旦定义,键就不应更改,因为它在源代码中被引用。值可以更改。实际上,当您的本地化人员创建新的属性文件以适应其他语言时,他们将把值翻译成各种语言。
2. 根据需要创建其他属性文件
要支持额外的Locale
,您的本地化人员将创建一个包含翻译值的新属性文件。不需要更改源代码,因为您的程序引用键,而不是值。
例如,要添加对德语的支持,您的本地化人员将翻译LabelsBundle.properties
中的值,并将其放入名为LabelsBundle_de.properties
的文件中。请注意,此文件的名称与默认文件的名称相同,以基本名称LabelsBundle
开头,并以.properties
后缀结尾。但是,由于此文件用于特定的Locale
,因此基本名称后面跟着语言代码(de
)。LabelsBundle_de.properties
的内容如下:
# This is the LabelsBundle_de.properties file s1 = Computer s2 = Platte s3 = Monitor s4 = Tastatur
PropertiesDemo
示例程序附带三个属性文件:
LabelsBundle.properties LabelsBundle_de.properties LabelsBundle_fr.properties
3. 指定 Locale
PropertiesDemo
程序创建Locale
对象如下:
Locale[] supportedLocales = { Locale.FRENCH, Locale.GERMAN, Locale.ENGLISH };
这些Locale
对象应该与前两个步骤中创建的属性文件相匹配。例如,Locale.FRENCH
对象对应于LabelsBundle_fr.properties
文件。Locale.ENGLISH
没有匹配的LabelsBundle_en.properties
文件,因此将使用默认文件。
4. 创建 ResourceBundle
此步骤展示了Locale
、属性文件和ResourceBundle
之间的关系。要创建ResourceBundle
,请调用getBundle
方法,指定基本名称和Locale
:
ResourceBundle labels = ResourceBundle.getBundle("LabelsBundle", currentLocale);
getBundle
方法首先查找与基本名称和Locale
匹配的类文件。如果找不到类文件,则会检查属性文件。在PropertiesDemo
程序中,我们使用属性文件而不是类文件来支持ResourceBundle
。当getBundle
方法找到正确的属性文件时,它会返回一个包含属性文件中键值对的PropertyResourceBundle
对象。
5. 获取本地化文本
要从ResourceBundle
中检索翻译后的值,请按照以下方式调用getString
方法:
String value = labels.getString(key);
getString
返回的String
对应指定的键。只要为指定的Locale
存在属性文件,该String
就是正确的语言。
6. 遍历所有键
这一步是可选的。在调试程序时,您可能希望获取ResourceBundle
中所有键的值。getKeys
方法返回ResourceBundle
中所有键的Enumeration
。您可以遍历Enumeration
并使用getString
方法获取每个值。以下代码片段来自PropertiesDemo
程序,展示了如何实现这一点:
ResourceBundle labels = ResourceBundle.getBundle("LabelsBundle", currentLocale); Enumeration bundleKeys = labels.getKeys(); while (bundleKeys.hasMoreElements()) { String key = (String)bundleKeys.nextElement(); String value = labels.getString(key); System.out.println("key = " + key + ", " + "value = " + value); }
7. 运行演示程序
运行PropertiesDemo
程序会生成以下输出。前三行显示了对各种Locale
对象调用getString
返回的值。当使用getKeys
方法遍历键时,程序会显示最后四行。
Locale = fr, key = s2, value = Disque dur Locale = de, key = s2, value = Platte Locale = en, key = s2, value = disk key = s4, value = Clavier key = s3, value = Moniteur key = s2, value = Disque dur key = s1, value = Ordinateur
使用 ListResourceBundle
本节演示了使用 ListResourceBundle
对象的示例程序 ListDemo
。接下来的文本解释了创建 ListDemo
程序所涉及的每个步骤,以及支持它的 ListResourceBundle
子类。
1. 创建 ListResourceBundle 子类
ListResourceBundle
由类文件支持。因此,第一步是为每个支持的 Locale
创建一个类文件。在 ListDemo
程序中,ListResourceBundle
的基本名称是 StatsBundle
。由于 ListDemo
支持三个 Locale
对象,因此需要以下三个类文件:
StatsBundle_en_CA.class StatsBundle_fr_FR.class StatsBundle_ja_JP.class
为日本定义的 StatsBundle
类在接下来的源代码中定义。请注意,类名是通过将语言和国家代码附加到 ListResourceBundle
的基本名称构建的。在类内部,二维 contents
数组使用键值对进行初始化。键是每对中的第一个元素:GDP
、Population
和 Literacy
。键必须是 String
对象,并且在 StatsBundle
集合中的每个类中必须相同。值可以是任何类型的对象。在此示例中,值是两个 Integer
对象和一个 Double
对象。
import java.util.*; public class StatsBundle_ja_JP extends ListResourceBundle { public Object[][] getContents() { return contents; } private Object[][] contents = { { "GDP", new Integer(21300) }, { "Population", new Integer(125449703) }, { "Literacy", new Double(0.99) }, }; }
2. 指定 Locale
ListDemo
程序如下定义 Locale
对象:
Locale[] supportedLocales = { new Locale("en", "CA"), new Locale("ja", "JP"), new Locale("fr", "FR") };
每个 Locale
对象对应于一个 StatsBundle
类。例如,用 ja
和 JP
代码定义的日语 Locale
与 StatsBundle_ja_JP.class
匹配。
3. 创建 ResourceBundle
要创建 ListResourceBundle
,请调用 getBundle
方法。以下代码行指定了类的基本名称(StatsBundle
)和 Locale
:
ResourceBundle stats = ResourceBundle.getBundle("StatsBundle", currentLocale);
getBundle
方法搜索以 StatsBundle
开头,后跟指定 Locale
的语言和国家代码的类。如果 currentLocale
是用 ja
和 JP
代码创建的,getBundle
将返回与类 StatsBundle_ja_JP
对应的 ListResourceBundle
,例如。
4. 获取本地化对象
现在程序有了适当 Locale
的 ListResourceBundle
,它可以通过其键获取本地化对象。以下代码行通过使用 Literacy
键参数调用 getObject
来检索识字率。由于 getObject
返回一个对象,请将其转换为 Double
:
Double lit = (Double)stats.getObject("Literacy");
5. 运行演示程序
ListDemo
程序打印了使用 getBundle
方法获取的数据:
Locale = en_CA GDP = 24400 Population = 28802671 Literacy = 0.97 Locale = ja_JP GDP = 21300 Population = 125449703 Literacy = 0.99 Locale = fr_FR GDP = 20200 Population = 58317450 Literacy = 0.99
自定义资源包加载
原文:
docs.oracle.com/javase/tutorial/i18n/resbundle/control.html
在本课程的前面,您已经学会了如何创建和访问ResourceBundle
类的对象。本节扩展了您的知识,并解释了如何利用ResourceBundle.Control
类的功能。
ResourceBundle.Control
被创建用于指定如何定位和实例化资源包。它定义了一组回调方法,这些方法在ResourceBundle.getBundle
工厂方法在加载资源包过程中调用。
与之前描述的ResourceBundle.getBundle
方法不同,此ResourceBundle.getBundle
方法使用指定的基本名称、默认区域设置和指定的控制定义资源包。
public static final ResourceBundle getBundle( String baseName, ResourceBundle.Control cont // ...
指定的控制提供了资源包加载过程的信息。
下面的示例程序RBControl.java
说明了如何为中文区域定义自己的搜索路径。
1. 创建properties
文件。
正如之前所述,您可以从类或properties
文件中加载资源。这些文件包含以下区域的描述:
RBControl.properties
– 全局RBControl_zh.properties
– 仅语言:简体中文RBControl_zh_cn.properties
– 仅区域:中国RBControl_zh_hk.properties
– 仅区域:香港RBControl_zh_tw.properties
– 台湾
在此示例中,应用程序为香港地区创建了一个新的区域设置。
2. 创建一个ResourceBundle
实例。
与上一节中的示例一样,此应用程序通过调用getBundle
方法创建了一个ResourceBundle
实例:
private static void test(Locale locale) { ResourceBundle rb = ResourceBundle.getBundle( "RBControl", locale, new ResourceBundle.Control() { // ... } );
getBundle
方法搜索具有 RBControl 前缀的properties
文件。然而,此方法包含一个Control
参数,用于驱动搜索中文区域的过程。
3. 调用getCandidateLocales
方法
getCandidateLocales
方法返回一个候选区域的Locales
对象列表,用于基本名称和区域设置。
new ResourceBundle.Control() { @Override public List<Locale> getCandidateLocales( String baseName, Locale locale) { // ... } }
默认实现返回以下Locale
对象的列表:Locale(语言, 国家)。
然而,此方法被覆盖以实现以下特定行为:
if (baseName == null) throw new NullPointerException(); if (locale.equals(new Locale("zh", "HK"))) { return Arrays.asList( locale, Locale.TAIWAN, // no Locale.CHINESE here Locale.ROOT); } else if (locale.equals(Locale.TAIWAN)) { return Arrays.asList( locale, // no Locale.CHINESE here Locale.ROOT); }
注意,候选区域序列的最后一个元素必须是根区域。
4. 调用test
类
为以下四种不同的区域设置调用test
类:
public static void main(String[] args) { test(Locale.CHINA); test(new Locale("zh", "HK")); test(Locale.TAIWAN); test(Locale.CANADA); }
5. 运行示例程序
你将看到程序的输出如下:
locale: zh_CN region: China language: Simplified Chinese locale: zh_HK region: Hong Kong language: Traditional Chinese locale: zh_TW region: Taiwan language: Traditional Chinese locale: en_CA region: global language: English
请注意,新创建的区域被分配为香港地区,因为在适当的properties
文件中指定了。繁体中文被分配为台湾区域的语言。
ResourceBundle.Control
类的另外两个有趣的方法没有在RBControl
示例中使用,但值得一提。getTimeToLive
方法用于确定资源包在缓存中可以存在多长时间。如果缓存中资源包的时间限制已过期,则调用needsReload
方法来确定是否需要重新加载资源包。
教训:格式化
本课程解释了如何格式化数字、货币、日期、时间和文本消息。因为最终用户可以看到这些数据元素,它们的格式必须符合各种文化习俗。遵循本课程中的示例将教会您如何:
- 以区域设置敏感的方式格式化数据元素
- 保持您的代码与区域设置无关
- 避免为特定区域编写格式化程序的需要
Java 中文官方教程 2022 版(二十九)(3)https://developer.aliyun.com/article/1486894