Documentation

§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:

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.

The I18nSupport also adds two convenient methods to Result:

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


Found an error in this documentation? The source code for this page can be found here. After reading the documentation guidelines, please feel free to contribute a pull request. Have questions or advice to share? Go to our community forums to start a conversation with the community.