(Quick Reference)

17 Internationalization

Version: 8.0.0-SNAPSHOT

17 Internationalization

Grails supports Internationalization (i18n) out of the box by leveraging the underlying Spring MVC internationalization support. With Grails you are able to customize the text that appears in a view based on the user’s Locale. To quote the javadoc for the Locale class:

A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user. For example, displaying a number is a locale-sensitive operation—​the number should be formatted according to the customs/conventions of the user’s native country, region, or culture.

A Locale is made up of a language code and a country code. For example "en_US" is the code for US English, whilst "en_GB" is the code for British English.

17.1 Understanding Message Bundles

Now that you have an idea of locales, to use them in Grails you create message bundle file containing the different languages that you wish to render. Message bundles in Grails are located inside the grails-app/i18n directory and are simple Java properties files.

Each bundle starts with the name messages by convention and ends with the locale. Grails ships with several message bundles for a whole range of languages within the grails-app/i18n directory. For example:

  • messages.properties

  • messages_da.properties

  • messages_de.properties

  • messages_es.properties

  • messages_fr.properties

  • …​

By default Grails looks in messages.properties for messages unless the user has specified a locale. You can create your own message bundle by simply creating a new properties file that ends with the locale you are interested in. For example messages_en_GB.properties for British English.

Configuration

Message resolution is Spring Boot’s, so the standard spring.messages.* properties apply:

spring:
    messages:
        encoding: UTF-8
        cache-duration: 5s
        fallback-to-system-locale: false
        use-code-as-default-message: false

Grails sets fallback-to-system-locale to false and carries grails.views.gsp.encoding over to spring.messages.encoding, but an application may override either.

You do not normally need to set spring.messages.basename. Grails records the bundles an application and each of its plugins ship at build time and composes the list for you. Setting it yourself is still supported and is the way to reach a bundle outside grails-app/i18n — anything you declare is kept and takes precedence over the discovered base names.

Base names

Every bundle needs a locale-independent file. messages_de.properties without messages.properties means Spring Boot’s message-source auto-configuration never activates and the application has no message source at all, so the build rejects it.

An application may use any base name, with one reservation: a base name ending in a valid locale identifier is ambiguous, because api_fr.properties reads as base name api in French. Declare the base name when you mean something else:

grails {
    i18n {
        basenames = ['api', 'api_errors']
    }
}

Plugin bundles

A plugin’s base names must be its plugin name, or that name followed by a hyphen and a suffix — so the spring-security-core plugin may ship spring-security-core.properties and spring-security-core-validation.properties, but not messages.properties. Spring resolves a base name to the first matching resource on the classpath, so a shared base name would silently shadow another bundle rather than merge with it. The plugin build enforces this.

Precedence runs application first, then plugins. Where several plugins define the same code, the reverse of Grails' plugin topological order wins, which is the behaviour applications had before Spring Boot owned the message source. An application’s own bundle always overrides every plugin’s, so redefining a plugin’s code in messages.properties is the supported way to change it.

Set grails.i18n.include-plugin-bundles to false to leave plugin bundles out entirely.

Reloading during development

Editing a value in an existing bundle is picked up without a restart. Grails contributes a short spring.messages.cache-duration automatically when reload is enabled; without one Spring caches every bundle for the lifetime of the application. Set the property yourself to override the duration.

Adding a locale file for a base name that already exists resolves without a restart too, though the language selector described in [changingLocales] only lists it once the build has regenerated its metadata. Adding or removing a base name needs a restart: Spring Boot reads the configured base names once, when it builds the message source.

17.2 Changing Locales

By default, the user locale is detected from the incoming Accept-Language header. You can provide users the capability to switch locales by simply passing a parameter called lang to Grails as a request parameter:

/book/list?lang=es

Grails will automatically switch the user’s locale and subsequent requests will use the switched locale.

To offer a language selector, the i18n plugin discovers the locales your application is actually translated into by scanning the classpath for messages_*.properties bundles (plus the default locale, configurable via grails.i18n.default.locale). This list is exposed as the AvailableLocaleResolver bean, ordered by each locale’s own name (autonym), and published to the servlet context under the availableLocales attribute.

The <g:localeSelect available="true"/> tag renders a <select> limited to those locales. To build a custom switcher, use the tag’s body form: it resolves and orders the locales and flags the active (loc.active) and configured-default (loc.default) entries, while your markup decides how each is rendered:

<g:localeSelect available="true" pinDefault="true" var="loc">
    <a href="?lang=${loc.tag}"${loc.active ? ' aria-current="page"' : ''}>${loc.autonym}</a>
</g:localeSelect>

Alternatively, iterate the published availableLocales list directly.

The list is drawn from the message bundles your application and its plugins actually ship, recorded at build time, so it offers only real translations. It follows exactly the same set of plugins that message resolution uses: a plugin that was evicted, excluded, or failed to load contributes neither messages nor a language entry, so the selector can never offer a language whose messages will not resolve. To list only your application’s own translations, set grails.i18n.include-plugin-bundles=false:

grails-app/conf/application.yml
grails:
    i18n:
        include-plugin-bundles: false   # list only the application's own translations

Applications created with grails create-app include a ready-made Bootstrap language dropdown in the default layout, built with the g:localeSelect body form shown above.

By default, Grails uses SessionLocaleResolver as the localeResolver bean.

You can select a different resolver strategy with the grails.i18n.localeResolver configuration property, without declaring a bean:

grails-app/conf/application.yml
grails:
    i18n:
        localeResolver: acceptHeader # session (default), cookie, acceptHeader or fixed

session and cookie are mutable, so the ?lang= switch works with them: the incoming Accept-Language header sets the initial locale, and a ?lang= request parameter then overrides it and is remembered for subsequent requests (in the session or a cookie). acceptHeader (the locale follows the incoming Accept-Language header on every request) and fixed are read-only, so the ?lang= switch has no effect and is silently ignored — the header, or for fixed the grails.i18n.default.locale value (falling back to the JVM default locale), always wins.

You can change the default locale easily:

grails-app/conf/spring/resources.groovy
import org.springframework.web.servlet.i18n.SessionLocaleResolver

beans = {
    localeResolver(SessionLocaleResolver) {
        defaultLocale= new Locale('es')
    }
}

Other localeResolver are available. For example, you could use save the switched locale in a Cookie:

grails-app/conf/spring/resources.groovy
import org.springframework.web.servlet.i18n.CookieLocaleResolver

beans = {
    localeResolver(CookieLocaleResolver) {
        defaultLocale= new Locale('es')
    }
}

Or fix the locale:

grails-app/conf/spring/resources.groovy
import org.springframework.web.servlet.i18n.FixedLocaleResolver

beans = {
    localeResolver(FixedLocaleResolver, new Locale('de'))
}

17.3 Reading Messages

Reading Messages in the View

The most common place that you need messages is inside the view. Use the message tag for this:

<g:message code="my.localized.content" />

As long as you have a key in your messages.properties (with appropriate locale suffix) such as the one below then Grails will look up the message:

my.localized.content=Hola, me llamo John. Hoy es domingo.

Messages can also include arguments, for example:

<g:message code="my.localized.content" args="${ ['Juan', 'lunes'] }" />

The message declaration specifies positional parameters which are dynamically specified:

my.localized.content=Hola, me llamo {0}. Hoy es {1}.

Reading Messages in Grails Artifacts with MessageSource

In a Grails artifact, you can inject messageSource and use the method getMessage with the arguments: message code, message arguments, default message and locale to retrieve a message.

import org.springframework.context.MessageSource
import org.springframework.context.i18n.LocaleContextHolder

class MyappController {

    MessageSource messageSource

    def show() {
        def msg = messageSource.getMessage('my.localized.content', ['Juan', 'lunes'] as Object[], 'Default Message', LocaleContextHolder.locale)
    }

Reading Messages in Controllers and Tag Libraries with the Message Tag

Additionally, you can read a message inside Controllers and Tag Libraries with the Message Tag. However, using the message tag relies on GSP support which a Grails application may not necessarily have; e.g. a rest application.

In a controller, you can invoke tags as methods.

def show() {
    def msg = message(code: "my.localized.content", args: ['Juan', 'lunes'])
}

The same technique can be used in tag libraries, but if your tag library uses a custom namespace then you must prefix the call with g.:

def myTag = { attrs, body ->
    def msg = g.message(code: "my.localized.content", args: ['Juan', 'lunes'])
}

17.4 Scaffolding and i18n

Grails scaffolding templates for controllers and views are fully i18n-aware. The GSPs use the message tag for labels, buttons etc. and controller flash messages use i18n to resolve locale-specific messages.

The scaffolding includes locale specific labels for domain classes and domain fields. For example, if you have a Book domain class with a title field:

class Book {
    String title
}

The scaffolding will use labels with the following keys:

book.label = Libro
book.title.label = Título del libro

You can use this property pattern if you’d like or come up with one of your own. There is nothing special about the use of the word label as part of the key other than it’s the convention used by the scaffolding.