Tutorial: Internationalize ZK Web Applications (Part 1)

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)

    New ZK Project

    New ZK 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.

    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:

    Not internationalized yet

  • 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 folder

    i3-label.properties

    i3-label.properties

    This 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"));
     }
    ...
    

    Labels from properties file


    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 ?

    1. 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/
    2. 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);
     }
    
     }
     ...
     

    Application reading from properties file

  • 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 !

    Maintaining properties files

     # 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

    Internationalized 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.

Advertisements

5 thoughts on “Tutorial: Internationalize ZK Web Applications (Part 1)

  1. Pingback: Tutorial: Internationalize ZK Web Applications (Part 2) « The JavaDude Weblog

  2. Pingback: Automatic Version Numbering in Web Applications with Hudson (Part 3) « The JavaDude Weblog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s