i18n 消息

  1. 概览
  2. GWT 特定格式
  3. 使用注解
  4. 复数形式
  5. 选择形式
  6. SafeHtml 消息

概述

The Messages 接口允许您将参数替换到消息中,甚至根据需要为不同的区域设置重新排序这些参数。属性文件中消息的格式遵循 Java MessageFormat 中的规范(请注意,choice 格式类型在某些扩展中不受支持)。您创建的接口将包含一个 Java 方法,其参数与格式字符串中指定的参数匹配。

以下是一个 Messages 属性值的示例

permissionDenied = Error {0}: User {1} Permission denied.

以下代码通过将值替换到消息中实现了一个警报对话框

public interface ErrorMessages extends Messages {
   String permissionDenied(int errorCode, String username);
 }
 ErrorMessages msgs = GWT.create(ErrorMessages.class)

 void permissionDenied(int errorVal, String loginId) {
   Window.alert(msgs.permissionDenied(errorVal, loginId));
 }

注意:请注意,使用引号的规则可能有点令人困惑。请参阅 MessageFormat javadoc 以获取更多详细信息。

更高级的格式化

MessageFormat javadoc 中所述,除了将值插入字符串之外,您还可以对值进行更多格式化。如果没有提供格式类型,则该值将被简单地追加到适当位置的输出字符串中。如果您希望将其格式化为数字,则可以使用 {0,number},它将使用区域设置的默认数字格式,或者 {0,number,currency} 使用区域设置的默认货币格式(但请注意您使用的货币),或者创建您自己的模式,如 {0,number,#,###.0}。日期可以使用 {0,date,medium} 等格式化,时间也一样:{0,time,full}

请注意,提供您自己的格式模式意味着您现在要负责对该模式进行本地化 - 例如,如果您使用 {0,date,MM/DD/YY} 来格式化日期,那么该模式将在所有区域设置中使用,并且其中一些区域设置可能会对月在日之前感到困惑。

静态字符串国际化的优势

从上面的示例可以看出,静态国际化依赖于国际化代码与其本地化资源之间的非常紧密的绑定。以这种方式使用显式方法调用有许多优点。GWT 编译器可以进行深度优化,删除未调用的方法并内联本地化字符串 - 使生成的代码与硬编码字符串一样高效。当应用于采用多个参数的消息时,编译时检查的价值变得更加明显。为每条消息创建 Java 方法允许编译器检查调用代码提供的参数数量和类型与属性文件中定义的消息模板是否一致。例如,尝试使用以下接口和 .properties 文件会导致编译时错误

public interface ErrorMessages extends Messages {
  String permissionDenied(int errorCode, String username);
}
permissionDenied = Error {0}: User {1} does not have permission to access {2}

返回错误,因为属性文件中的消息模板需要三个参数,而 permissionDenied 方法只能提供两个参数。

GWT 特定格式

除了 MessageFormat 支持的格式化之外,GWT 还支持许多扩展。

{name,text}

一个“静态参数”,它实际上就是 text,只是它在翻译输出中显示得好像它是一个名为 name 的占位符一样。text 始终以下一个“}”结束。这对于将未翻译的代码保留在翻译人员看到的内容之外非常有用,例如 HTML 标记

@DefaultMessage("Welcome back, {startBold,<b>}{0}{endBold,</b>}")
{0,list}{0,list,format...}
使用区域设置特定的标点符号格式化 List 或数组。例如,在英语中,列表将以这种方式格式化
项目数量 示例输出
0 (空字符串)
1 a
2 a 和 b
3 a、b 和 c

请注意,当前仅支持区域设置默认分隔符和逻辑连词形式 - 目前没有办法生成像“a;b;或 c”这样的列表。

请参阅 复数文档 以了解这与复数支持之间的交互方式。list 标记后面的格式(如果有)描述了如何格式化每个列表元素。例如,{0,list} 表示每个元素的格式如同由 {0} 格式化一样,{0,list,number,#,##} 就像由 [0,number,#,##} 格式化一样,等等。
{0,localdatetime,skeleton}
使用提供的骨架模式以区域设置特定的格式格式化日期/时间。模式字符的顺序无关紧要,空格或其他分隔符也不重要。本地化模式将包含相同的字段(但可能会将 MMM 更改为 LLL 等),以及每个字段的相同数量。
如果预定义格式之一不足,那么使用骨架模式将好得多,这样您将包含所需的项目,但仍然可以获得本地化的格式。
例如,如果您使用 {0,date,MM/dd/yy} 来格式化日期,那么您将在每个区域设置中获得完全相同的模式,这会让那些期望 dd/MM/yy 的用户感到困惑。相反,您可以使用 {0,localdatetime,MMddyy},您将获得每个区域设置的正确本地化模式。
{0,localdatetime,predef:PREDEF_NAME}
使用区域设置特定的预定义格式 - 请参阅 DateTimeFormat.PredefinedFormat" 以获取可能的值,例如:{0,localdatetime,predef:DATE_SHORT}
额外的格式化程序参数
某些格式化程序接受其他参数。这些参数将被添加到主格式规范中,并用冒号隔开 - 例如:{0,list,number:curcode=USD,currency} 表示使用默认货币格式来格式化列表元素,但使用 USD(美元)作为货币代码。您还可以提供动态参数,例如 {0,localdatetime:tz=$tz,predef:DATE_FULL},它表示要使用的时区由提供给方法的参数 TimeZone tz 提供。在支持的情况下,可以像 {0,format:arg1=val:arg2=val} 一样提供多个参数。

当前支持的参数

格式 参数名称 参数类型 说明
number curcode String 用于货币格式化的货币代码
date、time 或 localdatetime tz TimeZone 用于日期/时间格式化的时区

使用注解

这里讨论的注解是特定于 Messages 的 - 对于共享注解,请参阅 主要国际化页面

方法注解

以下注解适用于 Messages 子类型中的方法

  • @DefaultMessage(String message) 指定要用于此方法的默认区域设置的消息字符串,以及所有上述选项。如果存在 @AlternateMessage 注解,则这是在更具体的表单不适用时使用的默认文本 - 对于英语中的计数消息,这将是复数形式而不是单数形式。

  • @AlternateMessage({String form, String message, …}) 指定消息的替代形式的文本。提供的字符串数组必须成对出现,第一个条目是适合默认区域设置的替代形式的名称,第二个条目是用于该形式的消息。请参阅下面的 复数形式选择形式 示例。

参数注解

以下注解适用于 Messages 子类型中方法的参数

Messages 子类型

  • @Example(String example) 此变量的示例。许多翻译工具将向翻译人员显示此内容,而不是占位符 - 例如,Hello {0}@Example("John") 将显示为 Hello John,其中“John”被突出显示以指示它不应被翻译。
  • @Optional 表示此参数在所有翻译中可能不存在。 如果未提供此注释,则在编译的翻译字符串中不包含该参数,则会产生编译时错误。
  • @PluralCount 表示此参数用于选择要使用的文本形式(例如,1 个小部件与 2 个小部件)。

    带注释的参数必须是 int、short、数组或列表(在后一种情况下,使用列表的大小作为计数)。

复数形式

Messages 接口还支持使用复数形式。 在英语中,您希望根据计数是否为 1 来调整被计数的词。 例如

You have one tree.
You have 2 trees.

其他语言可能具有更复杂的复数形式。 幸运的是,GWT 允许您通过以下方式轻松处理此问题

public interface MyMessages extends Messages {
  @DefaultMessage("You have {0} trees.")
  @AlternateMessage({"one", "You have one tree."})
  String treeCount(@PluralCount int count);
}

然后,myMessages.treeCount(1) 返回 "You have one tree.",而 myMessages.treeCount(2) 返回 "You have 2 trees."

有关使用复数形式的详细信息,请参阅 使用复数形式的详细信息

选择形式

与上面的复数形式类似,您可能需要根据除计数之外的其他内容选择消息。 例如,您可能知道消息中引用的人的性别("{0} gave you her credits"),或者您可能希望根据用户首选项支持消息的缩写和完整版本。

public enum Gender {
  MALE,
  FEMALE,
  UNKNOWN
}

public interface MyMessages extends Messages {
  @DefaultMessage("{0} gave you their credits.")
  @AlternateMessage({
      "MALE", "{0} gave you his credits.",
      "FEMALE", "{0} gave you her credits."
  })
  String gaveCredits(String name, @Select Gender gender);
}

如果没有任何形式匹配,则使用默认消息,在本例中,如果 gendernullUNKNOWN

SafeHtml 消息

有时,您创建的消息格式包含 HTML 标记,生成的消息旨在用于 HTML 上下文中,例如 InlineHTML 小部件的内容。 如果消息是参数化的,并且 String 类型参数的值来自不受信任的输入,则应用程序容易受到跨站点脚本 (XSS) 攻击。

为了避免由于在 HTML 上下文中使用消息而导致的 XSS 漏洞,您可以在 Messages 接口中声明返回类型为 SafeHtml 的方法

public interface ErrorMessages extends Messages {
   @DefaultMessage("A <strong>{0} error</strong> has occurred: {1}.")
   SafeHtml errorHtml(String error, SafeHtml details);
 }
 ErrorMessages msgs = GWT.create(ErrorMessages.class)

 void showError(String error, SafeHtml details) {
   errorBar.setHTML(msgs.errorHtml(error, details));
   errorBar.setVisible(true);
 }

对于 SafeHtml 消息,代码生成器生成的代码保证会产生满足 SafeHtml 类型契约 的值,并且在 HTML 上下文中安全使用。 在将参数的值代入消息之前,会自动对参数的值进行 HTML 转义,除非参数的声明类型为 SafeHtml。 在上面的示例中,error 参数在代入模板之前进行 HTML 转义,而 details 参数则不会。 details 参数可以在没有转义的情况下代入消息,因为 SafeHtml 类型契约保证其值实际上可以安全用作 HTML,无需进一步转义。 有关如何创建 SafeHtml 值的更多信息,请参阅 SafeHtml 开发人员指南

在 SafeHtml 消息的消息格式中,不允许在 HTML 标记内使用参数。 例如,以下不是有效的 SafeHml 消息格式,因为 {0} 参数出现在标记的属性内

errorHtmlWithClass = A <span class="{0}">{1} error</span> has occurred.

有关使用 SafeHtml 值的更多信息,请参阅 SafeHtml 开发人员指南