Internationalization

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:

Internationalization

Supporting views in multiple languages

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:

Supporting views in multiple languages

We can also use the messages within the Scala code after importing play.api.i18n:

val title = Messages("enquiry.title")

Understanding internationalization

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)
    })
  }

}

Note

The implicit lang parameter is the key to get messages in the accepted language.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.240.244