§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.messages
和 request.lang
。這可以透過從 I18nSupport
延伸,或透過 import I18nSupport._
來新增。匯入版本不包含 request2messages
隱式轉換。
§使用 MessagesProvider 整合訊息
新的 MessagesProvider
特質可用,它公開了一個 Messages
執行個體。
trait MessagesProvider {
def messages: Messages
}
MessagesImpl
實作了 Messages
和 MessagesProvider
,並預設傳回它自己。
所有範本輔助程式現在都將 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}!")
}
)
}
}
}
由於 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
的預設實作是 DefaultMessagesApi
。 DefaultMessagesApi
過去直接使用 Configuration
和 Environment
,這使得在表單中處理它很麻煩。對於單元測試目的,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.applicationMessagesApi
和 play.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 移轉
在此文件中發現錯誤?此頁面的原始程式碼可以在 此處 找到。閱讀 文件準則 後,請隨時提交拉取請求。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。