§Internationalization with Messages
§Specifying languages supported by your application
You specify languages for your application using language tags, specially formatted strings that identify a specific language. Language tags can specify simple languages, such as “en” for English, a specific regional dialect of a language (such as “en-AU” for English as used in Australia), a language and a script (such as “az-Latn” for Azerbaijani written in Latin script), or a combination of several of these (such as “zh-cmn-Hans-CN” for Chinese, Mandarin, Simplified script, as used in China).
To start you need to specify the languages supported by your application in the conf/application.conf
file:
play.i18n.langs = [ "en", "en-US", "fr" ]
These language tags will be used to create play.api.i18n.Lang
instances. To access the languages supported by your application, you can inject a play.api.i18n.Langs
component into your class:
import javax.inject.Inject
import play.api.i18n.Lang
import play.api.i18n.Langs
import play.api.mvc.Action
import play.api.mvc.AnyContent
import play.api.mvc.BaseController
import play.api.mvc.ControllerComponents
class ScalaI18nService @Inject() (langs: Langs) {
val availableLangs: Seq[Lang] = langs.availables
}
An individual play.api.i18n.Lang
can be converted to a java.util.Locale
object by using lang.toLocale
:
val locale: java.util.Locale = lang.toLocale
§Externalizing messages
You can externalize messages in the conf/messages.xxx
files.
The default conf/messages
file matches all languages. Additionally you can specify language-specific message files such as conf/messages.fr
or conf/messages.en-US
.
Messages are available through the MessagesApi
instance, which can be added via injection. You can then retrieve messages using the play.api.i18n.MessagesApi
object:
import play.api.i18n.MessagesApi
class MyService @Inject() (langs: Langs, messagesApi: MessagesApi) {
val lang: Lang = langs.availables.head
val title: String = messagesApi("home.title")(lang)
}
You can also make the language implicit rather than declare it:
class MyOtherService @Inject() (langs: Langs, messagesApi: MessagesApi) {
implicit val lang: Lang = langs.availables.head
lazy val title: String = messagesApi("home.title")
}
Play provides predefined messages for forms validation. You can overwrite these messages either with the default message file or any language-specific message file. You can see below which messages can be overwritten:
# Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
# Default messages
# --- Constraints
constraint.required=Required
constraint.min=Minimum value: {0}
constraint.max=Maximum value: {0}
constraint.minLength=Minimum length: {0}
constraint.maxLength=Maximum length: {0}
constraint.email=Email
constraint.pattern=Required pattern: {0}
# --- Formats
format.date=Date (''{0}'')
format.numeric=Numeric
format.real=Real
format.uuid=UUID
# --- Patterns for Formats
formats.date=yyyy-MM-dd
# --- Errors
error.invalid=Invalid value
error.invalid.java.util.Date=Invalid date value
error.required=This field is required
error.number=Numeric value expected
error.real=Real number value expected
error.real.precision=Real number value with no more than {0} digit(s) including {1} decimal(s) expected
error.min=Must be greater or equal to {0}
error.min.strict=Must be strictly greater than {0}
error.max=Must be less or equal to {0}
error.max.strict=Must be strictly less than {0}
error.minLength=Minimum length is {0}
error.maxLength=Maximum length is {0}
error.email=Valid email required
error.pattern=Must satisfy {0}
error.date=Valid date required
error.uuid=Valid UUID required
error.expected.date=Date value expected
error.expected.date.isoformat=Iso date value expected
error.expected.time=Time value expected
error.expected.jsarray=Array value expected
error.expected.jsboolean=Boolean value expected
error.expected.jsnumber=Number value expected
error.expected.jsobject=Object value expected
error.expected.jsstring=String value expected
error.expected.jsnumberorjsstring=String or number expected
error.expected.keypathnode=Node value expected
error.expected.uuid=UUID value expected
error.expected.validenumvalue=Valid enumeration value expected
error.expected.enumstring=String value expected
error.path.empty=Empty path
error.path.missing=Missing path
error.path.result.multiple=Multiple results for the given path
§Using Messages and MessagesProvider
Because it’s common to want to use messages without having to provide an argument, you can wrap a given Lang
together with the MessagesApi
to create a play.api.i18n.Messages
instance. The play.api.i18n.MessagesImpl
case class implements the Messages
trait if you want to create one directly:
val messages: Messages = MessagesImpl(lang, messagesApi)
val title: String = messages("home.title")
You can also use Singleton object methods with an implicit play.api.i18n.MessagesProvider
:
implicit val messagesProvider: MessagesProvider = {
MessagesImpl(lang, messagesApi)
}
// uses implicit messages
val title2 = Messages("home.title")
A play.api.i18n.MessagesProvider
is a trait that can provide a Messages
object on demand. An instance of Messages
extends MessagesProvider
and returns itself.
MessagesProvider
is most useful when extended by something that is not a Messages
:
implicit val customMessagesProvider: MessagesProvider = new MessagesProvider {
// resolve messages at runtime
override def messages: Messages = { ... }
}
// uses implicit messages
val title3: String = Messages("home.title")
§Using Messages with Controllers
You can add Messages
support to your request by extending MessagesAbstractController
or MessagesBaseController
:
import javax.inject.Inject
import play.api.i18n._
class MyMessagesController @Inject() (mcc: MessagesControllerComponents) extends MessagesAbstractController(mcc) {
def index = Action { implicit request: MessagesRequest[AnyContent] =>
val messages: Messages = request.messages
val message: String = messages("info.error")
Ok(message)
}
def messages2 = Action { implicit request: MessagesRequest[AnyContent] =>
val lang: Lang = request.messages.lang
val message: String = messagesApi("info.error")(lang)
Ok(message)
}
def messages4 = Action { implicit request: MessagesRequest[AnyContent] =>
// MessagesRequest is an implicit MessagesProvider
Ok(views.html.formpage())
}
}
Or by adding the play.api.i18n.I18nSupport
trait to your controller and ensuring an instance of MessagesApi
is in scope, which will use implicits to convert a request.
import javax.inject.Inject
import play.api.i18n._
class MySupportController @Inject() (val controllerComponents: ControllerComponents)
extends BaseController
with I18nSupport {
def index: Action[AnyContent] = Action { implicit request =>
// type enrichment through I18nSupport
val messages: Messages = request.messages
val message: String = messages("info.error")
Ok(message)
}
def messages2: Action[AnyContent] = Action { implicit request =>
// type enrichment through I18nSupport
val lang: Lang = request.lang
val message: String = messagesApi("info.error")(lang)
Ok(message)
}
def messages3: Action[AnyContent] = Action { request =>
// direct access with no implicits required
val messages: Messages = messagesApi.preferred(request)
val lang = messages.lang
val message: String = messages("info.error")
Ok(message)
}
def messages4: Action[AnyContent] = Action { implicit request =>
// takes implicit Messages, converted using request2messages
// template defined with @()(implicit messages: Messages)
Ok(views.html.formpage())
}
}
All the form helpers in Twirl templates take MessagesProvider
, and it is assumed that a MessagesProvider
is passed into the template as an implicit parameter when processing a form.
@(form: Form[Foo])(implicit messages: MessagesProvider)
@helper.inputText(field = form("name")) @* <- takes MessagesProvider *@
§Retrieving supported language from an HTTP request
You can retrieve the languages supported by a specific HTTP request:
def index: Action[AnyContent] = Action { request =>
Ok("Languages: " + request.acceptLanguages.map(_.code).mkString(", "))
}
§Request Types
The I18nSupport
trait adds the following methods to a Request
:
request.messages
returns an instance ofMessages
, using an implicitMessagesApi
request.lang
returns the preferredLang
, using an implicitMessagesApi
The preferred language is extracted from the Accept-Language
header (and optionally the language cookie) and matching one of the MessagesApi
supported languages using messagesApi.preferred
.
§Language Cookie Support
The I18nSupport
also adds two convenient methods to Result
:
result.withLang(lang: Lang)
is used to set the language using Play’s language cookie.result.withoutLang
is used to clear the language cookie.
For example:
def homePageInFrench: Action[AnyContent] = Action {
Redirect("/user/home").withLang(Lang("fr"))
}
def homePageWithDefaultLang: Action[AnyContent] = Action {
Redirect("/user/home").withoutLang
}
The withLang
method sets the cookie named PLAY_LANG
for future requests, while withoutLang discards the cookie, and Play will choose the language based on the client’s Accept-Language header.
The cookie name can be changed by changing the configuration parameter: play.i18n.langCookieName
.
§Implicit Lang Conversion
The LangImplicits
trait can be declared on a controller to implicitly convert a request to a Messages
given an implicit Lang
instance.
import play.api.i18n.LangImplicits
class MyClass @Inject() (val messagesApi: MessagesApi) extends LangImplicits {
def convertToMessage: Unit = {
implicit val lang: Lang = Lang("en")
val messages: Messages = lang2Messages // implicit conversion
}
}
§Messages format
Messages are formatted using the java.text.MessageFormat
library. For example, assuming you have message defined like:
files.summary=The disk {1} contains {0} file(s).
You can then specify parameters as:
Messages("files.summary", d.files.length, d.name)
§Notes on apostrophes
Since Messages uses java.text.MessageFormat
, please be aware that single quotes are used as a meta-character for escaping parameter substitutions.
For example, if you have the following messages defined:
info.error=You aren''t logged in!
example.formatting=When using MessageFormat, '''{0}''' is replaced with the first parameter.
you should expect the following results:
messagesApi("info.error") == "You aren't logged in!"
messagesApi("example.formatting") == "When using MessageFormat, '{0}' is replaced with the first parameter."
§Explicit MessagesApi
The default implementation of MessagesApi
is DefaultMessagesApi
. You can see unit testing and functional testing examples in the testing section of the documentation.
You can also use Helpers.stubMessagesApi()
in testing to provide a premade empty MessagesApi
.
Next: Dependency Injection