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)。

要本地化应用,第一步是将所有硬编码消息移出代码,放入资源文件中

例如,资源目录结构如下:

messages localization

每个文件包含 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 国际化指南

将消息放入资源文件的好处:

  1. 翻译人员不需要在代码中查找字符串
  2. 翻译时能看到完整语境,翻译更准确
  3. 新语言上线无需重新编译代码

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.propertiesformats_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 更丰富的本地化规则。

比如,它支持 pluralselect,可以更自然地处理性别和复数问题。

添加依赖:

<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 支持更高级的本地化规则(如复数、性别)

⚠️ 本地化不仅仅是翻译,还要考虑语法、文化习惯和格式差异。

合理使用资源文件和格式化工具,可以让你的应用真正“全球化”。


原始标题:Java Localization - Formatting Messages | Baeldung

« 上一篇: JPA 中的默认列值
» 下一篇: JPA @Basic 注解详解