§I18N API 迁移
I18N API 有许多更改,使使用消息和语言更容易,尤其是在表单和模板中。
§Java API
§将消息 API 重构为接口
play.i18n
包已更改,以便更容易访问 Messages
。这些更改对于用户来说应该是透明的,但这里提供了这些更改,供扩展 I18N API 的团队使用。
Messages
现在是一个接口,并且有一个 MessagesImpl
类实现该接口。
§已弃用/已删除的方法
在 2.6.x 中,play.i18n.Messages
中的静态弃用方法已被删除,因为 MessagesApi
实例上存在等效方法。
§Scala API
§已删除隐式默认语言
Lang
单例对象有一个 defaultLang
,它指向 JVM 默认区域设置。在 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
。
§将消息 API 重构为特征
play.api.i18n
包已更改,以简化对 Messages
实例的访问,并减少 Play 中的隐式数量。这些更改对用户应该是透明的,但这里提供给扩展 I18N API 的团队。
Messages
现在是一个特质(而不是一个 case 类)。case 类现在是 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
,它是一个 WrappedRequest
,它实现了 MessagesProvider
并提供首选语言。
您可以通过使用 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
}
}
或者,您可以使用 MessagesAbstractController
,它将默认的 Action
替换为提供 MessagesRequest
而不是 Request
的 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
的主体与模板无关,因此我们可以在这里使用 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 迁移
发现此文档中的错误?此页面的源代码可以在这里找到 这里。在阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。