Localization and Skinning of Applications
Wicket provides a wide range of features that make it possible to easily create web applications for different locales and with different skins (styles). First, lets take a look at the basic localization and skinning features of Wicket. After that we will move on to how you can extend this functionality to take full control over localization.
Locale and Style
Wicket will use the locale of the incoming request along with a Style identifier to load resources. These resources include
- Markup files
- localization resource bundles (properties files)
- form input validators (also properties files)
Locale is set in the Wicket WebRequest object from the http request header locale in the User Agent field. Optionally, a user's locale can be set in their Session object by calling setLocale.
Style enables skinning of the application and is set by calling setStyle on the Wicket Session object. Session.get() returns the Session object associated with the current request.
Additionally, classes derived from Component provide a getVariation method which the developer can override on a per component basis to further discriminate resources to load.
Markup Files
Describe the loading of markup files by locale and style.
The ResourceStreamLoader determines what markup file to load for a component based on the variant, style, and locale values set for that component.
The Pub example in wicket-examples demonstrates how html markup is chosen based on locale. There is a default Home.html, Home_de_DE.html for users with German locale set, Home_zh_CN.html for Mandarin, etc.
These pages can be skinned by setting the Style attribute in the user's session. You could have three different styles for home: stone, wood, and brick. If the style you set does not have a corresponding markup file, then the localized markup is returned. If there is no markup for the locale, the default markup is returned. You can skin the Home page for a German user by providing Home_stone_de_DE.html, Home_wood_de_DE.html, and Home_brick_de_DE.html.
There is also a variation attribute that is additive to the style attribute. If no style attribute has been set, then variation takes the place of style in the resource name. If style is set, variation + "_" is prepended to the style name, which is then returned by getStyle().
If you have a panel inside Home called kitchen, you can override getVariation in the kitchen panel to return "granite" or "corriander". For a German locale, you could provide the following markups for the granite variation: Kitchen_granite_de_DE.html, Kitchen_granite_stone_de_DE.html, Kitchen_granite_wood_de_DE.html, Kitchen_granite_brick_de_DE.html. The possibilities are exponential.
The javadoc for the Wicket Session class describes the algorithm for resolving markup resources to load:
1. [sourcePath]/name[style][locale].[extension] 2. [sourcePath]/name[locale].[extension] 3. [sourcePath]/name[style].[extension] 4. [sourcePath]/name.[extension] 5. [classPath]/name[style][locale].[extension] 6. [classPath]/name[locale].[extension] 7. [classPath]/name[style].[extension] 8. [classPath]/name.[extension]
String Resources
Wicket can also manage message localization with ResourceBundle properties files.
The Localizer is a utility class that encapsulates all of the localization related functionality in Wicket so it can be accessed by all areas of the framework in a consistent way. All classes derived from Component have a getLocalizer() method which retrieves the shared Localizer from the Application. The Localizer maintains a static cache of messages already retrieved by a key generated from the class, locale, style and messageKey for the message. Message bundles are looked up using classes derived from AbstractStringResourceLoader. The Localizer fetches a list of AbstractStringResourceLoader from the Application object. By default, the Application provides a ComponentStringResourceLoader and a ClassStringResourceLoader.
In the default setup, the ComponentStringResourceLoader is first asked to locate the message bundle. If it can't find one (using the algorithm described below), the application's ClassStringResourceLoader is consulted next; this attempts to find a single resource bundle that has the same name and location as the application
The algorithm employed by ComponentStringResourceLoader to resolve message requests is described in the javadoc for the ComponentStringResourceLoader:
The component based string resource loader attempts to find the resource from a bundle that corresponds to the supplied component object or one of its parent containers. Generally the component will be an instance of Page, but it may also be an instance of any reusable component that is packaged along with its own resource files. If the component is not an instance of Page then it must be a component that has already been added to a page.
The search order for resources is built around the containers that hold the component (if it is not a page). Consider a Page that contains a Panel that contains a Label. If we pass the Label as the component then resource loading will first look for the resource against the page, then against the panel and finally against the label.
An exception will be thrown if you are using Localizer on a component that has not already been added to a page. Unless you are sure your component has already been attached to the Page hierarchy, use StringResourceModel, which will defer message bundle lookup until after the component has been attached to a page (during the render process).
The naming convention for resource lookup is the similar to the convention to markup files. The resource loader is style and locale aware. For example, you have a page Home with a panel Kitchen that has a panel Sink, and Sink has labels LeftFawcett (key fawcett.left) and RightFawcett (key fawcett.right) that need to be localized. Your locale is German and your style is "standard". The ComponentStringResourceLoader will first look for fawcett.left and fawcett.right in Home_standard_de_DE.properties, then the same extension for Kitchen, and then Sink. If the key is not found, it will continue following the same steps detailed above for the markup files, but for each step trying it for Home, then Kitchen, then Sink
Using getLocalizer()
You can always get localizer inside a component by calling getLocalizer()
. The localizer provides various getString
methods, two of the most important ones are:
getString(key, component)
getString(key, component, model)
You can use first one if your property does not contain any strings which should be substituted. The second one is used in case if your property contains values which should be replaces, such as
weather.message=Weather station reports that the temperature is ${currentTemperature} ${units}
In this case you can either provide an object which contains getters for currentTemperature
and units
as model or you can provide a MapModel
which maps currentTemprature
and units
to desired replacements. For example:
Map<String,String> map = new HashMap<String, String>(); map.put("currentTemperature", "12"); map.put("units", "C"); String string = getLocalizer().getString("test", this,new MapModel<String, String>(map)); // string = "Weather station reports that the temperature is 12 C"
Using StringResourceModel
The Localizer contains powerful string substitution methods for manipulating message values. Examples are provided in the javadoc for StringResourceModel, such this:
/** * In this example the found resource string contains a property expression that is * substituted via the model. */ public MyPage extends WebPage { public MyPage(final PageParameters parameters) { WeatherStation ws = new WeatherStation(); add(new Label("weatherMessage", new StringResourceModel("weather.message", this, new Model(ws))); } }
Where the resource bundle contains the entry
weather.message=Weather station reports that the temperature is ${currentTemperature} ${units}
For Localizing components, you mostly will want to rely on StringResourceModel. As mentioned before, you cannot call getLocalizer on a component before it has been attached to the framework (added to a hierarchy that has a Page). StringResourceModel uses Localizer inside it, but does not get rendered until after the component heirarchy has been established, so no exceptions. The StringResourceModel offers all the features of Localizer, plus the application of substitution parameters by java.text.MessageFormat. Again, there is an example demonstrating this in the StringResourceModel javadocs:
public MyPage extends WebPage { public MyPage(final PageParameters parameters) { WeatherStation ws = new WeatherStation(); Model model = new Model(ws); add(new Label("weatherMessage", new StringResourceModel( "weather.detail", this, model, new Object[] { new Date(), new PropertyModel(model, "currentStatus"), new PropertyModel(model, "currentTemperature"), new PropertyModel(model, "units") })); } }
And where the resource bundle entry is:
weather.detail=The report for {0,date}, shows the temparature as {2,number,###.##} {3} \ and the weather to be {1}
FormValidators derived from AbstractValidator or StringValidator also exploit Wicket's localization capabilities. Inside, they delegate localization to the Localizer. There must be a resource bundle properties file in the Localizer search path that has a messageKey equivalent to form-name.component-name.validator-class. This allows the validators to be Style and locale aware. Substitution is enabled, but parameterized MessageFormat is not.
Other Resources
Describe the loading of other resources by locale and style.
String Resource Loaders
Describe how to write new string resource loaders.