文件

§I18N API 遷移

I18N API 有許多變更,讓訊息和語言更容易使用,特別是在表單和範本中。

§Java API

§將 Messages API 重構為介面

play.i18n 套件已變更,讓存取 Messages 更容易。這些變更對使用者來說應該是透明的,但在此提供給擴充 I18N API 的團隊參考。

Messages 現在是一個介面,有一個 MessagesImpl 類別實作該介面。

§已棄用/已移除的方法

play.i18n.Messages 中的靜態已棄用方法已在 2.6.x 中移除,因為在 MessagesApi 執行個體上有等效的方法。

§Scala API

§移除隱含預設語言

Lang 單例物件有一個 defaultLang 指向 JVM 預設的 Locale。在 2.6.x 之前,defaultLang 是隱含值,結果是如果在區域範圍內找不到 Lang,它可以在隱含範圍解析中使用。這個設定太過於一般化,導致錯誤,如果請求未宣告為隱含,則會使用 defaultLang 而不是請求的 locale。

因此,已移除隱含,所以

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 現在是特質(而非案例類別)。案例類別現在是 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,它是一個實作了 MessagesProvider 並提供首選語言的 WrappedRequest

您可以透過使用 MessagesRequest 來存取 MessagesActionBuilder


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 而不是 Request


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}!") } ) } } }

由於 MessagesRequestMessagesProvider,因此您只需將要求定義為內隱,它就會傳遞到範本。這在涉及 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 移轉


在此文件中發現錯誤?此頁面的原始程式碼可以在 此處 找到。閱讀 文件準則 後,請隨時提交拉取請求。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。