Scala
PlayFramework
playframework2.6

I18N APIの移行 / Play2.6【翻訳】

https://playframework.com/documentation/2.6.x/MessagesMigration26 をgoogle翻訳した


I18N APIの移行

I18N APIには、特にフォームやテンプレートを使用するとメッセージや言語を使いやすくするための変更がいくつかあります。

JavaAPI

インターフェースへのリファクタリングされたメッセージAPI

play.i18nパッケージは Messages に簡単にアクセスできるように変更されました。 これらの変更はユーザーには透過的でなければなりませんが、I18N APIを拡張するチームではここで提供されます。

Messages は現在インターフェイスであり、実装は MessagesImpl に移されました。

非推奨/削除されたメソッド

MessagesApi インスタンスに同等のメソッドが存在するため、 play.i18n.Messages の静的で非推奨のメソッドは2.6.xで削除されました。

Scala API

暗黙のデフォルトLangを削除しました

Lang シングルトンオブジェクトには、JVMのデフォルトロケールを指す defaultLang があります。 2.6.xより前では、 defaultLang は implicit の値でした。これは利用時のローカルスコープでLangが見つからなかった場合、暗黙的に使用されます。この設定はあまりにも一般的であり、request が暗黙的に宣言されていない場合は、request のロケールの代わりに defaultLang が使用されてバグが発生しました。

そこで、暗黙的なものは削除されました。

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

このようになります:

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

この implicit に依存していたコードでは、 Lang.defaultLang を明示的に使用する必要があります。

リファクタリングされたMessaging API はトレイトに

play.api.i18nパッケージが変更され、 Messages インスタンスへのアクセスが容易になり、Play 中の implicits の数が少なくなりました。これらの変更はユーザーには透過的でなければなりませんが、I18N APIを拡張するチームではここで提供されます。

Messages は現在、(case classではなく)トレイトである。 case class は、Messagesをインプリメントする MessagesImpl になりました。

I18nSupport暗黙の変換

Play 2.5からPlay 2.6に直接アップグレードする場合は、 I18nSupport のサポートが2.6.xで変更されていることがわかります。 2.5.xでは、 request が暗黙のスコープであると宣言されていない場合、一連のimplicits を使用して "language default" 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 には、 AbstractController によって公開される MessagesApi インスタンスが含まれます。つまり、 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}!")
      }
    )
  }
}

request.messages と request.lang を追加する I18nSupport にもタイプエンリッチメントが追加されていることに注意してください。これは、 I18nSupport から拡張するか、 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

サポートするために、MessagesProviderを実装し、優先言語を提供するWrappedRequestであるMessagesRequestがあります。

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
    }
}

または、 MessageAbstractController を使用して、ブロック内のRequestではなくMessagesRequestを提供するデフォルトのActionをスワップアウトすることができます。

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は構成と環境を直接使用するため、フォームを扱うのが面倒でした。単体テストの目的で、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 インスタンスに依存しているため、非推奨になりました。

フードの下でグローバル Application を使用していたため、 play.api.mvc.Controller.request2lang メソッドは廃止されました。

play.api.i18n.I18nSupport.request2Messages 暗黙の変換メソッドが I18NSupportLowPriorityImplicits.request2Messages に移動され、全体的に明確な request.messages タイプのエンリッチメントが使用されなくなりました。

暗黙の RequestLang の両方がスコープに入っていると混乱するため、 I18NSupportLowPriorityImplicits.lang2Messages 暗黙の変換が LangImplicits.lang2Messages に移動されました。暗黙的な Lang からメッセージを作成する場合は、特に play.api.i18n.LangImplicits のトレイトを拡張してください。