Ted is a software developer from the Netherlands. He likes everything that has to do with Java, Grails & Groovy, Agile & Scrum best practices and web development in general. Ted has posted 6 posts at DZone. You can read more from them at their website. View Full User Profile

Grails scaffolding: enums and i18n

09.04.2012
| 7899 views |
  • submit to reddit

Grails scaffolding feature is a great asset for quickly generating create/read/update/delete (CRUD) screens for your domain classes. Create an entity, create a controller and put static scaffold on top. For various types of properties within your domain class a suitable (HTML) input is used, to take the user's input: booleans become checkboxes, associations with other domain classes become (depending on ownership and constraints) e.g. dropdowns or multi-selects, etc.

Even an enum works out of the box as one could imagine :-) This post serves as a small quicktip - primarily for me and other developers looking for these kind of things - of internationalizing scaffolded screens in general and enums in particular using  toString() and Spring's MessageSourceResolvable.

Take the following Product with a Status:

class Product {

	enum Status {
		AVAILABLE, SOLD_OUT
	}

	String name
	Status status

	static constraints = { name blank: false, unique: true }
}

class ProductController {

	def scaffold = true
}

Opening up our /product/create url will show what you would expect.

A great thing of the scaffolding is that internationalizing the elements on the screen is a breeze! Default translations for Home and Create have already been provided for a dozen or so languages in the i18n folder. The name of the entity itself ("Product") and its properties can be defined by following a convention: [entity name].label and [entity name].[property name].label - see your Grails installation /src/grails/templates/scaffolding folder.

Take a look at how we would translate a few things into Dutch, by adding a few keys to messages_nl.properties:

product.label=Product
product.name.label=Naam
product.status.label=Status

If we want to test for a certain language (without changing your - browser - environment) or you just want to fix the application to a certain locale, we can override the default locale with Spring globally. Let's do that for Dutch for the following examples.

beans = {

    localeResolver(org.springframework.web.servlet.i18n.FixedLocaleResolver) {
        defaultLocale = new Locale("nl", "NL")
    }
}

Reviewing our screen again shows us the correct Dutch translations, for the default UI elements as for our own properties.

So far so good, but wouldn't we like to have translated values for our enum values AVAILABLE and SOLD_OUT too? There are a few approaches we can take, of which I will describe toString() and using Spring's MessageSourceResolvable.

#1. toString()

As you know, the scaffolded screens just output values in selects and dropdowns through the toString() method of the shown object. We can construct each enum with a hardcoded translation and have toString() return that value.

class Product {

	enum Status {

		AVAILABLE("Beschikbaar"), SOLD_OUT("Uitverkocht")

		final String value
		Status(String value) {
			this.value = value
		}

		String toString() {
			value
		}
	}

	String name
	Status status

	static constraints = { name blank: false, unique: true }
}

And presto!

The actual names of the enum now only appear in the generated HTML:

<select name="status" required="" id="status" >
  <option value="AVAILABLE" >Beschikbaar</option>
  <option value="SOLD_OUT" >Uitverkocht</option>
</select>

#2. MessageSourceResolvable

By now you'll probably understand above hardcoded solution works for just one language, one hardcoded in the enum itself - which will cause problems when in a month from now your application actually needs to support a 2nd language :-) So how do we leverage the fact that we have already have message_XX.properties where we've put our other message keys?

Use the underlying Spring framework. Have our enum implement org.springframework.context.MessageSourceResolvable e.g. like this:

	enum Status implements org.springframework.context.MessageSourceResolvable {

		AVAILABLE, SOLD_OUT

		public Object[] getArguments() { [] as Object[] }

		public String[] getCodes() { [ name() ] }

		public String getDefaultMessage() { "?-" + name() }
	}

Just like validation errors - Spring's FieldError and ObjectError which implement aformentioned interface - we can provide our own codes to lookup a particular message key.

Reviewing our screen you'll notice that since we haven't provided a translation yet, the value from getDefaultMessage() is taken. I just added a "?-" in front of it to make this more noticeable, but you can do whatever you like there e.g. just return name() maybe.

Now we can provide a value in our messages_nl.properties for each enum we have:

product.label=Product
product.name.label=Naam
product.status.label=Status
<b>AVAILABLE=Beschikbaar
SOLD_OUT=Uitverkocht</b>

And we have the same effect of our earlier hardcoded version, but now we've done i18n the proper way!

 Original Article:  http://tedvinke.wordpress.com/2012/08/22/grails-scaffolding-enums-and-i18n

Published at DZone with permission of its author, Ted Vinke.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Soeren Glasius replied on Mon, 2013/12/02 - 3:10pm

This is not the best way of doing this, as Grails will not translate your enum in your 'show' action. If you instead implement MessageSourceResolvable, you will get translation for both forms and show views.

To make this even easier, I have created the EnumMessageSourceResolvable plugin, that will, by just one annotation, implement those three methodes from MessageSourceResolvable for you.

Read about the plugin here: http://grails.org/plugin/enum-message-source-resolvable

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.