Java国际化(i18n)中的正确复数形式

38

我原本打算使用Java标准的i18n系统和ChoiceFormat类来处理复数形式,但是发现它无法处理某些语言(例如波兰语)的复杂复数规则。如果只能处理类似英语的语言,那似乎有点毫无意义。

有哪些选项可以实现正确的复数形式?使用它们的优缺点是什么?


你能举一个复杂的复数规则的例子,这是资源包无法处理的吗? - Marc Baumbach
1
资源包与处理复数无关。你难道是混淆了MessageFormat API和ResourceBundle API的区别? - BalusC
1
@mbaumbach:这是一些关于Qt库的复数形式的文档以及Java的ChoiceFormat无法处理的示例(波兰语):http://doc.qt.digia.com/qq/qq19-plurals.html - Dr.Haribo
2
@BalusC:我不同意删除国际化标签和更改标题。这不是关于如何使ChoiceFormat处理复数形式的问题。它无法做到。这是关于寻找一个真正可行的Java国际化替代方案的问题。 - Dr.Haribo
有些语言使用词形变化来创建复数形式。实际上,我认为所有具有适当的词形变化的语言都是如此... - yeoman
显示剩余3条评论
2个回答

46

好的,您已经正确标记了问题,所以我认为您对ICU有一定了解。

使用ICU可以选择以下两种适当的复数形式处理方法:

  • PluralRules,它为给定的语言环境提供规则。
  • PluralFormat,它使用上述规则来允许格式化。

哪个更好?就我个人而言,我更喜欢直接使用PluralRules从资源包中选择适当的消息。

ULocale uLocale = ULocale.forLanguageTag("pl-PL");
ResourceBundle resources = ResourceBundle.getBundle( "path.to.messages",
                               uLocale.toLocale());
PluralRules pluralRules = PluralRules.forLocale(uLocale);

double[] numbers = { 0, 1, 1.5, 2, 2.5, 3, 4, 5, 5.5, 11, 12, 23 };
for (double number : numbers) { 
  String resourceKey = "some.message.plural_form." + pluralRules.select(number);
  String message = "!" + resourceKey + "!";
  try {
    message = resources.getString(resourceKey);
    System.out.println(format(message, uLocale, number));
   } catch (MissingResourceException e) { // Log this } 
}

当然,你(或翻译者)需要在属性文件中添加正确的表单,在这个例子中,假设如下:
some.message.plural_form.one=Znaleziono {0} plik
some.message.plural_form.few=Znaleziono {0} pliki
some.message.plural_form.many=Znaleziono {0} plików
some.message.plural_form.other=Znaleziono {0} pliku

对于其它语言(如阿拉伯语),您可能还需要使用“零”和“二”关键字,请参见CLDR的语言复数规则了解详细信息。

或者,您可以使用PluralFormat选择有效格式。通常的示例展示的是直接实例化,但在我看来这完全没有意义。更容易的方法是使用ICU的MessageFormat

String pattern = "Znaleziono {0,plural,one{# plik}" +
                 "few{# pliki}" +
                 "many{# plików}" +
                 "other{# pliku}}";
MessageFormat fmt = new MessageFormat(pattern, ULocale.forLanguageTag("pl-PL"));
StringBuffer result = new StringBuffer();
FieldPosition zero = new FieldPosition(0);
double[] theNumber = { number };
fmt.format(theNumber, result, zero);

当然,现实中你不会硬编码字符串模式,而是将类似于以下内容放入属性文件中:
some.message.pattern=Found {0,plural,one{# file}other{# files}}

这种方法唯一的问题是,翻译人员必须了解占位符的格式。另一个问题,在上面的代码中我试图展示的是,MessageFormat的静态format()方法(易于使用的那个)总是针对默认的Locale进行格式化。这在Web应用程序中可能是真正的问题,因为默认的Locale通常意味着服务器的Locale。因此,我不得不为特定的Locale进行格式化(注意浮点数),而且代码看起来相当丑陋...
我仍然喜欢PluralRules方法,对我来说更加清晰简洁(尽管它需要使用相同的消息格式化样式,只是包裹了一个Helper方法)。

1
谢谢,有很多好的信息。不,我不知道 ICU 和 gettext,我只是读到它们对复数形式有更好的支持。我也想知道它们之间的比较,如果你有使用 gettext 的经验的话。也许 ICU 有一个优势,因为你正在使用资源包,这可能会更好地与标准 Java 工具配合使用。 - Dr.Haribo
1
@Dr.Haribo:这取决于你如何处理翻译。根据你使用的翻译记忆工具(如果有),gettext 可能是更好或更差的解决方案。我会先咨询翻译提供商。 - Paweł Dyda

4

6
请查看您链接到的帖子的评论区。有一个来自波兰的例子表明ChoiceFormat并不太适用。在http://stuartgunter.wordpress.com/2011/08/14/even-better-java-i18n-pluralisation-using-icu4j/中有一篇后续文章,展示了如何使用ICU4J修复此问题。 - Dr.Haribo
4
@Peter: ChoiceFormat不能很好地处理浮点数(小数部分)以及重复规则(使用模算术)。我很抱歉地说,ChoiceFormat在波兰语或类似语言中是无用的(我确实知道我在说什么)。 - Paweł Dyda
已经注意到了,我不是波兰语专家,本应该知道这似乎太简单了。我在我的答案中添加了后续帖子的链接,以使它更清晰,仅使用ChoiceFormat是不够的。 - Peter Elliott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接