I would say internationalization is a key feature in any software that is (potentially) used in different countries or user with different languages. You dont want to inject this feature at the end of your implementation but put in place right at the beginning. Using todays framework is not a big deal to hook on the browsers locale settings and make an application speaking that language (if it is provided, otherwise fall back into the default language). I found 2 tutorials for the ZK Ajax framework covering internationalization (link1, link2), I will add another approach here, focusing on the separation of zul page (viewer) and its controller.
Requirements:
- Netbeans 6.8
- Glassfish V3 Application Server
- Installed ZK Plugin for Netbeans (link)
(Older Versions and non-EE6 work as well, Internationalization is not a Java EE6 specific feature)
Tutorial:
- Create a new Web Application
ZKInternationalDemo (ZK5 EE6 project) - Create a Controller Class
indexController.java in package controller
We strictly separate the zul file and any business logic, the zul file only contains components with ID’s pointing to the controller class.Extending GenericForwardComposer and overriding the doAfterCompose method to control the page before it is rendered in the browser.
package controller; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.util.GenericForwardComposer; public class indexController extends GenericForwardComposer { @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); } }
- Adding components to the page
Add labels,inputbox and 3 buttons that shall display text according to the browsers language.
We use the apply approach (<window id=”win” apply=”controller.indexController” >) for the window, all implementation is done in the controller.
Important: All components must have unique ID’s.<?xml version="1.0" encoding="UTF-8"?> <zk xmlns="http://www.zkoss.org/2005/zul"> <window id="win" apply="controller.indexController" > <vbox> <label id="lblApplName" value="##Internation Demo Application"/> <hbox> <label id="lblID" value="##Your Name" /> <textbox id="txbName" value=""/> </hbox> <button id="btnSave" label="##Save" "/> <button id="btnQuit" label="##Quit" "/> <button id="btnCancel" label="##Cancel" "/> </vbox> </window> </zk>
Why using ## for the labels in the zul file ? Later the labels will read the value from properties files for the respective language. In case you forget to add a label in the properties file, you will see the ## telling you visually that you forgot to do it.
Deploying and running the application will result in something like this:
- Implementing the controller and languages (Approach 1)
Following the apply approach we need to create locale variables in our controller (that ZK bind to the component on the zul page).
Now we could (in the most manual way) set the labels for our components manually and maybe derive the language from some configuration settings.package controller; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.util.GenericForwardComposer; import org.zkoss.zul.Button; import org.zkoss.zul.Label; public class indexController extends GenericForwardComposer { Button btnSave; Button btnCancel; Button btnQuit; Label lblID; Label lblApplName; @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); btnSave.setLabel("SAVE BUTTON"); btnCancel.setLabel("CANCEL BUTTON"); } }
- Create a language property file (Approach 2)
We can tap into the internationalization features of our framework that get the browser locale setting for us and read the respective label from different properties files.
Create a file i3-label.properties in the /WEB-INF folderThis file contains the default text/labels independent from the browser setting.
idsave=Save idcancel=Cancel idquit=Quit idyourname=Your Name idapplname=Multiple Language Demo Application
Now we can read the label from the properties file.
... @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); btnSave.setLabel(Labels.getLabel("idsave")); btnQuit.setLabel(Labels.getLabel("idquit")); btnCancel.setLabel(Labels.getLabel("idcancel")); } ...
You could create a second properties file named i3-label_de.properties by copying the existing file and translate all labels to german. Once you change the browser language and run the web application it will show the german lables.What are the problems with this approach ?
- The id’s we use in the properties file are not very unique. You might have the same button several times in your application, you end up creating idname1, idname2, … and it would be very hard to link from the properties file to the screen (specifically if a translator do the job for you editing your properties file). We need a convention that includes the screen or controller as part of the ID/
- We need to code each control. Very hard to maintain in the controller class and properties file. We need a way to iterate through the components and do the job.
- Auto-Create ID’s (Approach 2)
We use the Java Reflection API (link) and enumerate through all class variables creating id’s with his syntax: package.classname.variable... import java.lang.reflect.Field; ... @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); for (Field f : this.getClass().getDeclaredFields()) { System.out.println(this.getClass().getName() + "." + f.getName() + " of type " + f.getType().getName()); } } } ...
Lets look at the resulting log:
... INFO: controller.indexController.btnSave of type org.zkoss.zul.Button INFO: controller.indexController.btnCancel of type org.zkoss.zul.Button INFO: controller.indexController.btnQuit of type org.zkoss.zul.Button INFO: controller.indexController.lblID of type org.zkoss.zul.Label INFO: controller.indexController.lblApplName of type org.zkoss.zul.Label
- Update the properties file
# index.zul controller.indexController.btnSave=Save controller.indexController.btnCancel=Cancel controller.indexController.btnQuit=Quit controller.indexController.lblID=Your Name controller.indexController.lblApplName=Multiple Language Demo Application
- Update the controller class
... @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); for (Field f : this.getClass().getDeclaredFields()) { // System.out.println(this.getClass().getName() + "." + f.getName() + " of type " + f.getType().getName()); String compName = this.getClass().getName() + "." + f.getName(); String compLabel = Labels.getLabel(compName); String compType = f.getType().getName(); if (compType.equals("org.zkoss.zul.Button")) ((Button) f.get(this)).setLabel(compLabel); else if (compType.equals("org.zkoss.zul.Label")) ((Label) f.get(this)).setValue(compLabel); } } ...
- Add one more language file
Copy the properties file and rename it to i3-label_de.properties. Once you did that Netbeans will give you a nice table to maintain your translations !# index.zul controller.indexController.btnSave=Speichern controller.indexController.btnCancel=Abbrachen controller.indexController.btnQuit=Beenden controller.indexController.lblID=Ihr Name: controller.indexController.lblApplName=Mehrsprachige Demo Anwendung
- Change the Browser language
Change it to German (depending on the browser, but usually somewhere under settings) and run the application
Conclusion:
- We automatized the label handling as much as possible and Netbeans helps us to maintain our translations.
- We are using properties id’s that are unique and can easily be identified during the translation process.
- But we are not at WEB 2.0 yet. Who wants to open the preferences to change the language ? … Anyone ? …. NO ONE !
Stay tuned for part 2 to tweak that.
Acknowledgment: Thanks to my teammate Alphy for creating this smart solution approach.
Pingback: Tutorial: Internationalize ZK Web Applications (Part 2) « The JavaDude Weblog
Pingback: Automatic Version Numbering in Web Applications with Hudson (Part 3) « The JavaDude Weblog
How to split i3-label.properties into more files, when it is becoming huge?
How to call this translación controller from a zul that o already has a VMMV propertie
I have a Question , how can I call zul this controller from a Zul that has MVVM properties,