spring:
messages:
encoding: UTF-8
cache-duration: 5s
fallback-to-system-locale: false
use-code-as-default-message: false
17 Internationalization
Version: 8.0.0-SNAPSHOT
Table of Contents
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:
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:
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:
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:
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:
import org.springframework.web.servlet.i18n.CookieLocaleResolver
beans = {
localeResolver(CookieLocaleResolver) {
defaultLocale= new Locale('es')
}
}
Or fix the locale:
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.