CHAPTER 7

Internationalization

One of the great things about web applications is that they are really easy to distribute to a lot of people. When deploying web applications to a broad audience, often the applications need to adapt and behave differently under certain circumstances. For example, when a request from Spain is made to a web application, the application may want to display messages to the user in Spanish, but the same application will want to render messages in English if the request comes from New York. The adaptations made by the application may involve more complexity than simply displaying different versions of text. An application may need to impose different business rules based on the origin of a particular request.

Grails provides a number of mechanisms for dealing with the internationalization and localization of a web application. In this chapter, we will explore those mechanisms, and you will see that internationalizing a web application does not have to be terribly difficult.

Localizing Messages

When deploying a Grails application to a broad audience, you may want the application to display messages in the user's preferred language. One way of providing this capability is to have a separate version of the application for each language you want to target. That approach has lots of problems. Maintaining all those different versions and trying to keep them all in sync would be an awful lot of work. A much better idea is to have a single version of the application that is flexible enough to display messages in various languages using localized messages.

To support localized messages in your Grails application, you should be defining all user messages in a properties file. So, user messages should not be hard-coded in GSP pages, GSP templates, or anywhere else. Having messages in a properties file means you have a single place to maintain all of them. It also lets you take advantage of the localization capabilities provided by Grails.

Defining User Messages

When a Grails app is created, the project includes a number of localized property files in the grails-app/i18n/ directory. Figure 7-1 shows the contents of the grails-app/i18n/ directory.

image

Figure 7-1. The grails-app/i18n/directory

The messages.properties file in the grails-app/i18n/ directory contains default validation messages in English. These messages are used when validation fails in a domain class or command object. You can add your own application messages to this file. In addition to the default messages.properties file, this directory has several other properties files that contain the same messages in other languages. For example, "es" is the language code for Spanish, so messages_es.properties contains validation messages in Spanish.


Note The naming convention for the messages files follows the standard convention used by the java.util.ResourceBundle class. For more information, see the documentation for java.util. ResourceBundle and java.util.Locale at http://java.sun.com/j2se/1.5.0/docs/api/.


Property files are plain-text files, which contain name-value pairs. Listing 7-1 represents a simple properties file.

Listing 7-1. A Simple Property File

# messages.properties
app.name=gTunes
book.title=The Definitive Guide to Grails
favorite.language=Groovy
favorite.framework=Grails

Retrieving Message Values

In a standard Java or Groovy program, you would use the java.util.ResourceBundle class to retrieve values from a properties file. Listing 7-2 demonstrates how you would retrieve and print the value of the app.name property.

Listing 7-2. Using java.util.ResourceBundle

// JavaMessages.java
import java.util.ResourceBundle;

public class JavaMessages {

    public static void main(String[] args) {
        ResourceBundle bundle = ResourceBundle.getBundle("messages");
        String appName = bundle.getString("app.name");
        System.out.println("application name is " + appName);
  }
}

// GroovyMessages.groovy
def messages = ResourceBundle.getBundle('messages')
def appName = messages.getString('app.name')
println "application name is ${appName}"

The java.util.ResourceBundle class takes care of loading the properties file and providing an API to retrieve the values of properties defined in the file. Grails provides a GSP tag called message that will retrieve property values from the messages files in the grails-app/ i18n/ directory. For the simplest case, only the code attribute must be specified when calling the message tag. The code attribute tells the message tag which property value should be retrieved. For example, if a property named gtunes.welcome is defined in grails-app/i18n/ messages.properties, the value of that property may be rendered in a GSP using code like that shown in Listing 7-3.

Listing 7-3. Using the message Tag

<body>
   ...
   <g:message code="gtunes.welcome"/>
   ...
</body>

By default, Grails will decide which version of the property file to use based on the locale of the current web request. This means that often you will not need to do anything special in your application code with respect to localization. If you define your message properties in several language-specific versions of the properties files under grails-app/i18n/, then Grails will use the appropriate file based on the client's locale.

Figure 7-2 represents the gTunes home page in English.

image

Figure 7-2. gTunes in English

There are several user messages represented in Figure 7-2. For example, on the left side of the screen is a navigation area, which includes the "My Music" and "The Store" links. The labels for those links will include different text when the application is accessed from different locales. The best way to deal with that is to define those messages as properties and render the messages in the GSP with the message tag. Listing 7-4 shows how those properties might be defined in grails-app/i18n/messages.properties.

Listing 7-4. User Messages in grails-app/i18n/messages.properties

gtunes.my.music=My Music
gtunes.the.store=The Store
...

With those properties defined, a GSP can render those values using the message tag, as shown in Listing 7-5.

Listing 7-5. Rendering Property Values from a GSP

<div id="navButtons">
  <ul>
    <li><a href="#"><g:message code="gtunes.my.music"/></a></li>
    <li><g:link controller="store" action="shop">
         <g:message code="gtunes.the.store"/>
        </g:link>
      </li>
   </ul>
</div>

With that code in place, you may add corresponding properties to as many of the other messages files as you like. To support a Spanish version of the site, add corresponding properties to grails-app/i18n/messages_es.properties, as shown in Listing 7-6.

Listing 7-6. User Messages in grails-app/i18n/messages_es.properties

gtunes.my.music=Mi Musica
gtunes.the.store=La Tienda
...

A simple way to test your Grails application's localization is to include a request parameter named lang and assign it a valid language code, such as "es" for Spanish (http://localhost:8080/gTunes/?lang=es). Figure 7-3 shows a Spanish version of the application.

image

Figure 7-3. gTunes in Spanish

Using URL Mappings for Internationalization

As shown previously, a request parameter named lang will tell the framework to use a specific language code while processing this request. One way to specify the request parameter is to include it in the request URL, as in http://localhost:8080/gTunes/?lang=es. Another way to specify the request parameter is by defining a custom URL mapping, as shown in Listing 7-7.

Listing 7-7. A URL Mapping for Localization

class UrlMappings {
  static mappings = {
    "/store/$lang"(controller:'store')

    // ...
   }
}

The mapping in Listing 7-7 will map all requests to a URL like http://localhost:8080/gTunes/en/ or http://localhost:8080/gTunes/es/ where "en" and "es" could be any valid language code.

Using Parameterized Messages

Often a user message may consist of more than simple static text. The message may need to include some data that is not known until runtime. For example, gTunes displays a message that lets the user know how many songs they have purchased. The message reads something like "You have purchased (97) songs." The "97" part of that message is a piece of information that isn't known until runtime.

Using java.text.MessageFormat

Java includes a class called java.text.MessageFormat. One of the things that java.text. MessageFormat is useful for is supporting parameterized messages, like the one described earlier, in a language-neutral way. A parameterized message may contain any number of parameters, and the parameters are represented with numbers surrounded by curly braces in the value of the message. Listing 7-8 shows how the "You have purchased (97) songs." message might be represented in grails-app/i18n/messages.properties.

Listing 7-8. Defining a Parameterized Message

# messages.properties
gtunes.purchased.songs=You have purchased ({0}) songs.
...

The value of the gtunes.purchased.songs message has one parameter in it. As is almost always the case in Java and Groovy, the java.text.MessageFormat class uses a zero-based index, so {0} in the message is a placeholder for the value of the first parameter. If the message had multiple parameters, they would be represented in the value of the message with placeholders like {0}, {1}, {2}, and so on.

The code in Listing 7-9 shows how java.text.MessageFormat might be used from a Java program.

Listing 7-9. Using MessageFormat to Populate a Parameterized Message with Java

// JavaMessages.java
import java.util.ResourceBundle;
import java.text.MessageFormat;

public class JavaMessages {

    public static void main(String[] args) {
        ResourceBundle bundle = ResourceBundle.getBundle("messages");
        String songsPurchased = bundle.getString("gtunes.purchased.songs");
        String message = MessageFormat.format(songsPurchased, 97);
        System.out.println("message: " + message);
    }
}

Listing 7-10 shows a Groovy script that does the same thing.

Listing 7-10. Using MessageFormat to Populate a Parameterized Message with Groovy

import java.text.MessageFormat

def bundle = ResourceBundle.getBundle('messages')
def songsPurchased = bundle.getString('gtunes.purchased.songs')
def message = MessageFormat.format(songsPurchased, 97)

println "message: ${message}"

Using the message Tag for Parameterized Messages

Grails allows for parameterized messages to be used without the need for you, the application developer, to deal directly with the java.text.MessageFormat class. The message tag supports an optional parameter named args, and if that parameter is assigned a value, its value will be treated as a list of parameters that need to be applied to the message. Listing 7-11 shows how to pass arguments to the message tag.

Listing 7-11. Using the message Tag to Populate a Parameterized Message

<div>
  <g:message code="gtunes.purchased.songs" args="[97]"/>
</div>

Of course, for a message like this, you will probably not want to hard-code the parameter value in a GSP like that. More likely, you will want that value to be dynamic. The code in Listing 7-12 is passing a parameter to the message to be applied to the gtunes.purchased.songs message. If the currently logged in user has purchased any songs, then the value of the parameter will be the number of songs they have purchased; otherwise, the value of the parameter will be 0.

Listing 7-12. Using the message Tag to Populate a Parameterized Message Dynamically

<div>
  <g:message code="gtunes.purchased.songs"
             args="[session.user.purchasedSongs?.size() ?: 0]"/>
</div>

Note Note the use of the so-called Elvis operator (?:) in the previous code. The Elvis operator is a shorthand version of Java ternary operator where the return value for the true condition is the same as the expression being evaluated. For example, the following expressions accomplish the same thing:

size = session.user.purchasedSongs?.size() ? session.user.purchasedSongs?.size() : 0
size = session.user.purchasedSongs?.size() ?: 0

Using Parameterized Messages for Validation

You will notice that the default grails-app/i18n/messages.properties file contains a number of messages by default. These messages are there to support the mechanism that is built in to Grails for validating domain classes and command objects. Listing 7-13 shows a domain class that contains some constraints.

Listing 7-13. A Domain Class with Constraints

class Person {
    String firstName
    String lastName
    Integer age

    static constraints = {
        firstName size: 2..30, blank: false
        lastName size: 2..30, blank: false
        age min: 0
    }
}

These constraints are in place to make sure that the firstName and lastName properties are at least 2 characters, no more than 30 characters, and not blank. You might think that specifying a minimum length of two would take care of the blank scenario, but that is not the case. A firstName that is simply three spaces would satisfy the length constraint but not the blank constraint. The age property also is constrained, so it may never have a negative value. If an instance of the Person class is created that does not satisfy all of those constraints, then a call to the validate() method on that instance would return false. Likewise, a call to save() on the instance would fail.

The default scaffolded views for a domain class contain code to display any validation errors. Listing 7-14 shows a piece of the default grails-app/views/person/create.gsp.

Listing 7-14. create.gsp Containing Code to Render Validation Errors

<h1>Create Person</h1>
<g:if test="${flash.message}">
  <div class="message">${flash.message}</div>
</g:if>
<g:hasErrors bean="${personInstance}">
  <div class="errors">
     <g:renderErrors bean="${personInstance}" as="list" />
   </div>
</g:hasErrors>

The hasErrors tag will render its body only if personInstance has errors. If personInstance does have errors, then the renderErrors tag will render a list of all those errors, and that rendering process is using the validation messages defined in grails-app/i18n/messages.properties.

Figure 7-4 shows what the user might see when attempting to create a Person in the user interface with no firstName, no lastName, and a negative age.

image

Figure 7-4. Validation messages in the user interface

The error messages you see there are all defined in grails-app/i18n/messages.properties as parameterized messages, as shown in Listing 7-15.

Listing 7-15. Default Validation Messages

default.invalid.min.message=
  Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]
default.blank.message=Property [{0}] of class [{1}] cannot be blank
...

You may modify the values of these messages to suit your application. For example, if the default.blank.message property was given a value of {0} is a required field, then the user would be shown error messages like those in Figure 7-5.

image

Figure 7-5. Custom validation messages in the user interface

Using messageSource

The message tag is easy to use and makes sense when a user message needs to be retrieved from messages.properties and the message is going to be rendered in a GSP. However, sometimes an application may need to retrieve the value of a user message and do something with it other than render the value in a GSP. For example, the message could be used in an e-mail message. In fact, the message could be used for any number of things, and not all of them involve rendering text in a GSP.

Grails provides a bean named messageSource that can be injected into any Grails artefact including controllers, taglibs, other beans, and so on. The messageSource bean is an instance of the org.springframework.context.MessageSource interface provided by the Spring Framework. This interface defines three overloaded versions of the getMessage method for retrieving messages from the source. Listing 7-16 shows the signatures of these methods.7


Note Throughout the source code and documentation of Grails, the word artefact is used to refer to a Groovy file that fulfills a certain concept (such as a controller, tag library, or domain class). It is spelled using the British English spelling of artefact as opposed to artifact, so we will be using that spelling throughout the book to maintain consistency with the APIs.


Listing 7-16. The MessageSource Interface

String getMessage(String code, Object[] args, Locale locale)
String getMessage(String code, Object[] args, String defaultMessage, Locale locale)
String getMessage(MessageSourceResolvable resolvable, Locale locale)

Since the messageSource bean participates in Grails' dependency autowiring process, all you need to do to get a reference to the bean is declare a property named messageSource in your Grails artefact. The code in Listing 7-17 shows how to use the messageSource bean in a controller.

Listing 7-17. Using messageSource in a Controller

package com.g2one.gtunes

class StoreController {

    def messageSource

    def index = {
        def msg = messageSource.getMessage('gtunes.my.music', null, null)
        // ...
    }
    ...
}

Note that the second and third arguments are null. The second argument is an Object[], which would be used to pass parameters to a parameterized message. The third argument is a java.util.Locale, which may be specified to retrieve a message for any Locale other than the default Locale for this request. For example, Listing 7-18 demonstrates retrieving a message in Italian.

Listing 7-18. Using messageSource and Specifying a Locale

package com.g2one.gtunes

class StoreController {

    def messageSource

    def index = {
        def msg = messageSource.getMessage('gtunes.my.music', null, Locale.ITALIAN)
        // ...
    }
    ...
}

Summary

Internationalization is an important aspect of building widely distributed applications. Grails provides a number of mechanisms that make the process much easier than it might otherwise be. All the message property files in a Grails application are located in the same place. This means that, as an application developer, you do not need to tell Grails where to look for these files. It also means that as a Grails developer moves from one Grails project to the next, the developer knows exactly where to look for the property files because they are always in the same place. This is the power of coding by convention at work. Also, retrieving messages from a property file is a snap in a Grails application. The message tag is very easy to use from GSP pages and GSP templates. The messageSource bean is easily accessible from wherever the application may need it. All of this is built on top of proven and well-understood tools on the Java platform including java.text.MessageFormat and org.springframework.context.MessageSource.



7. See http://static.springframework.org/spring/docs/2.5.x/api/index.html for complete documentation of the MessageSource interface and related classes.

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

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