1. 简介
本文我们将探讨如何基于 Locale
对消息进行本地化和格式化。
我们会使用 Java 原生的 MessageFormat
,以及第三方库 ICU(International Components for Unicode)来实现。
2. 本地化场景
当你的应用用户遍布全球,你自然希望根据用户的偏好展示不同的消息。
首要考虑的是用户的语言,其次是货币、数字和日期格式。还有文化差异:某些在一个国家可接受的内容,可能在另一个国家是冒犯的。
举个例子:你正在开发一个邮件客户端,当收到新消息时要弹出通知。
英文用户的提示可以是:
Alice has sent you a message.
但法语用户更希望看到:
Alice vous a envoyé un message.
波兰语用户则期望:
Alice wysłała ci wiadomość.
如果 Alice 发的不是一条消息,而是多条呢?
你可能会这样拼接:
String message = "Alice has sent " + quantity + " messages";
但问题来了,当收件人可能是 Alice 也可能是 Bob 时:
Bob has sent two messages.
Bob a envoyé deux messages.
Bob wysłał dwie wiadomości.
注意波兰语中动词的变化(wysłała vs wysłał),这说明字符串拼接在本地化中几乎不可行。
我们面临两个问题:翻译问题 和 格式问题。接下来我们分别讨论。
3. 消息本地化
我们可以将本地化(l10n)定义为让应用适应用户习惯的过程。有时也会提到国际化(i18n)。
要本地化应用,第一步是将所有硬编码消息移出代码,放入资源文件中。
例如,资源目录结构如下:
每个文件包含 key-value 对,比如:
messages_en.properties
:
label=Alice has sent you a message.
messages_pl.properties
:
label=Alice wysłała ci wiadomość.
然后通过 ResourceBundle
获取对应语言的消息:
ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.UK);
String message = bundle.getString("label");
若要获取波兰语版本:
ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.forLanguageTag("pl-PL"));
String message = bundle.getString("label");
✅ 如果没有指定 Locale,系统会使用默认的。关于更多 Locale 设置,可参考 Java 8 国际化指南。
将消息放入资源文件的好处:
- 翻译人员不需要在代码中查找字符串
- 翻译时能看到完整语境,翻译更准确
- 新语言上线无需重新编译代码
4. 消息格式化
虽然我们已经将消息移出代码,但它们仍包含固定内容。我们需要一种方式,让消息中的变量(如名字、数量)能自动适配语法和格式。
消息格式化就是用变量替换模板中的占位符,生成最终字符串的过程。
我们介绍两种方案:
4.1. Java 的 MessageFormat
Java 提供了多种格式化方式,但 MessageFormat
更强大,支持复杂的格式化逻辑。
例如:
String pattern = "On {0, date}, {1} sent you "
+ "{2, choice, 0#no messages|1#a message|2#two messages|2<{2, number, integer} messages}.";
MessageFormat formatter = new MessageFormat(pattern, Locale.UK);
然后传入参数:
String message = formatter.format(new Object[] {date, "Alice", 2});
输出:
On 27-Apr-2019, Alice sent you two messages.
4.2. MessageFormat
语法
MessageFormat
的模板语法如下:
{index}
{index, type}
{index, type, style}
支持的类型和样式包括:
类型 | 样式 |
---|---|
number | integer, currency, percent |
date | short, medium, long, full |
time | short, medium, long, full |
choice | 自定义规则 |
例如:
{2, choice, 0#no messages|1#a message|2#two messages|2<{2, number, integer} messages}
这是一个 choice 类型,根据数值选择不同输出。其规则是:
- 如果值在 [ki, ki+1) 范围内,就使用对应的字符串 vi
示例:
输入值 | 输出结果 |
---|---|
-1, 0 | You've got no messages. |
1, 1.5 | You've got a message. |
2 | You've got two messages. |
2.5 | You've got 2 messages. |
5 | You've got 5 messages. |
4.3. 进一步优化
虽然我们已经在格式化消息了,但模板本身还是硬编码的。更好的做法是将其也放入资源文件中。
我们创建一组新的资源文件,比如 formats_en.properties
、formats_pl.properties
,然后定义 key-value:
英文:
label=On {0, date, full} {1} has sent you
{2, choice, 0#nothing|1#a message|2#two messages|2<{2,number,integer} messages}.
法文:
label={0, date, short}, {1}{2, choice, 0# ne|0<} vous a envoyé
{2, choice, 0#aucun message|1#un message|2#deux messages|2<{2,number,integer} messages}.
波兰语中还有性别问题,比如:
- Alice wysłała...
- Bob wysłał...
这就需要更复杂的逻辑,我们看下一个方案。
4.4. 使用 ICU 的 MessageFormat
ICU 是一个成熟的国际化库,支持比 Java 更丰富的本地化规则。
比如,它支持 plural
和 select
,可以更自然地处理性别和复数问题。
添加依赖:
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>64.2</version>
</dependency>
英文模板:
label-icu={0} has sent you
{2, plural, =0 {no messages} =1 {a message}
other {{2, number, integer} messages}}.
波兰语模板(考虑性别):
label-icu={0} {2, plural, =0 {nie} other {}}
{1, select, male {wysłał} female {wysłała} other {wysłało}}
ci {2, plural, =0 {żadnych wiadomości} =1 {wiadomość}
other {{2, number, integer} wiadomości}}.
使用方式:
Object[] data = new Object[] { "Alice", "female", 0 };
可以看到,ICU 更适合处理复杂的本地化场景。
5. 总结
本文我们探讨了如何在 Java 中进行消息的本地化和格式化:
✅ 使用 ResourceBundle
实现多语言支持
✅ 使用 MessageFormat
支持复杂格式
✅ 使用 ICU 支持更高级的本地化规则(如复数、性别)
⚠️ 本地化不仅仅是翻译,还要考虑语法、文化习惯和格式差异。
合理使用资源文件和格式化工具,可以让你的应用真正“全球化”。