i18n Fields

  • Tags: internationalization
  • Latest: 0.8.1
  • Last Updated: 28 November 2013
  • Grails version: 2.1.0 > *
  • Authors: Jorge Uriarte
2 votes
Dependency:
compile ":i18n-fields:0.8.1"
Custom repositories:
mavenRepo "http://snapshots.repository.codehaus.org/"
mavenRepo "http://repository.codehaus.org/"
mavenRepo "http://download.java.net/maven/2/"
mavenRepo "http://repository.jboss.com/maven2/"
mavenRepo "http://m2repo.spockframework.org/ext/"
mavenRepo "http://m2repo.spockframework.org/snapshots/"

 Documentation

Summary

This plugin provides an easy way of declarativily localize database fields of your content tables.

Installation

grails install-plugin i18n-fields

Description

i18n fields plugin

Overview

This plugin provides a declarative way for localizing domain classes' fields in different languages.

The idea is, once you've got your application up and running, adding multiple language support for your database fields must be as easy (or more) than extracting your literals to bundles in your views.

The plugin is inspired in the way the Symfony php framework does i18n, even though the technical solution behind the façade is quite different.

A simple case

Just assume you have this domain class defined and working:

class MyDomainEntity {
    def name
    def description
}

You've also got quite a few places all around the system where your accessing this fields. Views, service code, controllers, jobs… usually in the form:

${object.name} : ${object.description}

And this is spread in your views, listings, emails, whatever...

Now assume you've got the requisite now of allowing the admin of your system provide Spanish and Portuguese translations for name and description. Your entity will now (conceptually) become something like:

class MyDomainEntity {
    def name_en_US
    def name_es_ES
    def name_pt_BR
    def description_en_US
    def description_es_ES
    def description_pt_BR
}

You'll use Grails' built-in i18n support to extract your texts and messages to bundles but...what about the fields of your domain? What about all that places in the views where you're accessing the fields?

Proposed solution

@i18nfields.I18nFields
class MyDomainEntity {
    def name
    def description

static i18nFields = ['name', 'description'] }

That's all. Accessing the fields will automatically take into account the current language of the request, and your table will hold localized content in new columns: name_es_ES, name_en_US, name_pt_BR, etc.

The languages that will be used are configured in a block in Config.groovy:

i18nFields {
    locales = ['en_US', 'es_ES', 'pt_PT', 'pt_BR']
}

The new fields are added in compilation time via AST, so they will be there for you to call and use them if necessary.

Localization when not bound to a request thread

Locale is bound to the request thread using LocaleContextHolder.

But sometimes, there's no user locale because there's no browser at all, but still you might need to operate in a given language (for example, sending mails to users in their choosen locale, from a quartz job). If you need to work with different languages in a job, or any offline code, you can use withLocale to setup different locales in your calls:

// Show how to set the language for a code block to get it properly, from within a quartz job or similar
withLocale(currentUser.getLocale()) {
  myLocaleDependentService.sendMessage(currentUser)  // Internally proper language will be selected
}
// Out of the closure, previous locale will be restored

The withLocale closure is dynamically added to domain classes, services, controllers, taglibs, etc… so you can always take an alternative route to the current locale in thread.

If you need to dinamically provide access to all languages for an entity (for example, you want to provide access to every language from the content management site), you might use:

org.codehaus.groovy.grails.commons.ConfigurationHolder.config.i18nFields.locales.each { loc ->
  // do what you need to do with the language…
  withLocale(new Locale("xx", "XX")) {
    // my stuff… will be in *all* the configured languages…
  }
}

Direct acces to i18nalized fields

When you use the standard getter for a field, you'll be using the current session's Locale definition to retrieve the correct field contents, as explained above.

If you need to access a particular translation of a field in a context where you can't easily perform a withLocale interaction (for example, in the views for l10n content), you can use a variation of the standard getter:

object.getField(new Locale("xx", "XX"))

And it will do the same as if:

withLocale(new Locale("xx", "XX")) { object.getField() }

This can be done also to set contents for a particular Locale:

object.setField("content", new Locale("xx", "XX"))

Also, you can always access a localized field in a language via object.setField_xx_XX(y)@, @object.getField_xx_XX() or object.field_xx_XX property.

Locale handling

As you will probably know, Locales are composed of a language or a language and country combinations. Please, take a look at the Locale class javadoc for more information.

You can define in your application any number of different locales for the same language depending on the country of your users. For example, we could use US english (en_US) and british english (en_GB) to reflect their particular differences.

Also, you could take the benefit of the hierarchical nature of locale definitions to maybe define a "generic" english (en) for the vast majority of your users and add US or british english for the small differences.

Doing so, you will enable your application to fall back to a correct locale definition if, for example, a user uses canadian english (en_CA) configured. The plugin will detect that "en_CA" isn't configured and will fall back to "en" instead of falling back to whatever locale default definition is configured.

In other words, object.getField(new Locale("en", "CA")) will really return object.getField(new Locale("en")) in this scenario.

Locale validation

The plugin will ignore by default any invalid locale, according to the list of available locales from Locale.getAvailableLocales().

However, the returned list is not complete: some valid language or language and country combinations are missing. For example, "eu" locale defintion for Basque wouldn't be valid according to that list.

If you come up with this problem you can define a list for extra locales in the plugin's configuration:

i18nFields {
    locales = ["en", "en_US", "es", "eu"]
    extraLocales = ["eu"]
}

To check if a locale is being ignored, please check the build output of your application. If a locale defintion is ignored, you won't get any localized fields or custom getters/setters

Caveats

  • If you're providing your own getters wrapping property access, it might not work for you. You'll have to take language into account and access properties properly (we will ease your task soon hopefully).
  • If you're using @Searchable@, you must ask for all localized fields to be indexed, and you will have to provide your own language filtering in searches if needed (we plan to provide a way to ease that, too).

Issues?

Report them here: http://jira.grails.org/browse/GPI18NFIELDS