原文:
docs.oracle.com/javase/tutorial/reallybigindex.html
BCP 47 扩展
原文:
docs.oracle.com/javase/tutorial/i18n/locale/extensions.html
Java SE 7 版本符合 IETF BCP 47 标准,支持向Locale
添加扩展。任何单个字符都可以用于表示扩展,但有两个预定义的扩展代码:'u'
指定Unicode 语言环境扩展,'x'
指定私有使用扩展。
Unicode 语言环境扩展由 Unicode通用语言环境数据存储库(CLDR)项目定义。它们用于指定非语言特定的信息,例如日历或货币。私有使用扩展可用于指定任何其他信息,例如平台(例如 Windows、UNIX 或 Linux)或发布信息(例如 6u23 或 JDK 7)。
扩展被指定为键/值对,其中键是单个字符(通常为'u'
或'x'
)。格式良好的值具有以下格式:
SUBTAG ('-' SUBTAG)*
在这种格式中:
SUBTAG = [0-9a-zA-Z]{2,8} *For key='u'*
SUBTAG = [0-9a-zA-Z]{1,8} *For key='x'*
请注意,私有使用扩展允许单个字符值。但是,Unicode 语言环境扩展中的值至少需要 2 个字符。
扩展字符串不区分大小写,但Locale
类将所有键和值映射为小写。
getExtensionKeys()
方法返回Locale
的(如果有的话)扩展键集。getExtension(key)
方法返回指定键的值字符串(如果有)。
Unicode 语言环境扩展
如前所述,Unicode 语言环境扩展由'u'
键代码或UNICODE_LOCALE_EXTENSION
常量指定。值本身也由键/类型对指定。合法值在键/类型定义表中在Unicode网站上定义。键代码由两个字母字符指定。以下表列出了 Unicode 语言环境扩展键:
键代码 | 描述 |
---|---|
ca | 日历算法 |
co | 排序类型 |
ka | 排序参数 |
cu | 货币类型 |
nu | 数字类型 |
va | 通用变体类型 |
注意:
指定 Unicode 语言环境扩展,例如数字格式,并不保证底层平台的语言环境服务会遵守该请求。
以下表格显示了 Unicode 语言环境扩展的一些键/类型对示例。
键/类型对 | 描述 |
---|---|
ca-buddhist | 泰国佛教日历 |
co-pinyin | 拉丁文拼音排序 |
cu-usd | 美元 |
nu-jpanfin | 日本金融数字 |
tz-aldav | Europe/Andorra |
以下字符串代表德国语言区域设置,使用 Linux 平台的电话簿样式排序。此示例还包含一个名为"email"
的属性。
de-DE-u-email-co-phonebk-x-linux
可以使用以下Locale
方法访问有关 Unicode 区域扩展的信息。这些方法使用前面的德语区域示例进行描述。
-
getUnicodeLocaleKeys()
– 返回 Unicode 区域键代码或空集(如果区域设置没有)。对德语示例,这将返回一个包含单个字符串"co"
的集合。 -
getUnicodeLocaleType(String)
– 返回与 Unicode 区域键代码关联的 Unicode 区域类型。对德语示例调用getUnicodeLocaleType("co")
将返回字符串"phonebk"
。 -
getUnicodeLocaleAttributes()
– 返回与此区域设置关联的 Unicode 区域设置属性集(如果有的话)。在德语示例中,这将返回一个包含单个字符串"email"
的集合。
私有使用扩展
私有使用扩展,由'x'
键代码或PRIVATE_USE_EXTENSION
常量指定,可以是任何值,只要值格式正确即可。
以下是可能的私有使用扩展示例:
x-jdk-1-7
x-linux
可用区域设置的识别
原文:
docs.oracle.com/javase/tutorial/i18n/locale/identify.html
您可以使用任何有效的语言和国家代码组合创建Locale
,但这并不意味着您可以使用它。请记住,Locale
对象只是一个标识符。您将Locale
对象传递给其他对象,然后这些对象才会执行实际工作。这些其他对象,我们称之为区域敏感对象,不知道如何处理所有可能的Locale
定义。
要找出区域敏感类识别的哪些类型的Locale
定义,您可以调用getAvailableLocales
方法。例如,要找出DateFormat
类支持哪些Locale
定义,您可以编写以下类似的例程:
import java.util.*;
import java.text.*;
public class Available {
static public void main(String[] args) {
Locale list[] = DateFormat.getAvailableLocales();
for (Locale aLocale : list) {
System.out.println(aLocale.toString());
}
}
}
请注意,toString
返回的String
包含由下划线分隔的语言和国家代码:
ar_EG
be_BY
bg_BG
ca_ES
cs_CZ
da_DK
de_DE
...
如果您想向最终用户显示Locale
名称列表,您应该向他们显示比toString
返回的语言和国家代码更容易理解的内容。相反,您可以调用Locale.getDisplayName
方法,该方法检索Locale
对象的本地化String
。例如,当在前面的代码中用getDisplayName
替换toString
时,程序会打印以下行:
Arabic (Egypt)
Belarussian (Belarus)
Bulgarian (Bulgaria)
Catalan (Spain)
Czech (Czech Republic)
Danish (Denmark)
German (Germany)
...
根据 Java 平台的不同实现,您可能会看到不同的区域设置列表。
语言标签过滤和查找
原文:
docs.oracle.com/javase/tutorial/i18n/locale/matching.html
Java 编程语言包含对语言标签、语言标签过滤和语言标签查找的国际化支持。这些功能由IETF BCP 47规定,其中包括RFC 5646“用于标识语言的标签”和RFC 4647“语言标签的匹配”。本课程描述了 JDK 中提供这种支持的方式。
什么是语言标签?
语言标签是专门格式化的字符串,提供有关特定语言的信息。语言标签可能是简单的(如“en”表示英语)、复杂的(如“zh-cmn-Hans-CN”表示中国普通话,简体字,用于中国)或介于两者之间的(如“sr-Latn”,表示使用拉丁文写的塞尔维亚语)。语言标签由连字符分隔的“子标签”组成;这个术语在整个 API 文档中使用。
java.util.Locale
类提供对语言标签的支持。Locale
包含几个不同的字段:语言(如“en”表示英语,或“ja”表示日语)、脚本(如“Latn”表示拉丁文或“Cyrl”表示西里尔文)、国家(如“US”表示美国或“FR”表示法国)、变体(指示区域的某些变体)和扩展(提供单字符键到String
值的映射,表示除语言标识之外的扩展)。要从语言标签String
创建Locale
对象,请调用Locale.forLanguageTag(String)
,将语言标签作为唯一参数传入。这样做会创建并返回一个新的Locale
对象,供应用程序使用。
示例 1:
package languagetagdemo;
import java.util.Locale;
public class LanguageTagDemo {
public static void main(String[] args) {
Locale l = Locale.forLanguageTag("en-US");
}
}
请注意,Locale API 只要求您的语言标签在语法上是格式良好的。它不执行任何额外的验证(例如检查标签是否在 IANA 语言子标签注册表中注册)。
什么是语言范围?
语言范围(由类java.util.Locale.LanguageRange
表示)标识具有特定属性的语言标签集。语言范围分为基本和扩展两类,类似于语言标签,由连字符分隔的子标签组成。基本语言范围的示例包括“en”(英语)、“ja-JP”(日语,日本)和“”(特殊语言范围,匹配任何语言标签)。扩展语言范围的示例包括“-CH”(任何语言,瑞士)、“es-”(西班牙语,任何地区)和“zh-Hant-”(繁体中文,任何地区)。
此外,语言范围可以存储在语言优先级列表中,这使用户可以在加权列表中优先考虑他们的语言偏好。语言优先级列表通过将LanguageRange
对象放入java.util.List
中来表示,然后可以将其传递给接受List
的LanguageRange
对象的Locale
方法。
创建语言范围
Locale.LanguageRange
类提供了两种不同的构造函数来创建语言范围:
-
public Locale.LanguageRange(String range)
-
public Locale.LanguageRange(String range, double weight)
它们之间唯一的区别是第二个版本允许指定权重;如果将范围放入语言优先级列表中,则将考虑此权重。
Locale.LanguageRange
还指定了一些常量,用于与这些构造函数一起使用:
-
public static final double MAX_WEIGHT
-
public static final double MIN_WEIGHT
MAX_WEIGHT
常量保存值 1.0,表示它非常适合用户。MIN_WEIGHT
常量保存值 0.0,表示它不适合。
示例 2:
package languagetagdemo;
import java.util.Locale;
public class LanguageTagDemo {
public static void main(String[] args) {
// Create Locale
Locale l = Locale.forLanguageTag("en-US");
// Define Some LanguageRange Objects
Locale.LanguageRange range1 = new Locale.LanguageRange("en-US",Locale.LanguageRange.MAX_WEIGHT);
Locale.LanguageRange range2 = new Locale.LanguageRange("en-GB*",0.5);
Locale.LanguageRange range3 = new Locale.LanguageRange("fr-FR",Locale.LanguageRange.MIN_WEIGHT);
}
}
示例 2 创建了三种语言范围:英语(美国)、英语(英国)和法语(法国)。这些范围被加权以表达用户的偏好,从最喜欢到最不喜欢的顺序。
创建语言优先级列表
您可以使用LanguageRange.parse(String)
方法从语言范围列表创建语言优先级列表。此方法接受一个逗号分隔的语言范围列表,在给定范围中对每个语言范围执行语法检查,然后返回新创建的语言优先级列表。
有关“ranges”参数所需格式的详细信息,请参阅此方法的 API 规范。
示例 3:
package languagetagdemo;
import java.util.Locale;
import java.util.List;
public class LanguageTagDemo {
public static void main(String[] args) {
// Create Locale
Locale l = Locale.forLanguageTag("en-US");
// Create a Language Priority List
String ranges = "en-US;q=1.0,en-GB;q=0.5,fr-FR;q=0.0";
List<Locale.LanguageRange> languageRanges = Locale.LanguageRange.parse(ranges)
}
}
示例 3 创建了与示例 2 相同的三种语言范围,但将它们存储在一个String
对象中,该对象被传递给parse(String)
方法。返回的LanguageRange
对象的List
是语言优先级列表。
过滤语言标签
语言标签过滤是将一组语言标签与用户的语言优先级列表进行匹配的过程。过滤的结果将是所有匹配结果的完整列表。Locale
类定义了两个过滤方法,它们返回一个Locale
对象的列表。它们的签名如下:
-
public static List<Locale> filter (List<Locale.LanguageRange> priorityList, Collection<Locale> locales)
-
public static List<Locale> filter (List<Locale.LanguageRange> priorityList, Collection<Locale> locales, Locale.FilteringMode mode)
在这两种方法中,第一个参数指定了用户的语言优先级列表,如前一节所述。
第二个参数指定要匹配的Locale
对象的Collection
。匹配本身将根据 RFC 4647 指定的规则进行。
第三个参数(如果提供)指定要使用的“过滤模式”。Locale.FilteringMode
枚举提供了许多不同的值可供选择,例如AUTOSELECT_FILTERING
(用于基本语言范围过滤)或EXTENDED_FILTERING
(用于扩展语言范围过滤)。
示例 4 演示了语言标记过滤。
示例 4:
package languagetagdemo;
import java.util.Locale;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class LanguageTagDemo {
public static void main(String[] args) {
// Create a collection of Locale objects to filter
Collection<Locale> locales = new ArrayList<>();
locales.add(Locale.forLanguageTag("en-GB"));
locales.add(Locale.forLanguageTag("ja"));
locales.add(Locale.forLanguageTag("zh-cmn-Hans-CN"));
locales.add(Locale.forLanguageTag("en-US"));
// Express the user's preferences with a Language Priority List
String ranges = "en-US;q=1.0,en-GB;q=0.5,fr-FR;q=0.0";
List<Locale.LanguageRange> languageRanges = Locale.LanguageRange.parse(ranges);
// Now filter the Locale objects, returning any matches
List<Locale> results = Locale.filter(languageRanges,locales);
// Print out the matches
for(Locale l : results){
System.out.println(l.toString());
}
}
}
该程序的输出是:
en_US
en_GB
返回的列表根据用户的语言优先级列表中指定的权重进行排序。
Locale
类还定义了filterTags
方法,用于将语言标记过滤为String
对象。
方法签名如下:
-
public static List<String> filterTags (List<Locale.LanguageRange> priorityList, Collection<String> tags)
-
public static List<String> filterTags (List<Locale.LanguageRange> priorityList, Collection<String> tags, Locale.FilteringMode mode)
示例 5 提供了与示例 4 相同的搜索,但使用了String
对象而不是Locale
对象。
示例 5:
package languagetagdemo;
import java.util.Locale;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class LanguageTagDemo {
public static void main(String[] args) {
// Create a collection of String objects to match against
Collection<String> tags = new ArrayList<>();
tags.add("en-GB");
tags.add("ja");
tags.add("zh-cmn-Hans-CN");
tags.add("en-US");
// Express user's preferences with a Language Priority List
String ranges = "en-US;q=1.0,en-GB;q=0.5,fr-FR;q=0.0";
List<Locale.LanguageRange> languageRanges = Locale.LanguageRange.parse(ranges);
// Now search the locales for the best match
List<String> results = Locale.filterTags(languageRanges,tags);
// Print out the matches
for(String s : results){
System.out.println(s);
}
}
}
与以前一样,搜索将匹配并返回“en-US”和“en-GB”(按顺序)。
执行语言标记查找
与语言标记过滤相反,语言标记查找是将语言范围与语言标记集匹配并返回最佳匹配的语言标记的过程。RFC4647 规定:“查找从可用标记列表中产生最佳匹配用户偏好的单个结果,因此在需要单个项目的情况下很有用(并且只能返回一个项目)。例如,如果一个过程要将可读的错误消息插入协议头部,它可能会根据用户的语言优先级列表选择文本。由于该过程只能返回一个项目,因此必须选择一个项目并返回某个项目,即使内容的语言标记都不匹配用户提供的语言优先级列表。”
示例 6:
package languagetagdemo;
import java.util.Locale;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class LanguageTagDemo {
public static void main(String[] args) {
// Create a collection of Locale objects to search
Collection<Locale> locales = new ArrayList<>();
locales.add(Locale.forLanguageTag("en-GB"));
locales.add(Locale.forLanguageTag("ja"));
locales.add(Locale.forLanguageTag("zh-cmn-Hans-CN"));
locales.add(Locale.forLanguageTag("en-US"));
// Express the user's preferences with a Language Priority List
String ranges = "en-US;q=1.0,en-GB;q=0.5,fr-FR;q=0.0";
List<Locale.LanguageRange> languageRanges = Locale.LanguageRange.parse(ranges);
// Find the BEST match, and return just one result
Locale result = Locale.lookup(languageRanges,locales);
System.out.println(result.toString());
}
}
与过滤示例相比,在示例 6 中的查找演示返回最佳匹配的一个对象(在这种情况下是en-US
)。为了完整起见,示例 7 展示了如何使用String
对象执行相同的查找。
示例 7:
package languagetagdemo;
import java.util.Locale;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
public class LanguageTagDemo {
public static void main(String[] args) {
// Create a collection of String objects to match against
Collection<String> tags = new ArrayList<>();
tags.add("en-GB");
tags.add("ja");
tags.add("zh-cmn-Hans-CN");
tags.add("en-US");
// Express user's preferences with a Language Priority List
String ranges = "en-US;q=1.0,en-GB;q=0.5,fr-FR;q=0.0";
List<Locale.LanguageRange> languageRanges = Locale.LanguageRange.parse(ranges);
// Find the BEST match, and return just one result
String result = Locale.lookupTag(languageRanges, tags);
System.out.println(result);
}
}
这个示例返回与用户的语言优先级列表最匹配的单个对象。
区域的范围
原文:
docs.oracle.com/javase/tutorial/i18n/locale/scope.html
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
原文:
docs.oracle.com/javase/tutorial/i18n/resbundle/list.html
本节演示了使用 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
方法来确定是否需要重新加载资源包。
教训:格式化
原文:
docs.oracle.com/javase/tutorial/i18n/format/index.html
本课程解释了如何格式化数字、货币、日期、时间和文本消息。因为最终用户可以看到这些数据元素,它们的格式必须符合各种文化习俗。遵循本课程中的示例将教会您如何:
-
以区域设置敏感的方式格式化数据元素
-
保持您的代码与区域设置无关
-
避免为特定区域编写格式化程序的需要
数字和货币
本节解释了如何使用NumberFormat
、DecimalFormat
和DecimalFormatSymbols
类。
日期和时间
版本说明: 本日期和时间部分使用了java.util
包中的日期和时间 API。在 JDK 8 发布的java.time
API 中,提供了一个全面的日期和时间模型,相比java.util
类有显著的改进。java.time
API 在日期时间教程中有详细描述。旧版日期时间代码页面可能会引起特别关注。
本节重点介绍了DateFormat
、SimpleDateFormat
和DateFormatSymbols
类。
消息
本节展示了MessageFormat
和ChoiceFormat
类如何帮助您解决在格式化文本消息时可能遇到的一些问题。
数字和货币
原文:
docs.oracle.com/javase/tutorial/i18n/format/numberintro.html
程序以与语言环境无关的方式存储和操作数字。在显示或打印数字之前,程序必须将其转换为符合语言环境的String
格式。例如,在法国,数字 123456.78 应格式化为 123 456,78,在德国应显示为 123.456,78。在本节中,您将学习如何使您的程序独立于语言环境的小数点、千位分隔符和其他格式化属性的约定。
使用预定义格式
使用NumberFormat
类提供的工厂方法,您可以获得针对数字、货币和百分比的特定于语言环境的格式。
使用模式进行格式化
使用DecimalFormat
类,您可以使用String
模式指定数字的格式。DecimalFormatSymbols
类允许您修改格式化符号,如小数分隔符和减号。
使用预定义格式
原文:
docs.oracle.com/javase/tutorial/i18n/format/numberFormat.html
通过调用NumberFormat
类提供的方法,你可以根据Locale
格式化数字、货币和百分比。接下来的内容演示了如何使用一个名为NumberFormatDemo.java
的示例程序进行格式化技术。
数字
你可以使用NumberFormat
方法来格式化原始类型数字,比如double
,以及它们对应的包装对象,比如Double
。
以下代码示例根据Locale
格式化一个Double
。调用getNumberInstance
方法返回一个特定于语言环境的NumberFormat
实例。format
方法接受Double
作为参数,并以String
形式返回格式化的数字。
static public void displayNumber(Locale currentLocale) {
Integer quantity = new Integer(123456);
Double amount = new Double(345987.246);
NumberFormat numberFormatter;
String quantityOut;
String amountOut;
numberFormatter = NumberFormat.getNumberInstance(currentLocale);
quantityOut = numberFormatter.format(quantity);
amountOut = numberFormatter.format(amount);
System.out.println(quantityOut + " " + currentLocale.toString());
System.out.println(amountOut + " " + currentLocale.toString());
}
这个例子打印如下内容;它展示了相同数字的格式如何随着Locale
的不同而变化:
123 456 fr_FR
345 987,246 fr_FR
123.456 de_DE
345.987,246 de_DE
123,456 en_US
345,987.246 en_US
使用阿拉伯数字以外的数字形状
默认情况下,当文本包含数字值时,这些值将使用阿拉伯数字显示。如果希望使用其他 Unicode 数字形状,请使用java.awt.font.NumericShaper
类。NumericShaper
API 使您能够以任何 Unicode 数字形状显示作为 ASCII 值内部表示的数字值。有关更多信息,请参见将拉丁数字转换为其他 Unicode 数字。
此外,一些语言环境具有变体代码,指定使用 Unicode 数字形状代替阿拉伯数字,比如泰语的语言环境。有关更多信息,请参见创建语言环境中的变体代码部分。
货币
如果您正在编写业务应用程序,可能需要格式化和显示货币。您可以像格式化数字一样格式化货币,只是调用getCurrencyInstance
来创建格式化程序。当调用format
方法时,它会返回一个包含格式化数字和适当货币符号的String
。
此代码示例展示了如何以区域特定的方式格式化货币:
static public void displayCurrency( Locale currentLocale) {
Double currencyAmount = new Double(9876543.21);
Currency currentCurrency = Currency.getInstance(currentLocale);
NumberFormat currencyFormatter =
NumberFormat.getCurrencyInstance(currentLocale);
System.out.println(
currentLocale.getDisplayName() + ", " +
currentCurrency.getDisplayName() + ": " +
currencyFormatter.format(currencyAmount));
}
由上述代码生成的输出如下所示:
French (France), Euro: 9 876 543,21 €
German (Germany), Euro: 9.876.543,21 €
English (United States), US Dollar: $9,876,543.21
乍一看,这个输出可能看起来对您来说是错误的,因为数字值都是相同的。当然,9 876 543,21 €不等同于$9,876,543.21。然而,请记住,NumberFormat
类不知道汇率。属于NumberFormat
类的方法格式化货币,但不会转换它们。
请注意,Currency
类设计为任何给定货币都不会有多个Currency
实例。因此,没有公共构造函数。如前面的代码示例所示,您可以使用getInstance
方法获取Currency
实例。
示例InternationalizedMortgageCalculator.java
还演示了如何使用Currency
类。(请注意,此示例不会转换货币值。)以下使用 en-US 区域设置:
以下使用 en-UK 区域设置:
示例InternationalizedMortgageCalculator.java
需要以下资源文件:
-
resources/Resources.properties
-
resources/Resources_ar.properties
-
resources/Resources_fr.properties
Currency
类包含其他方法来检索与货币相关的信息:
-
getAvailableCurrencies
:返回 JDK 中所有可用的货币 -
getCurrencyCode
:返回Currency
实例的 ISO 4217 数字代码 -
getSymbol
:返回Currency
实例的符号。您可以选择性地将Locale
对象作为参数。考虑以下摘录:Locale enGBLocale = new Locale.Builder().setLanguage("en").setRegion("GB").build(); Locale enUSLocale = new Locale.Builder().setLanguage("en").setRegion("US").build(); Currency currencyInstance = Currency.getInstance(enUSLocale); System.out.println( "Symbol for US Dollar, en-US locale: " + currencyInstance.getSymbol(enUSLocale)); System.out.println( "Symbol for US Dollar, en-UK locale: " + currencyInstance.getSymbol(enGBLocale));
该摘录打印如下内容:
Symbol for US Dollar, en-US locale: $ Symbol for US Dollar, en-UK locale: USD
此摘录演示了货币符号可以根据区域设置而变化。
-
getDisplayName
:返回Currency
实例的显示名称。与getSymbol
方法类似,您可以选择性地指定一个Locale
对象。
ISO 4217 货币代码的可扩展支持
ISO 4217是国际标准化组织发布的标准。它指定三个字母代码(以及等效的三位数字代码)来表示货币和资金。此标准由外部机构维护,并独立于 Java SE 平台发布。
假设一个国家采用了不同的货币,并且 ISO 4217 维护机构发布了一个货币更新。要实现此更新并在运行时取代默认货币,请创建一个名为*<JAVA_HOME>*/lib/currency.properties
的属性文件。此文件包含ISO 3166国家代码和 ISO 4217 货币数据的键/值对。值部分包括三个逗号分隔的 ISO 4217 货币值:字母代码、数字代码和小单位。任何以井号字符(#
)开头的行都被视为注释行。例如:
# Sample currency property for Canada
CA=CAD,124,2
CAD
代表加拿大元;124
是加拿大元的数字代码;2
是小单位,表示货币需要表示分数货币的小数位数。例如,以下属性文件将把默认的加拿大货币替换为没有比加拿大元更小单位的加拿大元:
CA=CAD,124,0
百分比
您还可以使用NumberFormat
类的方法来格式化百分比。要获取特定于区域设置的格式化程序,请调用getPercentInstance
方法。使用这个格式化程序,例如小数分数 0.75 将显示为 75%。
以下代码示例显示了如何格式化百分比。
static public void displayPercent(Locale currentLocale) {
Double percent = new Double(0.75);
NumberFormat percentFormatter;
String percentOut;
percentFormatter = NumberFormat.getPercentInstance(currentLocale);
percentOut = percentFormatter.format(percent);
System.out.println(percentOut + " " + currentLocale.toString());
}
这个示例打印如下内容:
75 % fr_FR
75% de_DE
75% en_US
自定义格式
原文:
docs.oracle.com/javase/tutorial/i18n/format/decimalFormat.html
你可以使用DecimalFormat
类将十进制数格式化为特定于区域设置的字符串。这个类允许你控制前导和尾随零的显示,前缀和后缀,分组(千位)分隔符和小数分隔符。如果你想要更改格式化符号,比如小数分隔符,你可以与DecimalFormat
类一起使用DecimalFormatSymbols
。这些类在数字格式化方面提供了很大的灵活性,但可能会使你的代码变得更加复杂。
以下文本使用示例演示了DecimalFormat
和DecimalFormatSymbols
类。本材料中的代码示例来自一个名为DecimalFormatDemo
的示例程序。
构建模式
你可以通过模式String
指定DecimalFormat
的格式属性。模式决定了格式化后的数字是什么样子的。有关模式语法的完整描述,请参见数字格式模式语法。
接下来的示例通过将模式String
传递给DecimalFormat
构造函数来创建一个格式化器。format
方法接受一个double
值作为参数,并以String
形式返回格式化后的数字:
DecimalFormat myFormatter = new DecimalFormat(pattern);
String output = myFormatter.format(value);
System.out.println(value + " " + pattern + " " + output);
前面代码的输出描述在以下表中。value
是要格式化的数字,一个double
,pattern
是指定格式属性的String
,output
是一个String
,表示格式化后的数字。
DecimalFormatDemo
程序的输出
value | pattern | output | 解释 |
---|---|---|---|
123456.789 | ###,###.### | 123,456.789 | 井号(#)表示一个数字,逗号是分组分隔符的占位符,句点是小数分隔符的占位符。 |
123456.789 | ###.## | 123456.79 | value 小数点右侧有三位数字,但pattern 只有两位。format 方法通过四舍五入处理这个问题。 |
123.78 | 000000.000 | 000123.780 | pattern 指定了前导和尾随零,因为使用 0 字符而不是井号(#)。 |
12345.67 | $###,###.### | $12,345.67 | pattern 中的第一个字符是美元符号($)。请注意,它紧跟在格式化的output 中最左边的数字之前。 |
12345.67 | \u00A5###,###.### | ¥12,345.67 | pattern 指定了日元(¥)的货币符号,Unicode 值为 00A5。 |
区域敏感格式化
前面的示例创建了一个默认Locale
的DecimalFormat
对象。如果你想要一个非默认Locale
的DecimalFormat
对象,你可以实例化一个NumberFormat
,然后将其转换为DecimalFormat
。以下是一个示例:
NumberFormat nf = NumberFormat.getNumberInstance(loc);
DecimalFormat df = (DecimalFormat)nf;
df.applyPattern(pattern);
String output = df.format(value);
System.out.println(pattern + " " + output + " " + loc.toString());
运行上一个代码示例会产生以下输出。格式化的数字,位于第二列,随Locale
而变化:
###,###.### 123,456.789 en_US
###,###.### 123.456,789 de_DE
###,###.### 123 456,789 fr_FR
到目前为止,这里讨论的格式化模式遵循美国英语的惯例。例如,在模式###,###.##中,逗号是千位分隔符,句点代表小数点。这种惯例是可以接受的,前提是您的最终用户不会接触到它。然而,一些应用程序,如电子表格和报表生成器,允许最终用户定义自己的格式化模式。对于这些应用程序,最终用户指定的格式化模式应使用本地化符号。在这些情况下,您需要在DecimalFormat
对象上调用applyLocalizedPattern
方法。
更改格式化符号
您可以使用DecimalFormatSymbols类来更改format
方法生成的格式化数字中出现的符号。这些符号包括小数分隔符、分组分隔符、减号和百分号等。
下一个示例演示了DecimalFormatSymbols
类,通过对数字应用奇怪的格式来实现。这种不寻常的格式是通过调用setDecimalSeparator
、setGroupingSeparator
和setGroupingSize
方法得到的。
DecimalFormatSymbols unusualSymbols = new DecimalFormatSymbols(currentLocale);
unusualSymbols.setDecimalSeparator('|');
unusualSymbols.setGroupingSeparator('^');
String strange = "#,##0.###";
DecimalFormat weirdFormatter = new DecimalFormat(strange, unusualSymbols);
weirdFormatter.setGroupingSize(4);
String bizarre = weirdFormatter.format(12345.678);
System.out.println(bizarre);
运行时,此示例会以奇怪的格式打印数字:
1²³⁴⁵|678
数字格式模式语法
您可以按照以下 BNF 图表指定的规则设计自己的数字格式模式:
pattern := subpattern{;subpattern}
subpattern := {prefix}integer{.fraction}{suffix}
prefix := '\\u0000'..'\\uFFFD' - specialCharacters
suffix := '\\u0000'..'\\uFFFD' - specialCharacters
integer := '#'* '0'* '0'
fraction := '0'* '#'*
在前面的图表中使用的符号在以下表格中有解释:
符号 | 描述 |
---|---|
X* | X 的 0 或多个实例 |
(X | Y) | X 或 Y 中的任意一个 |
X..Y | 从 X 到 Y 的任意字符,包括 X 和 Y |
S - T | S 中的字符,但不包括 T 中的字符 |
{X} | X 是可选的 |
在前面的 BNF 图表中,第一个子模式指定了正数的格式。第二个子模式是可选的,指定了负数的格式。
尽管在 BNF 图表中没有说明,但逗号可能出现在整数部分内。
在子模式中,您可以使用特殊符号指定格式。这些符号在以下表格中描述:
符号 | 描述 |
---|---|
0 | 一个数字 |
# | 一个数字,零表示不存在 |
. | 小数分隔符的占位符 |
, | 用于分组分隔符的占位符 |
E | 用于指数格式中的尾数和指数的分隔符 |
; | 分隔格式 |
- | 默认负数前缀 |
% | 乘以 100 并显示为百分比 |
? | 乘以 1000 并显示为千分数 |
¤ | 货币符号;替换为货币符号;如果双倍,则替换为国际货币符号;如果在模式中存在,则使用货币小数分隔符而不是小数分隔符 |
X | 前缀或后缀中可以使用任何其他字符 |
’ | 用于引用前缀或后缀中的特殊字符 |
日期和时间
原文:
docs.oracle.com/javase/tutorial/i18n/format/dateintro.html
版本说明: 此日期和时间部分使用java.util
包中的日期和时间 API。JDK 8 发布的java.time
API 提供了一个全面的日期和时间模型,相比java.util
类有显著改进。java.time
API 在日期时间教程中有描述。旧版日期时间代码页面可能会引起特别关注。
Date
对象表示日期和时间。您无法显示或打印Date
对象,而不先将其转换为适当格式的String
。什么是“适当”的格式呢?首先,格式应符合最终用户的Locale
的约定。例如,德国人认为20.4.09
是一个有效的日期,但美国人期望同一日期显示为4/20/09
。其次,格式应包含必要的信息。例如,一个测量网络性能的程序可能报告经过的毫秒数。在线约会日历可能不会显示毫秒,但会显示星期几。
本节介绍如何以各种方式和符合区域设置的方式格式化日期和时间。如果您遵循这些技术,您的程序将以适当的Locale
显示日期和时间,但您的源代码将保持独立于任何特定的Locale
。
使用预定义格式
DateFormat
类提供了具有区域特定性和易于使用的预定义格式样式。
自定义格式
使用SimpleDateFormat
类,您可以创建定制的、区域特定的格式。
更改日期格式符号
使用DateFormatSymbols
类,您可以更改表示月份名称、星期几和其他格式化元素的符号。
使用预定义格式
原文:
docs.oracle.com/javase/tutorial/i18n/format/dateFormat.html
版本说明: 本日期和时间部分使用了java.util
包中的日期和时间 API。在 JDK 8 发布的java.time
API 中,提供了一个全面的日期和时间模型,相比java.util
类有显著的改进。java.time
API 在日期时间教程中有详细描述。旧日期时间代码页面可能会引起特别关注。
DateFormat
类允许您以区域敏感的方式使用预定义样式格式化日期和时间。接下来的部分演示了如何使用名为DateFormatDemo.java
的程序与DateFormat
类一起使用。
日期
使用DateFormat
类格式化日期是一个两步过程。首先,使用getDateInstance
方法创建格式化器。其次,调用format
方法,返回包含格式化日期的String
。以下示例通过调用这两个方法格式化今天的日期:
Date today;
String dateOut;
DateFormat dateFormatter;
dateFormatter = DateFormat.getDateInstance(DateFormat.DEFAULT, currentLocale);
today = new Date();
dateOut = dateFormatter.format(today);
System.out.println(dateOut + " " + currentLocale.toString());
以下是此代码生成的输出。请注意日期的格式会随着Locale
的不同而变化。由于DateFormat
是区域敏感的,它会为每个Locale
处理格式化细节。
30 juin 2009 fr_FR
30.06.2009 de_DE
Jun 30, 2009 en_US
前面的代码示例指定了默认
格式样式。默认
样式只是DateFormat
类提供的预定义格式样式之一,如下所示:
-
默认
-
短
-
中等
-
长
-
完整
以下表显示了美国和法国区域的每种样式的日期格式:
示例日期格式
样式 | 美国区域 | 法国区域 |
---|---|---|
默认 | 2009 年 6 月 30 日 | 2009 年 6 月 30 日 |
短 | 6/30/09 | 30/06/09 |
中等 | 2009 年 6 月 30 日 | 2009 年 6 月 30 日 |
长 | 2009 年 6 月 30 日 | 2009 年 6 月 30 日 |
完整 | 2009 年 6 月 30 日星期二 | 2009 年 6 月 30 日星期二 |
时间
Date
对象代表日期和时间。使用DateFormat
类格式化时间与格式化日期类似,只是你需要使用getTimeInstance
方法创建格式化器,如下所示:
DateFormat timeFormatter =
DateFormat.getTimeInstance(DateFormat.DEFAULT, currentLocale);
下表显示了美国和德国区域的各种预定义格式样式:
示例时间格式
样式 | 美国区域 | 德国区域 |
---|---|---|
默认 | 上午 7:03:47 | 7:03:47 |
短 | 上午 7:03 | 07:03 |
中等 | 上午 7:03:47 | 07:03:07 |
长 | 上午 7:03:47 PDT | 07:03:45 PDT |
完整 | 上午 7:03:47 PDT | 下午 7.03 PDT |
日期和时间都包括
要在同一String
中显示日期和时间,使用getDateTimeInstance
方法创建格式化器。第一个参数是日期样式,第二个是时间样式。第三个参数是Locale
。以下是一个快速示例:
DateFormat formatter = DateFormat.getDateTimeInstance(
DateFormat.LONG,
DateFormat.LONG,
currentLocale);
以下表显示了美国和法国区域的日期和时间格式样式:
日期和时间格式示例
样式 | 美国区域设置 | 法国区域设置 |
---|---|---|
DEFAULT | Jun 30, 2009 7:03:47 AM | 30 juin 2009 07:03:47 |
SHORT | 6/30/09 7:03 AM | 30/06/09 07:03 |
MEDIUM | Jun 30, 2009 7:03:47 AM | 30 juin 2009 07:03:47 |
LONG | June 30, 2009 7:03:47 AM PDT | 30 juin 2009 07:03:47 PDT |
FULL | Tuesday, June 30, 2009 7:03:47 AM PDT | mardi 30 juin 2009 07 h 03 PDT |
自定义格式
原文:
docs.oracle.com/javase/tutorial/i18n/format/simpleDateFormat.html
版本说明: 此日期和时间部分使用java.util
包中的日期和时间 API。在 JDK 8 发布的java.time
API 提供了一个全面的日期和时间模型,相比java.util
类有显著改进。java.time
API 在日期时间教程中有详细描述。旧版日期时间代码页面可能会引起特别关注。
前一节,使用预定义格式,描述了DateFormat
类提供的格式样式。在大多数情况下,这些预定义格式是足够的。但是,如果你想创建自定义格式,可以使用SimpleDateFormat
类。
接下来的代码示例演示了SimpleDateFormat
类的方法。你可以在名为SimpleDateFormatDemo
的文件中找到示例的完整源代码。
关于模式
当你创建一个SimpleDateFormat
对象时,你指定一个模式String
。模式String
的内容决定了日期和时间的格式。有关模式语法的完整描述,请参见日期格式模式语法中的表格。
以下代码根据传递给SimpleDateFormat
构造函数的模式String
格式化日期和时间。format
方法返回的String
包含要显示的格式化日期和时间。
Date today;
String output;
SimpleDateFormat formatter;
formatter = new SimpleDateFormat(pattern, currentLocale);
today = new Date();
output = formatter.format(today);
System.out.println(pattern + " " + output);
以下表格显示了在指定美国Locale
时前面代码示例生成的输出:
自定义日期和时间格式
模式 | 输出 |
---|---|
dd.MM.yy | 30.06.09 |
yyyy.MM.dd G ‘at’ hh:mm:ss z | 2009.06.30 公元 at 08:29:36 PDT |
EEE, MMM d, ''yy | 周二, 六月 30, '09 |
h:mm a | 8:29 PM |
H:mm | 8:29 |
H:mm:ss:SSS | 8:28:36:249 |
K:mm a,z | 8:29 AM,PDT |
yyyy.MMMMM.dd GGG hh:mm aaa | 2009.六月.30 公元 08:29 AM |
模式和区域设置
SimpleDateFormat
类是区域敏感的。如果实例化SimpleDateFormat
而不带Locale
参数,它将根据默认Locale
格式化日期和时间。模式和Locale
都决定了格式。对于相同的模式,如果Locale
不同,SimpleDateFormat
可能会以不同方式格式化日期和时间。
在接下来的示例代码中,模式是硬编码在创建SimpleDateFormat
对象的语句中的:
Date today;
String result;
SimpleDateFormat formatter;
formatter = new SimpleDateFormat("EEE d MMM yy", currentLocale);
today = new Date();
result = formatter.format(today);
System.out.println("Locale: " + currentLocale.toString());
System.out.println("Result: " + result);
当currentLocale
设置为不同值时,前面的代码示例生成以下输出:
Locale: fr_FR
Result: mar. 30 juin 09
Locale: de_DE
Result: Di 30 Jun 09
Locale: en_US
Result: Tue 30 Jun 09
日期格式模式语法
你可以根据以下表格中的符号列表设计自己的日期和时间格式模式:
符号 | 含义 | 显示 | 示例 |
---|---|---|---|
G | 时代指示符 | 文本 | 公元 |
y | 年份 | 数字 | 2009 |
M | 年份中的月份 | 文本 & 数字 | 七月 & 07 |
d | 月中的日期 | 数字 | 10 |
h | 上午/下午的小时数 (1-12) | 数字 | 12 |
H | 一天中的小时数 (0-23) | 数字 | 0 |
m | 小时中的分钟数 | 数字 | 30 |
s | 分钟中的秒数 | 数字 | 55 |
S | 毫秒 | 数字 | 978 |
E | 星期几 | 文本 | 星期二 |
D | 一年中的日期 | 数字 | 189 |
F | 月中的星期几 | 数字 | 2 (七月第二个星期三) |
w | 年中周数 | 数字 | 27 |
W | 月中周数 | 数字 | 2 |
a | 上午/下午标记 | 文本 | 下午 |
k | 一天中的小时数 (1-24) | 数字 | 24 |
K | 上午/下午的小时数 (0-11) | 数字 | 0 |
z | 时区 | 文本 | 太平洋标准时间 |
’ | 转义文本 | 分隔符 | (无) |
’ | 单引号 | 字面值 | ’ |
非字母字符被视为引用文本。也就是说,即使它们没有被单引号括起来,它们也会出现在格式化文本中。
您指定的符号字母数量还决定了格式。例如,如果“zz”模式结果为“PDT”,那么“zzzz”模式会生成“太平洋夏令时间”。以下表总结了这些规则:
展示 | 符号数量 | 结果 |
---|---|---|
文本 | 1 - 3 | 缩写形式,如果存在的话 |
文本 | >= 4 | 完整形式 |
数字 | 需要的最小数字位数 | 较短的数字用零填充(例如,如果’y’的计数为 2,则年份将被截断为 2 位数) |
文本 & 数字 | 1 - 2 | 数字形式 |
文本 & 数字 | 3 | 文本形式 |
更改日期格式符号
原文:
docs.oracle.com/javase/tutorial/i18n/format/dateFormatSymbols.html
版本说明: 此日期和时间部分使用java.util
包中的日期和时间 API。在 JDK 8 发布的java.time
API 提供了一个全面的日期和时间模型,相比java.util
类有显著改进。java.time
API 在日期时间教程中有详细描述。传统日期时间代码页面可能会引起特别关注。
SimpleDateFormat
类的format
方法返回由数字和符号组成的String
。例如,在String
“Friday, April 10, 2009"中,符号是"Friday"和"April”。如果SimpleDateFormat
封装的符号不符合您的需求,您可以使用DateFormatSymbols
进行更改。您可以更改代表月份、星期几和时区等的符号。以下表列出了允许您修改符号的DateFormatSymbols
方法:
DateFormatSymbol
方法
设置方法 | 方法修改的符号示例 |
---|---|
setAmPmStrings | 下午 |
setEras | 公元 |
setMonths | 十二月 |
setShortMonths | 十二月 |
setShortWeekdays | 星期二 |
setWeekdays | 星期二 |
setZoneStrings | PST |
以下示例调用setShortWeekdays
将星期几的简称从小写更改为大写字符。此示例的完整源代码在DateFormatSymbolsDemo
中。setShortWeekdays
的数组参数中的第一个元素是一个空String
。因此,数组是基于一的而不是零的。SimpleDateFormat
构造函数接受修改后的DateFormatSymbols
对象作为参数。以下是源代码:
Date today;
String result;
SimpleDateFormat formatter;
DateFormatSymbols symbols;
String[] defaultDays;
String[] modifiedDays;
symbols = new DateFormatSymbols( new Locale("en", "US"));
defaultDays = symbols.getShortWeekdays();
for (int i = 0; i < defaultDays.length; i++) {
System.out.print(defaultDays[i] + " ");
}
System.out.println();
String[] capitalDays = {
"", "SUN", "MON",
"TUE", "WED", "THU",
"FRI", "SAT"
};
symbols.setShortWeekdays(capitalDays);
modifiedDays = symbols.getShortWeekdays();
for (int i = 0; i < modifiedDays.length; i++) {
System.out.print(modifiedDays[i] + " ");
}
System.out.println();
System.out.println();
formatter = new SimpleDateFormat("E", symbols);
today = new Date();
result = formatter.format(today);
System.out.println("Today's day of the week: " + result);
以上代码生成以下输出:
Sun Mon Tue Wed Thu Fri Sat
SUN MON TUE WED THU FRI SAT
Today's day of the week: MON
消息
原文:
docs.oracle.com/javase/tutorial/i18n/format/messageintro.html
我们都喜欢使用让我们了解正在发生什么的程序。通常,让我们保持了解的程序通过显示状态和错误消息来实现。当然,这些消息需要被翻译,以便全球的最终用户能够理解。隔离特定区域数据部分讨论了可翻译的文本消息。通常,在将消息String
移入ResourceBundle
后,你就完成了。然而,如果在消息中嵌入了变量数据,你需要采取一些额外的步骤来准备翻译。
复合消息包含变量数据。在下面的复合消息列表中,变量数据被划线标出:
The disk named MyDisk contains 300 files.
The current balance of account #34-09-222 is $2,745.72.
405,390 people have visited your website since January 1, 2009.
Delete all files older than 120 days.
你可能会尝试通过连接短语和变量来构建前述列表中的最后一条消息,如下所示:
double numDays;
ResourceBundle msgBundle;
// ...
String message = msgBundle.getString(
"deleteolder" +
numDays.toString() +
msgBundle.getString("days"));
这种方法在英语中效果很好,但对于动词出现在句子末尾的语言来说,这种方法不适用。因为这条消息的词序是硬编码的,你的本地化人员将无法为所有语言创建语法正确的翻译。
如果需要使用复合消息,如何使你的程序可本地化?你可以通过使用MessageFormat
类来实现,这是本节的主题。
注意:
复合消息很难翻译,因为消息文本是分散的。如果使用复合消息,本地化将需要更长的时间和更多的成本。因此,只有在必要时才应使用复合消息。
处理复合消息
复合消息可能包含多种类型的变量:日期、时间、字符串、数字、货币和百分比。为了以与区域无关的方式格式化复合消息,你需要构建一个模式,然后应用到MessageFormat
对象上。
处理复数形式
如果可能同时存在复数和单数形式的词汇,消息中的词汇通常会有所变化。通过ChoiceFormat
类,你可以将数字映射到一个词或短语,从而构建语法正确的消息。
处理复合消息
原文:
docs.oracle.com/javase/tutorial/i18n/format/messageFormat.html
复合消息可能包含几种类型的变量:日期、时间、字符串、数字、货币和百分比。为了以与语言环境无关的方式格式化复合消息,您构造一个模式,将其应用于 MessageFormat
对象,并将此模式存储在 ResourceBundle
中。
通过逐步执行示例程序,本节演示了如何国际化复合消息。示例程序使用了 MessageFormat
类。此程序的完整源代码在名为 MessageFormatDemo.java
的文件中。德语区域设置属性在名为 MessageBundle_de_DE.properties
的文件中。
1. 识别消息中的变量
假设您想要国际化以下消息:
请注意,我们已经划线标出了变量数据,并确定了将表示这些数据的对象的类型。
2. 在 ResourceBundle 中隔离消息模式
将消息存储在名为 MessageBundle
的 ResourceBundle
中,如下所示:
ResourceBundle messages =
ResourceBundle.getBundle("MessageBundle", currentLocale);
这个 ResourceBundle
是由每个 Locale
的属性文件支持的。由于 ResourceBundle
名称为 MessageBundle
,因此美国英语的属性文件名为 MessageBundle_en_US.properties
。此文件的内容如下:
template = At {2,time,short} on {2,date,long}, \
we detected {1,number,integer} spaceships on \
the planet {0}.
planet = Mars
属性文件的第一行包含消息模式。如果您将此模式与步骤 1 中显示的消息文本进行比较,您将看到在消息文本中的每个变量都由大括号括起的参数替换。每个参数以称为参数编号的数字开头,该数字与保存参数值的 Object
数组中的元素的索引相匹配。请注意,在模式中,参数编号没有特定顺序。您可以将参数放置在模式的任何位置。唯一的要求是参数编号在参数值数组中有一个匹配的元素。
下一步讨论了参数值数组,但首先让我们看一下模式中的每个参数。以下表格提供了有关参数的一些详细信息:
在 MessageBundle_en_US.properties
中为 template
参数提供参数
参数 | 描述 |
---|---|
{2,time,short} | 一个 Date 对象的时间部分。short 样式指定了 DateFormat.SHORT 格式化样式。 |
{2,date,long} | 一个Date 对象的日期部分。相同的Date 对象用于日期和时间变量。在参数的Object 数组中,保存Date 对象的元素的索引为 2。 (这在下一步中描述。) |
{1,number,integer} | 一个带有integer 数字样式的Number 对象。 |
{0} | 与planet 键对应的ResourceBundle 中的String 。 |
对于参数语法的完整描述,请参阅MessageFormat
类的 API 文档。
3. 设置消息参数
以下代码行为模式中的每个参数分配值。messageArguments
数组中元素的索引与模式中的参数编号相匹配。例如,索引为 1 的Integer
元素对应于模式中的{1,number,integer}
参数。因为必须进行翻译,所以元素 0 处的String
对象将使用getString
方法从ResourceBundle
中获取。以下是定义消息参数数组的代码:
Object[] messageArguments = {
messages.getString("planet"),
new Integer(7),
new Date()
};
4. 创建格式化程序
接下来,创建一个MessageFormat
对象。您设置Locale
,因为消息包含应以区域敏感的方式格式化的Date
和Number
对象。
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(currentLocale);
5. 使用模式和参数格式化消息
这一步展示了模式、消息参数和格式化程序如何协同工作。首先,使用getString
方法从ResourceBundle
中获取模式String
。模式的关键是template
。使用applyPattern
方法将模式String
传递给格式化程序。然后通过调用format
方法使用消息参数的数组格式化消息。format
方法返回的String
已经准备好显示。所有这些只需两行代码就可以完成:
formatter.applyPattern(messages.getString("template"));
String output = formatter.format(messageArguments);
6. 运行演示程序
演示程序打印了英语和德语区域设置的翻译消息,并正确格式化了日期和时间变量。请注意,英语和德语动词(“detected"和"entdeckt”)相对于变量的位置不同:
currentLocale = en_US
At 10:16 AM on July 31, 2009, we detected 7
spaceships on the planet Mars.
currentLocale = de_DE
Um 10:16 am 31\. Juli 2009 haben wir 7 Raumschiffe
auf dem Planeten Mars entdeckt.
处理复数形式
原文:
docs.oracle.com/javase/tutorial/i18n/format/choiceFormat.html
如果消息中的单词既可能是复数形式又可能是单数形式,则可能会有所变化。使用ChoiceFormat
类,您可以将数字映射到一个单词或短语,从而构造语法正确的消息。
在英语中,单词的复数和单数形式通常是不同的。当您构造涉及数量的消息时,这可能会带来问题。例如,如果您的消息报告磁盘上的文件数量,则可能存在以下变化:
There are no files on XDisk.
There is one file on XDisk.
There are 2 files on XDisk.
解决这个问题的最快方法是创建一个像这样的MessageFormat
模式:
There are {0,number} file(s) on {1}.
不幸的是,前面的模式导致了不正确的语法:
There are 1 file(s) on XDisk.
只要使用ChoiceFormat
类,你就可以做得更好。在本节中,您将通过一个名为ChoiceFormatDemo
的示例程序逐步学习如何处理消息中的复数。该程序还使用了在前一节中讨论的MessageFormat
类,即处理复合消息。
1. 定义消息模式
首先,识别消息中的变量:
然后,用参数替换消息中的变量,创建一个可以应用于MessageFormat
对象的模式:
There {0} on {1}.
磁盘名称的参数,由{1}
表示,处理起来相当简单。您只需像处理MessageFormat
模式中的任何其他String
变量一样对待它。此参数匹配参数值数组中索引为 1 的元素。(参见步骤 7。)
处理参数{0}
更加复杂,原因有几个:
-
此参数替换的短语随文件数量的变化而变化。为了在运行时构造这个短语,您需要将文件数量映射到特定的
String
。例如,数字 1 将映射到包含短语is one file
的String
。ChoiceFormat
类允许您执行必要的映射。 -
如果磁盘包含多个文件,则短语中包含一个整数。
MessageFormat
类允许您将数字插入到短语中。
2. 创建一个 ResourceBundle
因为消息文本必须被翻译,所以将其隔离在一个ResourceBundle
中:
ResourceBundle bundle = ResourceBundle.getBundle(
"ChoiceBundle", currentLocale);
示例程序使用属性文件支持ResourceBundle
。ChoiceBundle_en_US.properties
包含以下内容:
pattern = There {0} on {1}.
noFiles = are no files
oneFile = is one file
multipleFiles = are {2} files
此属性文件的内容显示了消息将如何构建和格式化。第一行包含了MessageFormat
的模式。(参见步骤 1。)其他行包含了将替换模式中参数{0}
的短语。multipleFiles
键的短语包含了参数{2}
,该参数将被一个数字替换。
这是属性文件的法语版本,ChoiceBundle_fr_FR.properties
pattern = Il {0} sur {1}.
noFiles = n'y a pas de fichiers
oneFile = y a un fichier
multipleFiles = y a {2} fichiers
3. 创建消息格式化器
在此步骤中,您实例化MessageFormat
并设置其Locale
:
MessageFormat messageForm = new MessageFormat("");
messageForm.setLocale(currentLocale);
4. 创建选择格式化器
ChoiceFormat
对象允许您根据double
数字选择特定的String
。double
数字的范围,以及它们映射到的String
对象,都在数组中指定:
double[] fileLimits = {0,1,2};
String [] fileStrings = {
bundle.getString("noFiles"),
bundle.getString("oneFile"),
bundle.getString("multipleFiles")
};
ChoiceFormat
将double
数组中的每个元素映射到具有相同索引的String
数组中的元素。在示例代码中,0 映射到调用bundle.getString("noFiles")
返回的String
。巧合的是,索引与fileLimits
数组中的值相同。如果代码将fileLimits[0]
设置为七,ChoiceFormat
将把数字 7 映射到fileStrings[0]
。
在实例化ChoiceFormat
时,您需要指定double
和String
数组:
ChoiceFormat choiceForm = new ChoiceFormat(fileLimits, fileStrings);
5. 应用模式
还记得您在步骤 1 中构建的模式吗?现在是从ResourceBundle
中检索模式并应用到MessageFormat
对象的时候了:
String pattern = bundle.getString("pattern");
messageForm.applyPattern(pattern);
6. 分配格式
在此步骤中,您将在步骤 4 中创建的ChoiceFormat
对象分配给MessageFormat
对象:
Format[] formats = {choiceForm, null, NumberFormat.getInstance()};
messageForm.setFormats(formats);
setFormats
方法将Format
对象分配给消息模式中的参数。在调用setFormats
方法之前,必须调用applyPattern
方法。以下表格显示了Format
数组的元素如何对应于消息模式中的参数:
ChoiceFormatDemo
程序的Format
数组
数组元素 | 模式参数 |
---|---|
choiceForm | {0} |
null | {1} |
NumberFormat.getInstance() | {2} |
7. 设置参数并格式化消息
在运行时,程序将变量分配给传递给MessageFormat
对象的参数数组。数组中的元素对应于模式中的参数。例如,messageArgument[1]
映射到模式参数{1}
,其中包含磁盘名称的String
。在上一步中,程序将ChoiceFormat
对象分配给模式的参数{0}
。因此,分配给messageArgument[0]
的数字确定ChoiceFormat
对象选择哪个String
。如果messageArgument[0]
大于或等于 2,则包含短语are {2} files
的String
将替换模式中的参数{0}
。分配给messageArgument[2]
的数字将替换模式参数{2}
的位置。以下是尝试此操作的代码:
Object[] messageArguments = {null, "XDisk", null};
for (int numFiles = 0; numFiles < 4; numFiles++) {
messageArguments[0] = new Integer(numFiles);
messageArguments[2] = new Integer(numFiles);
String result = messageForm.format(messageArguments);
System.out.println(result);
}
8. 运行演示程序
将程序显示的消息与第 2 步的ResourceBundle
中的短语进行比较。注意ChoiceFormat
对象选择了正确的短语,MessageFormat
对象用于构建适当的消息。ChoiceFormatDemo
程序的输出如下:
currentLocale = en_US
There are no files on XDisk.
There is one file on XDisk.
There are 2 files on XDisk.
There are 3 files on XDisk.
currentLocale = fr_FR
Il n'y a pas des fichiers sur XDisk.
Il y a un fichier sur XDisk.
Il y a 2 fichiers sur XDisk.
Il y a 3 fichiers sur XDisk.