...
Another extremely powerful concept is how the message is looked up in the property files. Normally a properties file with the same name as the current component is used. E.g. if you are working within the component SummaryPanel, the message is looked up in the file SummaryPanel_nl_NL.properties
(for Dutch, Netherlands locale). If that fails (either because the file does not exist, or because it does not contain the message), file SummaryPanel_nl.properties
, and then SummaryPanel.properties
are investigated. This is all just like Java's resource bundles work. But of course Wicket goes further. If the message is still not found, the property files of the parent class are investigated, and again, and again, all the way up to java.lang.Object
. For each class all locale variants are searched for. If that fails also, the parent component (in the page hierarchy) is used as reference to look up the properties file. The whole search game as described above starts again for this component. Searching continues up the component hierarchy until the page is reached. Finally, the class hierarchy trick is repeated with your application class. So Wicket first looks at MyApplication.properties
(provided MyApplication is the name of your application) and then up the class hierarchy, passing org.apache.wicket.Application
, up to java.lang.Object
.
This might sound complicated, but in practice you simply have one properties file per page and some more for components that are reused over multiple pages. For smaller applications you can even put everything in one properties file. These rules work so well that you just do what you think is correct and it almost always just is.
One note on the location of the properties files: like HTML files, they must be in the same package, and the same classloader as the component they are associated with. In practice they live next to each other in the same directory.
If you want Wicket to get its resources from somewhere else (e.g. from a database), you can implement the interface org.apache.wicket.resource.loader.IStringResourceLoader
and configure this in the init()
method of your application class.
References: ExtensionResourceNameIterator, ComponentStringResourceLoader, and for real control freaks the new PackageStringResourceLoader.
Reloading and caching
When Wicket is started in development mode, changed properties files are detected and reloaded. To properly make use of this feature from an IDE, you should run Wicket directly from the compiled sources, for example with the Start file, included in every QuickStart. In Eclipse you just save the properties file, in IntelliJ you must do a make (Ctrl-F9) before changes are picked up.
In production mode, resolved properties are heavily cached for performance. (Same applies to html files.)
Putting dynamic values in the messages
As soon as you need to add values to the messages, you also need to add some Java code. The java code provides the vales, but the rest of the text still comes from the properties file. One of the nice things here is how Wicket leverages java bean properties.
Here is a complete example. Not many frameworks make this so easy!
MyPanel.properties:
Code Block |
---|
summ: You, and $\{otherCount\} others, reviewed '$\{title\}' and rated it $\{rate\} |
.
MyPanel.html:
Code Block |
---|
<span wicket:id="summary">Text that will be replaced.</span> |
MyPanel.java:
Code Block |
---|
// Summary has getters for otherCount, title, etc.
Summary summary = ...;
add(new Label("summary", new StringResourceModel("summ", this, new Model(summary)))); |
Resulting in something like:
Code Block |
---|
<span>You, and 5 others, reviewed 'Wicket in Action' and rated it excellent.</span> |
Property based message key
It goes further: do you know a framework that can do this?
MyPanel.properties:
Code Block |
---|
summ.short: Thanks!
summ.long: You, and $\{otherCount\} others, reviewed '$\{title\}' and rated it $\{rate\}. Thanks! |
MyPanel.html:
Code Block |
---|
<span wicket:id="summary">Text that will be replaced.</span> |
MyPanel.java:
Code Block |
---|
// summary.getMsgPrefs().getStyle() returns "short" or "long"
add(new Label("summary", new StringResourceModel("summ.$\{msgPrefs.style\}", this, new Model(summary)))); |
...
Components are reusable, but in order to make it really reusable you should be able to override the messages depending on where the component is used. This is facilitated by first looking up the message (following the algorithm above) for every parent in the component hierarchy (aka page hierarchy). Every component can override the messages of its child components, so the search starts at the page's properties and then trickles down to the component that uses it (yes, its top-down). In order to make overrides specific to a certain child component, you can prefix the message key with the component id of the child. See ComponentStringResourceLoader for more details.
If no message was found in the page hierarchy, another search starts which will look at your application class and its super classes. So Wicket first looks at MyApplication.properties
(provided MyApplication is the name of your application) and then up the class hierarchy, passing org.apache.wicket.Application
, up to java.lang.Object
. This is how Wicket provides its many default i18n texts.
This might sound complicated, but in practice you simply have one properties file per page and some more for components that are reused over multiple pages. For smaller applications you can even put everything in one properties file. These rules work so well that you just do what you think is correct and it almost always just is.
One note on the location of the properties files: like HTML files, they must be in the same package, and the same classloader as the component they are associated with. In practice they live next to each other in the same directory.
If you want Wicket to get its resources from somewhere else (e.g. from a database), you can implement the interface org.apache.wicket.resource.loader.IStringResourceLoader
and configure this in the init()
method of your application class.
References: ExtensionResourceNameIterator, ComponentStringResourceLoader, and for real control freaks the new PackageStringResourceLoader.
Reloading and caching
When Wicket is started in development mode, changed properties files are detected and reloaded. To properly make use of this feature from an IDE, you should run Wicket directly from the compiled sources, for example with the Start file, included in every QuickStart. In Eclipse you just save the properties file, in IntelliJ you must do a make (Ctrl-F9) before changes are picked up.
In production mode, resolved properties are heavily cached for performance. (Same applies to html files.)
Putting dynamic values in the messages
As soon as you need to add values to the messages, you also need to add some Java code. The java code provides the vales, but the rest of the text still comes from the properties file. One of the nice things here is how Wicket leverages java bean properties.
Here is a complete example. Not many frameworks make this so easy!
MyPanel.properties:
Code Block |
---|
summ: You, and ${otherCount} others, reviewed '${title}' and rated it ${rate}. |
MyPanel.html:
Code Block |
---|
<span wicket:id="summary">Text that will be replaced.</span> |
MyPanel.java:
Code Block |
---|
// Summary has getters for otherCount, title, etc.
Summary summary = ...;
add(new Label("summary", new StringResourceModel("summ", this, new Model(summary)))); |
Resulting in something like:
Code Block |
---|
<span>You, and 5 others, reviewed 'Wicket in Action' and rated it excellent.</span> |
Property based message key
It goes further: do you know a framework that can do this?
MyPanel.properties:
Code Block |
---|
summ.short: Thanks!
summ.long: You, and ${otherCount} others, reviewed '${title}' and rated it ${rate}. Thanks! |
MyPanel.html:
Code Block |
---|
<span wicket:id="summary">Text that will be replaced.</span> |
MyPanel.java:
Code Block |
---|
// summary.getMsgPrefs().getStyle() returns "short" or "long"
add(new Label("summary", new StringResourceModel("summ.${msgPrefs.style}", this, new Model(summary)))); |
And more!
The Java property syntax is also still available (e.g. like {0,Date
}, {2,number,###.##
} etc.)? You can find all forms in the javadocs of StringResourceModel.
The Component
class has convenience method which you may find useful:
Code Block |
---|
add(new Label("summary", getString("summ.${msgPrefs.style}", new Model(summary)))); |
Putting wicket components into the message
If you want to translate a components text containing additional Wicket components (e.g. Links) you can use wicket:message-tag in combination with a container around it. Example a form component label related to a checkbox (label for) containing a link:
Java:
Code Block |
---|
final FormComponentLabel termsAndConditionsCheckLabel = new FormComponentLabel("termsAndConditionsCheckLabel", acceptedTerms);
form.add(termsAndConditionsCheckLabel);
final Link terms = new Link("terms") {
@Override
public void onClick() {
setResponsePage(TermsOfUsePage.class);
}
};
termsAndConditionsCheckLabel.add(terms);
final Label termsOfUse = new Label("terms_of_use", new ResourceModel("terms_of_use"));
termsOfUse.setRenderBodyOnly(true);
terms.add(termsOfUse);
|
Markup (HTML):
Code Block |
---|
<label wicket:id="termsAndConditionsCheckLabel">
<wicket:message key="termsAndConditionsCheckLabel">I am 18 years old and agree with the <a href="#" wicket:id="terms"><span wicket:id="terms_of_use">terms of use</span></a> and conditions.</wicket:message>
</label>
|
Messages (in ....properties.xml):
Code Block |
---|
<entry key="termsAndConditionsCheckLabel">I am 18 years old and agree with the ${terms_of_use} and conditions.</entry>
<entry key="terms_of_use">Terms of Use</entry>
|
The trouble with property files
...
Code Block |
---|
summ: You, and $\{othersLink\}, reviewed $\{titleLink\} and rated it $\{rate\}. others: $\{otherCount\} others |
MyPanel.html:
Code Block |
---|
<wicket:message key="summ">Text that will be replaced. <span wicket:id="rate">rate</span> <a href="#" wicket:id="titleLink"> <span wicket:id="titleLabel">label</span></a> <a href="#" wicket:id="othersLink"> <span wicket:id="othersLabel">label<</span></a> </wicket:message> |
...
Code Block |
---|
// Note, we directly add the embedded components // rate, othersLink and titleLink add(new Label("rate", new PropertyModel(summary, "rate"))); Link othLink = new Link("othersLink") \{ .... \} add(othLink); othLink.add(new Label("othersLabel", new StringResourceModel( "others", this, new Model(summary)))); ExternalLink titleLink = new ExternalLink( "titleLink", summary.getTitleUrl()); add(titleLink); titleLink.add(new Label("titleLabel", new PropertyModel(summary, "title"))); |
...
If you want runnable code you can download the complete example code (a Maven 2 project based on Wicket's QuickStart). See below for instructions.
...
Despite all of the above, there always remain some cases in which you need direct access to the messages. Luckily Wicket provides access through the Localizer. You can get the localizer from any component with getLocalizer()
.
Again, see the complete example code for an example.
Encoding troubles
Fairly unknown to beginning programmers is that you are only allowed to use ISO-8859-1 encoding in java properties files. If you live in Europe this is a fairly annoying as many languages have characters that are not known to ISO-8859-1 (for example the euro symbol €). The simple workaround is escaping: cree\u00EBr instead of creeër. (I always use this site to look up the ISO codepoint.)
...
Code Block |
---|
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <entry key="Required">ข้อมูลใน $\{label\} เป็นที่ต้องการ.</entry> </properties> |
...
To test the code in this article I created a small test application. Unzip it, and run mvn jetty:run to start it. When it is started access it on http://localhost:8080/i18ntest.
Changing resource settings
Finally, if you need more power, you can change all of Wicket's settings in the init()
method of your application. Call getResourceSettings() to get a IResourceSettings instance. Let look at some of the options.
ThrowExceptionOnMissingResource: this will make Wicket throw an exception when a resource is missing. At first this may seem a convenient way to test that you listed all messages in a properties file. However, many standard Wicket components use defaults as fall back, so this option is mostly useless. Alternatively, watch for warnings in the log.
UseDefaultOnMissingResource: this will make Wicket use the default value (e.g. the text within the wicket:message element) when the resource is not found in a properties file. Setting this to true (the default) may hide errors for a long time, but setting this false will make your site not work if you made an error. Choose carefully.
...