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

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

---

# I18N APIの移行

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

## JavaAPI

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

play.i18nパッケージは [Messages](https://playframework.com/documentation/2.6.x/api/java/play/i18n/Messages.html) に簡単にアクセスできるように変更されました。 これらの変更はユーザーには透過的でなければなりませんが、I18N APIを拡張するチームではここで提供されます。

[Messages](https://playframework.com/documentation/2.6.x/api/java/play/i18n/Messages.html) は現在インターフェイスであり、実装は [MessagesImpl](https://playframework.com/documentation/2.6.x/api/java/play/i18n/MessagesImpl.html) に移されました。

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

[MessagesApi](https://playframework.com/documentation/2.6.x/api/java/play/i18n/MessagesApi.html) インスタンスに同等のメソッドが存在するため、 [play.i18n.Messages](https://playframework.com/documentation/2.6.x/api/java/play/i18n/Messages.html) の静的で非推奨のメソッドは2.6.xで削除されました。

## Scala API

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

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

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

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

このようになります：

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

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

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

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

[Messages](https://playframework.com/documentation/2.6.x/api/scala/play/api/i18n/Messages.html) は現在、（case classではなく）トレイトである。 case class は、Messagesをインプリメントする [MessagesImpl](https://playframework.com/documentation/2.6.x/api/scala/play/api/i18n/MessagesImpl.html) になりました。

### I18nSupport暗黙の変換

Play 2.5からPlay 2.6に直接アップグレードする場合は、 `I18nSupport` のサポートが2.6.xで変更されていることがわかります。 2.5.xでは、 request が暗黙のスコープであると宣言されていない場合、一連のimplicits を使用して "language default" Messages インスタンスを使用することが可能でした。

```scala
  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](https://playframework.com/documentation/2.6.x/api/scala/play/api/i18n/I18nSupport.html) の暗黙的な変換では、要求の優先ロケールと言語を正しく決定するために、暗黙の要求または要求ヘッダーが有効になりました。

これは、次のことを意味します。

```scala
def index = Action {

}
```

次のように変更する必要があります。

```scala
def index = Action { implicit request =>

}
```

これにより、i18nサポートがリクエストのロケールを確認し、エラーメッセージと検証アラートをユーザーの言語で提供できるようになります。

### よりスムーズなI18nSupport

コントローラ内でフォームを使用すると、2.6.xでよりスムーズな操作ができます。 [ControllerComponents](https://playframework.com/documentation/2.6.x/api/scala/play/api/mvc/ControllerComponents.html) には、 [AbstractController](https://playframework.com/documentation/2.6.x/api/scala/play/api/mvc/AbstractController.html) によって公開される [MessagesApi](https://playframework.com/documentation/2.6.x/api/scala/play/api/i18n/MessagesApi.html) インスタンスが含まれます。つまり、 [I18nSupport](https://playframework.com/documentation/2.6.x/api/scala/play/api/i18n/I18nSupport.html) のトレイトは、Play 2.5.xと同様に、明示的な `val messagesApi：MessagesApi` 宣言を必要としません。

```scala
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](https://playframework.com/documentation/2.6.x/api/scala/play/api/i18n/MessagesProvider.html) トレイトが利用可能で、これはMessagesインスタンスを公開します。

```scala
trait MessagesProvider {
  def messages: Messages
}
```

[MessagesImpl](https://playframework.com/documentation/2.6.x/api/scala/play/api/i18n/MessagesImpl.html) は Messages と MessagesProvider を実装し、デフォルトで自身を返します。

すべてのテンプレートヘルパーは、そのままの Messages オブジェクトではなく、暗黙のパラメータとして MessagesProvider を受け取ります。つまり、 `inputText.scala.html` は次のようになります。

```scala
@(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にアクセスできます。

```scala
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](https://playframework.com/documentation/2.6.x/api/scala/play/api/mvc/MessagesAbstractController.html) を使用して、ブロック内のRequestではなくMessagesRequestを提供するデフォルトのActionをスワップアウトすることができます。


```scala
class MyController @Inject() (
  mcc: MessagesControllerComponents
) extends MessagesAbstractController(mcc) {

  def index = Action { implicit request: MessagesRequest[AnyContent] =>
      Ok(s"The messages are ${request.messages}")
  }
}
```

次に、CSRFアクションを含むフォームを使用した完全な例を示します（CSRFフィルタが無効になっていることを前提とします）。

```scala
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ページは次のとおりです。

```scala

@(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を使用できます。

詳細については、 [フォームヘルパーにメッセージを渡す](https://playframework.com/documentation/2.6.x/ScalaForms#Passing-MessagesProvider-to-Form-Helpers) を参照してください。

### DefaultMessagesApiコンポーネント

MessagesApiのデフォルト実装はDefaultMessagesApiです。 DefaultMessagesApiは構成と環境を直接使用するため、フォームを扱うのが面倒でした。単体テストの目的で、DefaultMessagesApiは引数なしでインスタンス化することができ、生のマップを取得します。

```scala
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を取得することをお勧めします。

```scala

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` タイプのエンリッチメントが使用されなくなりました。

暗黙の `Request` と `Lang` の両方がスコープに入っていると混乱するため、 `I18NSupportLowPriorityImplicits.lang2Messages` 暗黙の変換が `LangImplicits.lang2Messages` に移動されました。暗黙的な `Lang` からメッセージを作成する場合は、特に [play.api.i18n.LangImplicits](https://playframework.com/documentation/2.6.x/api/scala/play/api/i18n/LangImplicits.html) のトレイトを拡張してください。