§I18N APIの移行
メッセージや言語の扱いを、特にフォームやテンプレートでより使いやすくするために、I18N APIにはいくつかの変更が加えられています。
§Java API
§インターフェースへのMessages APIのリファクタリング
Messages
へのアクセスを容易にするために、play.i18n
パッケージが変更されました。これらの変更はユーザーには透過的であるはずですが、I18N APIを拡張するチームのためにここに提供されています。
Messages
はインターフェースとなり、そのインターフェースを実装するMessagesImpl
クラスがあります。
§非推奨/削除されたメソッド
play.i18n.Messages
の静的非推奨メソッドは2.6.xで削除されました。これは、MessagesApi
インスタンスに同等のメソッドがあるためです。
§Scala API
§暗黙のデフォルト言語の削除
Lang
シングルトンオブジェクトには、JVMのデフォルトロケールを指すdefaultLang
があります。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
を明示的に使用する必要があります。
§トレイトへのMessages APIのリファクタリング
Messages
インスタンスへのアクセスを容易にし、Play内の暗黙の設定の数を減らすために、play.api.i18n
パッケージが変更されました。これらの変更はユーザーには透過的であるはずですが、I18N APIを拡張するチームのためにここに提供されています。
Messages
は(ケースクラスではなく)トレイトになりました。ケースクラスはMessagesImpl
になり、Messages
を実装しています。
§I18nSupportの暗黙の変換
Play 2.5からPlay 2.6に直接アップグレードする場合は、2.6.xでI18nSupport
のサポートが変更されたことを知っておく必要があります。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
トレイトが、Play 2.5.xで行っていたように、明示的なval messagesApi: MessagesApi
宣言を必要としないことを意味します。
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
を実装し、デフォルトで自身を返します。
すべてのテンプレートヘルパーは、ストレートなMessages
オブジェクトではなく、暗黙のパラメーターとしてMessagesProvider
を受け取るようになりました。例えば、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
です。
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
}
}
または、ブロック内でRequest
の代わりにMessagesRequest
を提供するデフォルトのAction
を入れ替えるMessagesAbstractController
を使用することもできます。
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
の本文はテンプレートには関係ないため、ここではMessageRequest[_]
の代わりにMessagesRequestHeader
を使用できることに注意してください。
詳細については、フォームヘルパーへのメッセージの渡し方を参照してください。
§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!")
}
}
}
構成をカスタマイズする必要がある場合は、DefaultMessagesApiProvider
を直接使用するよりも、構成値をGuiceApplicationBuilder
に追加することをお勧めします。
§非推奨のメソッド
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
暗黙的な変換は、暗黙的な Request と Lang の両方がスコープ内にある場合の混乱を避けるため、LangImplicits.lang2Messages
に移動されました。暗黙的な Lang
から Messages
を作成したい場合は、明示的に play.api.i18n.LangImplicits
トレイトを拡張してください。
次: WS マイグレーション
このドキュメントにエラーを見つけましたか? このページのソースコードは こちら にあります。 ドキュメントガイドライン を読んだ後、お気軽にプルリクエストを送信してください。質問や共有したいアドバイスがありますか? コミュニティフォーラム にアクセスして、コミュニティとの会話を始めましょう。