Due to the wide reach of the Internet, it is now possible to communicate and interact with people from diverse locations. An application that communicates with users in one specific language restricts its user base through the use of only that language. Internationalization and localization can be used to cater to user groups from various regions by removing barriers that arise due to the use of a particular language only.
Now, let's build a simple view, which allows us to ask a question. The views/index.scala.html
view file will be similar to the following:
@(enquiryForm: Form[(String, Option[String], String)]) @import helper._ @main("Enquiry") { <div> <h2>Have a question? Ask Us</h2> @form(routes.AppController.enquire) { @enquiryForm.globalError.map { error => <p> @error.message </p> } <label for="emailId">Your email address <input type="email" id="emailId" name="emailId" required> </label> <label for="userName">Your name <input type="text" class="form-control" id="userName" name="userName"> </label> <label for="question">Your question <textarea rows="4" id="question" name="question"></textarea> </label> <br/> <button type="submit">Ask</button> } </div> }
Here, AppController
is a controller and is defined as follows:
package controllers import play.api.mvc._ import play.api.data.Form import play.api.data.Forms._ object AppController extends Controller { val enquiryForm = Form( tuple( "emailId" -> email, "userName" -> optional(text), "question" -> nonEmptyText) ) def index = Action { implicit request => Redirect(routes.AppController.askUs) } def askUs = Action { implicit request => Ok(views.html.index(enquiryForm)) } def enquire = Action { implicit request => enquiryForm.bindFromRequest.fold( errors => BadRequest(views.html.index(errors)), query => { println(query.toString) Redirect(routes.AppController.askUs) } ) } }
The main template
views/main.scala.html
is defined as follows:
@(title: String)(content: Html) <!DOCTYPE html> <html> <head> <title>@title</title> </head> <body> @content </body> </html>
The routes for the application are defined as follows:
# Home page GET / controllers.AppController.index # Other GET /ask controllers.AppController.askUs POST /enquire controllers.AppController.enquire
Now when we start the application, with the help of a little bit of styling (CSS styles), our view looks similar to this:
We might want our application to be available in both English and French. Therefore, having different views for different languages is a bad idea. This would mean that every time the support for a language is included, we would need to define all the views in our application in this particular language as well. Using Play's i18n support, supporting another language can be as simple as adding a file that contains translations.
Firstly, we will need to specify the languages supported by our application in conf/application.conf
. Notice that this is commented code in the default conf/application.conf
, which indicates the following:
# The application languages # ~~~~~ # application.langs="en"
The format in which the language should be specified is its ISO 639-2 code, optionally followed by an ISO 3166-1 alpha-2 country code. You can include French as well, as shown here:
application.langs="en,fr"
In Play, the translations required for content to be rendered in a particular language are called messages. For each language, we need to provide a conf/messages.lang-code
file. If we wish to have common content, we should define it in conf/messages
; this can be quite useful for names, branding, and so on.
Let's create a
messages
file for English called conf/messages.en
:
enquiry.title = Enquiry enquiry.askUs=Have A Question? Ask Us! enquiry.user.email=Your email address enquiry.user.name=Your name enquiry.question=Your question enquiry.submit=Ask
Now we need to update our view to use these messages, in the form of @(enquiryForm: Form[(String, Option[String], String)])(implicit lang: Lang)
:
@import helper._ @main(Messages("enquiry.title")) { <div> <h2>@Messages("enquiry.askUs")</h2> @form(routes.AppController.enquire) { @enquiryForm.globalError.map { error => <p> @error.message </p> } <label for="emailId">@Messages("enquiry.user.email") <input type="email" id="emailId" name="emailId" required> </label> <label for="userName">@Messages("enquiry.user.name") <input type="text" class="form-control" id="userName" name="userName"> </label> <label for="question">@Messages("enquiry.question") <textarea rows="4" id="question" name="question"></textarea> </label> <br/> <button type="submit">@Messages("enquiry.submit")</button> } </div> }
Now, let's add the French messages
file, conf/messages.fr
:
enquiry.title = Demande de renseignements enquiry.askUs = Vous avez une question? Demandez-nous! enquiry.user.email = Votre adresse e-mail enquiry.user.name = Votre nom enquiry.question = Votre question enquiry.submit = Demandez
Change your browser settings so that you have French (fr) enabled as the primary language and run the application. You should be able to see the enquiry view in French:
We can also use the messages within the Scala code after importing play.api.i18n
:
val title = Messages("enquiry.title")
When we use Messages
(word) in our code, it calls the apply
method of the play.api.i18n.Messages
object. The apply
method is defined as follows:
def apply(key: String, args: Any*)(implicit lang: Lang): String = { Play.maybeApplication.flatMap { app => app.plugin[MessagesPlugin].map(_.api.translate(key, args)).getOrElse(throw new Exception("this plugin was not registered or disabled")) }.getOrElse(noMatch(key, args)) }
Play has an internal plugin called the MessagesPlugin
, defined as follows:
class MessagesPlugin(app: Application) extends Plugin { import scala.collection.JavaConverters._ import scalax.file._ import scalax.io.JavaConverters._ private def loadMessages(file: String): Map[String, String] = { app.classloader.getResources(file).asScala.toList.reverse.map { messageFile => new Messages.MessagesParser(messageFile.asInput, messageFile.toString).parse.map { message => message.key -> message.pattern }.toMap }.foldLeft(Map.empty[String, String]) { _ ++ _ } } private lazy val messages = { MessagesApi { Lang.availables(app).map(_.code).map { lang => (lang, loadMessages("messages." + lang)) }.toMap + ("default" -> loadMessages("messages")) } } //The underlying internationalization API. def api = messages //Loads all configuration and message files defined in the classpath. override def onStart() { messages } }
This plugin is responsible for loading all the messages and generating a MessagesApi
object, which is later used to fetch the value of a message. So, when we refer to a message, it's fetched from this instance of MessagesApi
. MessagesApi
and is defined as follows:
case class MessagesApi(messages: Map[String, Map[String, String]]) { import java.text._ //Translates a message. def translate(key: String, args: Seq[Any])(implicit lang: Lang): Option[String] = { val langsToTry: List[Lang] = List(lang, Lang(lang.language, ""), Lang("default", ""), Lang("default.play", "")) val pattern: Option[String] = langsToTry.foldLeft[Option[String]](None)((res, lang) => res.orElse(messages.get(lang.code).flatMap(_.get(key)))) pattern.map(pattern => new MessageFormat(pattern, lang.toLocale).format(args.map(_.asInstanceOf[java.lang.Object]).toArray)) } //Check if a message key is defined. def isDefinedAt(key: String)(implicit lang: Lang): Boolean = { val langsToTry: List[Lang] = List(lang, Lang(lang.language, ""), Lang("default", ""), Lang("default.play", "")) langsToTry.foldLeft[Boolean](false)({ (acc, lang) => acc || messages.get(lang.code).map(_.isDefinedAt(key)).getOrElse(false) }) } }
3.137.188.201