§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 マイグレーション
このドキュメントにエラーを見つけましたか? このページのソースコードは こちら にあります。 ドキュメントガイドライン を読んだ後、お気軽にプルリクエストを送信してください。質問や共有したいアドバイスがありますか? コミュニティフォーラム にアクセスして、コミュニティとの会話を始めましょう。