Our Blog

Let us find tech solutions together

Oct 06

Enums internationalization with Wicket

By ildella | Comments

 

(Editor’s note: Olivier Croisier contributed this column from YesWicket.)

Enums are a convenient way to represent finite collections of elements : seasons, week days… As a consequence, they frequently need to be input or displayed in web applications - and it better be in a I18N-aware way. Let’s see how easily Wicket can handle this.

Enums internationalization

Throughout this article, we’ll use the Seasons Enum as an example :

1
2
3
public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER;
}

Wicket has a powerful hierarchical I18N system : given a logical I18N key (a string), the corresponding translation is first searched for at the current component level, then up its hierarchy - all the way up to the Application root if required. To benefit from it, the developer only has to call the getString() method on any Wicket component :

1
String translation = anyComponent.getString(key);

The first thing we need to do is therefore to devise a way to generate a unique I18N key for each Enum constant. The most obvious solution is to use their Fully Qualified Names as a key (eg. “com.yeswicket.wickettips.model.WINTER”) ; this should prevent fortuitous name clashes.

The following code snippet demonstrates how to get an Enum’s FQN :

1
2
Season enumValue = Season.WINTER;
String messageKey = enumValue.getDeclaringClass().getCanonicalName() + "." + enumValue.name();

In order to avoid copy/pasting this code all around the application, and to be able to easily replace it with another algorithm should the need arise, we’ll use the Strategy pattern, where every algorithm is encapsulated in its own class. This might seem easy to implement, but keep in mind that every class accessible from a Wicket component will be serialized along with it in the session store… and that’s something you will definetely want to avoid.

Static methods to the rescue ! The following class, inspired from java.util.Locale, has a static-only API for its consumers (thus solving the serialization concern) and still allows the underlying algorithm to be changed at will :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class EnumMessageKeyProvider {
	private static EnumMessageKeyProvider provider = new DefaultEnumResourceKeyProvider();

	public static EnumMessageKeyProvider getDefault() {
		return EnumMessageKeyProvider.provider;
	}

	public static void setDefault(EnumMessageKeyProvider provider) {
		EnumMessageKeyProvider.provider = provider;
	}

	public abstract <t extends Enum<t>> String computeMessageKey(T enumValue);

	public static <t extends Enum<t>> String getMessageKey(T enumValue) {
		return EnumMessageKeyProvider.provider.computeMessageKey(enumValue);
	}
}
public class DefaultEnumResourceKeyProvider extends EnumMessageKeyProvider {
	@Override
	public <t extends Enum<t>> String computeMessageKey(T enumValue) {
		return enumValue.getDeclaringClass().getCanonicalName() + "." + enumValue.name();
	}
}

Getting an I18N key for an Enum is now as easy as :

1
2
Season enumValue = Season.WINTER;
String key = EnumMessageKeyProvider.getMessageKey(enumValue);

To use another key generation algorithm, just write a new strategy…

1
2
3
4
5
6
public class SimpleNameEnumResourceKeyProvider extends EnumMessageKeyProvider {
	@Override
	public <t extends Enum<t>> String computeMessageKey(T enumValue) {
		return enumValue.name();
	}
}

…and register it with the EnumMessageKeyProvider ; a good place for that is the Application’s init() method :

1
2
3
4
5
6
7
public class WicketTipsApplication extends WebApplication {
	@Override
	protected void init() {
		EnumMessageKeyProvider.setDefault(new SimpleNameEnumResourceKeyProvider());
	}
	(...)
}

Displaying internationalized enums

Wicket’s Label is a very basic component that simply displays the text provided by its Model. To display Enums, we can develop a new kind of Model that automatically performs internationalization and hands over the resulting text to the component it is attached to.

As an example, we’ll extend the very convenient PropertyModel. Please note that our Model provides a String but operates on an Enum : EnumPropertyModel<t extends Enum> extends PropertyModel. Also, in addition to the PropertyModel's existing constructor parameters (the target object and the name of the property to retrieve), our class needs a Component to call the getString() method on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class EnumPropertyModel<t extends Enum<t>> extends PropertyModel<string> {

	private Component component;

	public EnumPropertyModel(Object modelObject, String expression, Component resourceProvider) {
		super(modelObject, expression);
		this.component = resourceProvider;
	}

	@Override
	public String getObject() {
		final String expression = propertyExpression();
		final Object target = getTarget();
		if (target != null) {
			T enumValue = (T) PropertyResolver.getValue(expression, target);
			String key = EnumMessageKeyProvider.getMessageKey(enumValue);
			return component.getString(key);
		}
		return null;
	}
}

This custom Model can now be used with a Label (and many other components) to easily display any Enum :

1
2
3
4
5
6
public class HomePage extends WebPage {
	private Season season = Season.SPRING;
	public HomePage() {
		add(new Label("label", new EnumPropertyModel<season>(this, "season", this)));
	}
}

Selecting an enum with a DropDownChoice

Now that we can display an Enum, let’s see how to let the user choose one from a dropdown list.

The DropDownChoice component follows the MVC pattern :

  • The DropDownChoice itself is the Controller
  • The Model it takes as a parameter is, obviously, the Model
  • The View is rendered by a ChoiceRenderer that is responsible for the HTML