文档

§I18N API 迁移

I18N API 有许多更改,使使用消息和语言更容易,尤其是在表单和模板中。

§Java API

§将消息 API 重构为接口

play.i18n 包已更改,以便更容易访问 Messages。这些更改对于用户来说应该是透明的,但这里提供了这些更改,供扩展 I18N API 的团队使用。

Messages 现在是一个接口,并且有一个 MessagesImpl 类实现该接口。

§已弃用/已删除的方法

在 2.6.x 中,play.i18n.Messages 中的静态弃用方法已被删除,因为 MessagesApi 实例上存在等效方法。

§Scala API

§已删除隐式默认语言

Lang 单例对象有一个 defaultLang,它指向 JVM 默认区域设置。在 2.6.x 之前,defaultLang 是一个隐式值,因此如果在本地范围内未找到 Lang,它可以在隐式范围解析中使用。此设置过于笼统,导致错误,其中如果请求未声明为隐式,则使用 defaultLang 而不是请求的区域设置。

因此,已删除隐式,因此

object Lang {
  implicit lazy val defaultLang: Lang = Lang(java.util.Locale.getDefault)
}

现在是

object Lang {
  lazy val defaultLang: Lang = Lang(java.util.Locale.getDefault)
}

任何依赖此隐式的代码都应显式使用 Lang.defaultLang

§将消息 API 重构为特征

play.api.i18n 包已更改,以简化对 Messages 实例的访问,并减少 Play 中的隐式数量。这些更改对用户应该是透明的,但这里提供给扩展 I18N API 的团队。

Messages 现在是一个特质(而不是一个 case 类)。case 类现在是 MessagesImpl,它实现了 Messages

§I18nSupport 隐式转换

如果您直接从 Play 2.5 升级到 Play 2.6,您应该知道 I18nSupport 支持在 2.6.x 中发生了变化。在 2.5.x 中,可以通过一系列隐式来使用“语言默认”Messages 实例,如果请求未声明在隐式范围内。

  def listWidgets = Action {
    val lang = implicitly[Lang] // Uses Lang.defaultLang
    val messages = implicitly[Messages] // Uses I18nSupport.lang2messages(Lang.defaultLang)
    // implicit parameter messages: Messages in requiresMessages template, but no request!
    val content = views.html.requiresMessages(form)
    Ok(content)
  }

I18nSupport 隐式转换现在需要一个隐式请求或请求头在范围内,以便正确确定请求的首选区域设置和语言。

这意味着如果您有以下内容

def index = Action {

}

您需要将其更改为

def index = Action { implicit request =>

}

这将允许 i18n 支持查看请求的区域设置,并以用户的语言提供错误消息和验证警报。

§更流畅的 I18nSupport

在 2.6.x 中,在控制器内使用表单是一种更流畅的体验。 ControllerComponents 包含一个 MessagesApi 实例,它由 AbstractController 公开。这意味着 I18nSupport 特质不需要显式 val messagesApi: MessagesApi 声明,就像它在 Play 2.5.x 中一样。

class FormController @Inject()(components: ControllerComponents)
  extends AbstractController(components) with I18nSupport {

  import play.api.data.validation.Constraints._

  val userForm = Form(
    mapping(
      "name" -> text.verifying(nonEmpty),
      "age" -> number.verifying(min(0), max(100))
    )(UserData.apply)(UserData.unapply)
  )

  def index = Action { implicit request =>
    // use request2messages implicit conversion method
    Ok(views.html.user(userForm))
  }

  def showMessage = Action { request =>
    // uses type enrichment
    Ok(request.messages("hello.world"))
  }

  def userPost = Action { implicit request =>
    userForm.bindFromRequest.fold(
      formWithErrors => {
        BadRequest(views.html.user(formWithErrors))
      },
      user => {
        Redirect(routes.FormController.index()).flashing("success" -> s"User is ${user}!")
      }
    )
  }
}

请注意,现在 I18nSupport 中也有类型增强,它添加了 request.messagesrequest.lang。这可以通过扩展 I18nSupport 或通过 import I18nSupport._ 来添加。导入版本不包含 request2messages 隐式转换。

§与 MessagesProvider 集成的消息

提供了一个新的 MessagesProvider 特质,它公开了一个 Messages 实例。

trait MessagesProvider {
  def messages: Messages
}

MessagesImpl 实现了 MessagesMessagesProvider,默认情况下返回自身。

所有模板助手现在都将 MessagesProvider 作为隐式参数,而不是直接的 Messages 对象,例如 inputText.scala.html 接受以下内容

@(field: play.api.data.Field, args: (Symbol,Any)*)(implicit handler: FieldConstructor, messagesProvider: play.api.i18n.MessagesProvider)

使用 MessagesProvider 的好处是,否则,如果您使用隐式 Messages,则必须在可能导致混淆的隐式转换的地方引入来自其他类型(如 Request)的隐式转换。

§MessagesRequest 和 MessagesAbstractController

为了帮助您,有 MessagesRequest,它是一个 WrappedRequest,它实现了 MessagesProvider 并提供首选语言。

您可以通过使用 MessagesActionBuilder 来访问 MessagesRequest


class MyController @Inject()( messagesAction: MessagesActionBuilder, cc: ControllerComponents ) extends AbstractController(cc) { def index = messagesAction { implicit request: MessagesRequest[AnyContent] => Ok(views.html.formTemplate(form)) // twirl template with form builders } }

或者,您可以使用 MessagesAbstractController,它将默认的 Action 替换为提供 MessagesRequest 而不是 RequestAction


class MyController @Inject() ( mcc: MessagesControllerComponents ) extends MessagesAbstractController(mcc) { def index = Action { implicit request: MessagesRequest[AnyContent] => Ok(s"The messages are ${request.messages}") } }

以下是一个使用表单和 CSRF 操作的完整示例(假设您已禁用 CSRF 过滤器)


class MyController @Inject() ( addToken: CSRFAddToken, checkToken: CSRFCheck, mcc: MessagesControllerComponents ) extends MessagesAbstractController(mcc) { import play.api.data.Form import play.api.data.Forms._ val userForm = Form( mapping( "name" -> text, "age" -> number )(UserData.apply)(UserData.unapply) ) def index = addToken { Action { implicit request => Ok(views.html.formpage(userForm)) } } def userPost = checkToken { Action { implicit request => userForm.bindFromRequest.fold( formWithErrors => { play.api.Logger.info(s"unsuccessful user submission") BadRequest(views.html.formpage(formWithErrors)) }, user => { play.api.Logger.info(s"successful user submission ${user}") Redirect(routes.MyController.index()).flashing("success" -> s"User is ${user}!") } ) } } }

因为 MessagesRequest 是一个 MessagesProvider,您只需要将请求定义为隐式,它就会传递到模板中。这在涉及 CSRF 检查时特别有用。formpage.scala.html 页面如下所示


@(userForm: Form[UserData])(implicit request: MessagesRequestHeader) @helper.form(action = routes.MyController.userPost()) { @views.html.helper.CSRF.formField @helper.inputText(userForm("name")) @helper.inputText(userForm("age")) <input type="submit" value="SUBMIT"/> }

请注意,由于 MessageRequest 的主体与模板无关,因此我们可以在这里使用 MessagesRequestHeader 而不是 MessageRequest[_]

有关更多详细信息,请参阅 将消息传递给表单助手

§DefaultMessagesApi 组件

MessagesApi 的默认实现是 DefaultMessagesApiDefaultMessagesApi 过去直接使用 ConfigurationEnvironment,这使得在表单中处理它变得很麻烦。为了进行单元测试,DefaultMessagesApi 可以不带参数实例化,并将使用原始映射。


import play.api.data.Forms._ import play.api.data._ import play.api.i18n._ val messagesApi = new DefaultMessagesApi( Map("en" -> Map("error.min" -> "minimum!") ) ) implicit val request = { play.api.test.FakeRequest("POST", "/") .withFormUrlEncodedBody("name" -> "Play", "age" -> "-1") } implicit val messages = messagesApi.preferred(request) def errorFunc(badForm: Form[UserData]) = { BadRequest(badForm.errorsAsJson) } def successFunc(userData: UserData) = { Redirect("/").flashing("success" -> "success form!") } val result = Future.successful(form.bindFromRequest().fold(errorFunc, successFunc)) Json.parse(contentAsString(result)) must beEqualTo(Json.obj("age" -> Json.arr("minimum!")))

对于涉及配置的功能测试,最佳选择是使用 WithApplication 并拉入注入的 MessagesApi


import play.api.test.{ PlaySpecification, WithApplication } import play.api.i18n._ class MessagesSpec extends PlaySpecification { sequential implicit val lang = Lang("en-US") "Messages" should { "provide default messages" in new WithApplication(_.requireExplicitBindings()) { val messagesApi = app.injector.instanceOf[MessagesApi] val javaMessagesApi = app.injector.instanceOf[play.i18n.MessagesApi] val msg = messagesApi("constraint.email") val javaMsg = javaMessagesApi.get(new play.i18n.Lang(lang), "constraint.email") msg must ===("Email") msg must ===(javaMsg) } "permit default override" in new WithApplication(_.requireExplicitBindings()) { val messagesApi = app.injector.instanceOf[MessagesApi] val msg = messagesApi("constraint.required") msg must ===("Required!") } } }

如果您需要自定义配置,最好将配置值添加到 GuiceApplicationBuilder 中,而不是直接使用 DefaultMessagesApiProvider

§已弃用方法

play.api.i18n.Messages.Implicits.applicationMessagesApiplay.api.i18n.Messages.Implicits.applicationMessages 已被弃用,因为它们依赖于隐式 Application 实例。

play.api.mvc.Controller.request2lang 方法已被弃用,因为它在幕后使用全局 Application

play.api.i18n.I18nSupport.request2Messages 隐式转换方法已移至 I18NSupportLowPriorityImplicits.request2Messages,并已弃用,建议使用更清晰的 request.messages 类型增强。

I18NSupportLowPriorityImplicits.lang2Messages 隐式转换已移至 LangImplicits.lang2Messages,因为当隐式 Request 和 Lang 都在范围内时会导致混淆。如果您想从隐式 Lang 创建 Messages,请专门扩展 play.api.i18n.LangImplicits 特性。

下一步:WS 迁移


发现此文档中的错误?此页面的源代码可以在这里找到 这里。在阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。