Loading...

Monday, November 14, 2011

Grails Goodness: Internationalize Javascript Messages with JAWR Plugin

Grails has great builtin support for internationalization (i18n). The underlying Spring support for i18n is used. We can easily change for example text on views based on the user's locale. But this only applies for the server side of our code. So we can generate the correct messages and labels based on the user's locale on the server, but not in our Javascript code. What if we want to display a localized message in a bit of Javascript code, that is not created on the server? Why do I add this extra information 'not created on the server'? Because we can still generate Javascript code in a view or use the gsp-resources plugin to create Javascript on the server. This code can contain the output of a localized message and can be used in Javascript. But that is not what we want for this blog post. Here we are going to reference our i18n messages from plain, non-generated Javascript code.

We can achieve this with the JAWR plugin. The plugin provides roughly the same functionality as the resources plugin for bundling resources efficiently in a Grails application. We are not interested in that part, but the JAWR library used by the plugin also has a i18n messages generator. And we are going to use that in our Grails application to get localized Javascript messages.

First we must install the JAWR plugin: $ grails install-plugin jawr. Next we can configure the plugin. We open our grails-app/conf/Config.groovy file and add:

// File: grails-app/conf/Config.groovy
...

jawr {
    js {
        // Specific mapping to disable resource handling by plugin.
        mapping = '/jawr/'

        bundle {
            lib {
                // Bundle id is used in views.
                id = '/i18n/messages.js'

                // Tell which messages need to localized in Javascript.
                mappings = 'messages:grails-app.i18n.messages'
            }
        }
    }
    locale {
        // Define resolver so ?lang= Grails functionality works with controllers.
        resolver = 'net.jawr.web.resource.bundle.locale.SpringLocaleResolver'
    }
}

...

At line 6 we define a mapping. If we don't define a mapping the JAWR plugin will also act as a resource and bundling plugin, but for this example we only want to use the i18n messages generator.

Line 14 defines which resource in the classpath contains the messages that need to be accessible in Javascript. For our Grails application we want the messages from messages.properties (and the locale specific versions) so we define grails-app.i18n.messages.

With Grails it is easy to switch to a specific user locale by adding the request parameter lang to a request. At line 20 we add a resolver that will use the Grails locale resolver to determine a user's locale.

It it time to see our Javascript in action. First we create two new message labels in messages.properties. One without variables and one with a variable placeholder to show how the JAWR plugin supports this:

// File: grails-app/i18n/messages.properties
js.sample.hello.message=Hello
js.sample.hello.user.message=Hello {0}

Let's add a Dutch version of these messages in grails-app/i18n/messages_nl.properties:

// File: grails-app/i18n/messages_nl.properties
js.sample.hello.message=Hallo
js.sample.hello.user.message=Hallo {0}

Now it is time to create a GSP view with a simple controller (only request through a controller will be able to use the locale resolver we defined in our configuration).

// File: grails-app/controller/grails/js/i18n/SampleController.groovy
package grails.js.i18n

class SampleController {
    def index = {
        // render 'sample/index.gsp'
    }
}
%{-- File: grails-app/views/sample/index.gsp --}%
<html>
    <head>
        <meta name="layout" content="main"/>
        <jawr:script src="/i18n/messages.js"/>
        <g:javascript library="application"/>
    </head>
    <body>
        <h1>Simple message</h1>

        <input type="button" onclick="showAlertHello();" value="Hello"/>

        <hr/>

        <h1>Message with variable placeholder</h1>

        Username: <input type="text" id="username" size="30"/>

        <input type="button" onclick="showAlertUsername();" value="Hello"/>

    </body>
</html>

At line 4 we inlude the Javascript i18n messages generated by the JAWR plugin. And at line 5 we include an external Javascript file that will use the generated messages:

// File: web-app/js/application.js

function showAlertHello() {
    var alertMessage = messages.js.sample.hello.message();
    alert(alertMessage);
}

function showAlertUsername() {
    var usernameValue = document.getElementById("username").value;
    var alertMessage = messages.js.sample.hello.username.message(usernameValue);
    alert(alertMessage);
}

Notice how we can access the i18n messages in Javascript. The plugin will convert the messages to Javascript functions to return the message. And even variable substitution is supported (see line 9).

The following screenshots show the alert messages for the default locale and for a request with the Dutch locale:






With the current configuration of the JAWR plugin all messages in the messages.properties (and locale versions) will be exported to Javascript messages. But maybe this is too much and we only want to include a subset of the messages in the generated Javascript. In the configuration we can define a prefix for the messages to be exported or we can even define a separate properties file with only messages necessary for Javascript:

// File: grails-app/conf/Config.groovy
...
// Only filter messages starting with js.
jawr.js.bundle.lib.mappings=messages:grails-app.i18n.messages[js]
...
// File: grails-app/conf/Config.groovy
...
// Use a different properties file: jsmessages.properties (jsmessages_nl.properties, ...).
jawr.js.bundle.lib.mappings=messages:grails-app.i18n.jsmessages
...

In our Javascript we reference the messages by prefixing messages. to the message properties. We can change this as well in our JAWR plugin configuration. If for example we want to use i18n we must define our plugin as follows:

// File: grails-app/conf/Config.groovy
...
// Define custom namespace for reference in Javascript.
jawr.js.bundle.lib.mappings=messages:grails-app.i18n.messages(i18n)
...

With the use of the JAWR plugin and the i18n messages generator we can easily use localized messages in our Javascript code.