Xoetrope
View

Introduction tutorial - Localizing the screens - Localize a page
SUMMARY  Localization is made very easy with XUI applications because most of the screen information is already in XML format and separated from your Java code.

FURTHER READING  Languages such a Chinese require a specific font to be used in order to appear properly. This will appear in the 'Advanced' tutorial in the near future.

Many modern applications are localized for different target audiences. With XUI this is a straightforward process because the page text is already stored outside the Java source code. The first thing to do is to set the resource attribute for each page which needs to be translated.

Listing 1 - Page 'resource' attribute
<XPage next="personal" class="net.xoetrope.mortgage.Welcome" resource="">
    ...

If you wanted a page to use a specific resource each time regardless of the application language you could specify the resource name here. But for this example, we will set the application language from the startup properties by placing the following keypair in the startup.properties file.

Listing 2 - startup.properties
    ...
Language=en
    ...

Now we will extract all of the text from the xml page and put it into a resource file. So, beginning with welcome.xml substitute the string for property names.

Listing 3 - welcome.xml
<XPage next="personal" class="net.xoetrope.mortgage.Welcome" resource="">
  <Components>		
    <Label x="10" y="5" w="80" h="20" style="prompt" content="WEL_LANG" 
		alignment="Left" opaque="true"/>
    <Combo name="languageList" x="90" y="5" w="120" h="20"/>
    <Label x="130" y="84" w="350" h="60" style="Heading" content="WEL_TEXT" 
		alignment="Left" opaque="true"/>
    <Panel x="0" y="150" w="650" h="50" style="banner/prompt">
      <RadioButton name="soleRadio" x="226" y="15" w="100" h="20" 
	  style="banner/prompt" content="WEL_SOLE" alignment="Leading"/>
      <RadioButton name="jointRadio" x="330" y="15" w="100" h="20" 
	  style="banner/prompt" content="WEL_JOINT" alignment="Leading"/>
    </Panel>
  </Components>
    ...

Now put these keypairs into a file called en.properties in the resources directory. Please note that the entire file's contents are not listed here but the files can be accessed by clicking the 'Open the project directory' link and the bottom of the page and going to the resources directory.

Listing 4 - en.properties
WEL_LANG=Language
WEL_TEXT=Welcome to ABC Bank's mortgage application. 
	Before we proceed please note the following...
WEL_SOLE=Sole
WEL_JOINT=Joint
BAN_TITLE=ABC Bank Mortgage Application
BAN_CLOSE=Close Application
PER_HEADING_1=Personal Details (First Applicant)
PER_HEADING_2=Personal Details (Second Applicant)
    ...

At the same time create a file called fr.properties in the resources directory to cater for French. Please excuse the translation (it was done with babelfish). Again, only part of the file is shown here.

Listing 5 - fr.properties
WEL_LANG=Langue
WEL_TEXT=Bienvenue à la demande de prêt hypothécaire de l'hypothèque de la banque 
	de ABC. Avant que nous veuillez procéder note le suivant...
WEL_SOLE=Unique
WEL_JOINT=Joint
BAN_TITLE=Demande de prêt hypothécaire D'Hypothèque De Banque de ABC
BAN_CLOSE=Application Étroite
PER_HEADING_1=Détails Personnels (Premier Demandeur)
PER_HEADING_2=Détails Personnels (Deuxième Demandeur)
    ...

That's all that's needed to translate the screen text directly. But there are other translations which are required and we will look at that next.

In the personal page, we are setting the title of the screen depending on the current customer ('Personal Details (First Applicant)'). So in the Personal.java file we need to make the following changes...

Listing 6 - Personal.java
    ...
  public void pageActivated() {
    if ( ( currentCust.get().toString().compareTo("1")==0 ) )
      titleLabel.setText( translate( "PER_HEADING_1" ) );
    else
      titleLabel.setText( translate( "PER_HEADING_2" ) );
  }
    ...

The translate function is in the pageActivated super class which checks the application language and carries out the appropriate translation. So, translating from your code is just as straightforward as translating your page xml.

What needs to be done next is to translate the data which is used in the combobox on the personal page. Transfer the strings in the statics.xml file into the resource files.

Listing 7 - statics.xml
    ...
    <list id="titleList">
      <item value="TITLE_LIST_MR" id="1" /> 
      <item value="TITLE_LIST_MRS" id="2" /> 
      <item value="TITLE_LIST_MISS" id="3" /> 
      <item value="TITLE_LIST_DR" id="4" /> 
      <item value="TITLE_LIST_FR" id="5" /> 
    </list>
    ...

Now the personal.xml file needs to be changed in order to use localized list binding in the XuiOptional.jar file

Listing 8 - personal.xml
    ...
<Bind type="custom" target="titleList" source="/defaults/titleList" 
	output="/mortapp/${getCustomerID()}/title" 
	class="net.xoetrope.optional.data.XLocalisedListBinding"/>
    ...

The type attribute tells the XuiBuilder that a custom XValidator is going to be used. The class attribute contains the name of the class which will do the binding.

First move the validation text from validations.xml into the resource files.

Listing 9 - validations.xml
<Validations>
  <validation name="firstname" type="mandatory" msg="VALID_FNAME" first="true"/>
  <validation name="surname" type="mandatory" msg="VALID_SNAME"/>
  <validation name="propvalue" type="custom" 
	class="net.xoetrope.mortgage.MinMaxValidator" 
	min="2000" max="2000000" msg="VALID_PROPVALUE" mandatory="true"/>
  <validation name="mortamt" type="custom" 
	class="net.xoetrope.mortgage.MinMaxValidator" 
	min="2000" max="1000000" msg="VALID_MORT_AMT" mandatory="true"/>
</Validations>

In order to localize the mandatory validation text the ExceptionHandler.java file needs to be modified. Change the handleException function to translate the validation text.

Listing 10 - ExceptionHandler.java
    ...
    validationText += currentPage.translate( msg );
    return true;
    ...

While the ExceptionHandler.java is open now is a good time to translate the title of the error dialog window. Declare a local String called title and populate it within the class contructor.

Listing 11 - ExceptionHandler.java
    ...
  private String title = "";
    ...
  public ExceptionHandler( XPage page )
  {
    ResourceBundle rb = XProjectManager.getCurrentProject().getResourceBundle( 
		XProjectManager.getCurrentProject().getStartupParam( "Language" ) );
    title = rb.getString( "ERROR_TITLE" );
    currentPage = page;
  }
    ...

The getStartupParam function will retrieve any of the properties in the startup properties file. The getResourceBundle function of the XProject class will open a resource file on the classpath. The title variable is populated from the resource file. Now when the error messages are shown the call is changed to...

Listing 12 - ExceptionHandler.java
    ...
    currentPage.showMessage( title, validationText );
    ...

The localization of the minmax validations is a little more complicated. In order to translate these validations it's necessary to subclass the XMinMaxValidator class as follows...

Listing 13 - MinMaxValidator.java
package net.xoetrope.mortgage;

import java.util.ResourceBundle;
import net.xoetrope.xui.helper.NumberFormatter;
import net.xoetrope.xui.validation.XMinMaxValidator;

public class MinMaxValidator extends XMinMaxValidator
{
  public MinMaxValidator()
  {
    super( null, 0 );
  }

  protected void replaceTokens()
  {
    if ( start )
      formattedMessage = message;
    
    ResourceBundle rb = XProjectManager.getCurrentProject().getResourceBundle( 
		XProjectManager.getCurrentProject().getStartupParam( "Language" ) );
    formattedMessage = rb.getString( formattedMessage );
    replaceToken( "{min}", String.valueOf( getMin() ).replace( '.', 
		NumberFormatter.getDecimalSeparator() ) );
    replaceToken( "{max}", String.valueOf( getMax() ).replace( '.', 
		NumberFormatter.getDecimalSeparator() ) );
    
    super.replaceTokens();
    start = false;
  }

}

In the new class the replaceTokens function of the super class is overloaded. Within this function the current language resource bundle gets the localized validation text. The replaceToken function of the super class replaces the passed text with the minimum and maximum values of the validator.

Now the custom validator is used by specifying the new class in the class attribute of the validations.xml file as shown in listing 9.

Now we can change language simply by setting the Language property of the startup.properties file, but in most localized applications the user is usually given the option of changing the language. We will do this by placing a combobox on the welcome page.

Listing 14 - welcome.xml
<XPage next="personal" class="net.xoetrope.mortgage.Welcome" resource="">
  <Components>		
    <Label x="10" y="5" w="80" h="20" style="prompt" content="WEL_LANG" 
		alignment="Left" opaque="true"/>
    <Combo name="languageList" x="90" y="5" w="120" h="20"/>
    <Label x="130" y="84" w="350" h="60" style="Heading" content="WEL_TEXT" 
	alignment="Left" opaque="true"/>
    <Panel x="0" y="150" w="650" h="50" style="banner/prompt">
      <RadioButton name="soleRadio" x="226" y="15" w="100" h="20" 
		style="banner/prompt" content="WEL_SOLE" alignment="Leading"/>
      <RadioButton name="jointRadio" x="330" y="15" w="100" h="20" 
		style="banner/prompt" content="WEL_JOINT" alignment="Leading"/>
    </Panel>
  </Components>
  <Events>
    <Event method="changeLanguage" target="languageList" type="ItemHandler"/>
    <Event method="langClicked" target="languageList" type="MouseHandler"/>
  </Events>
  <Data>
    <Bind target="languageList" source="defaults/languages" output="temp/lang" />
  </Data>
</XPage>

The new combobox is populated using the new binding which loads the data from a new list in the statics.xml file and an ItemHandler is bound to the combobox which invokes the changeLanguage function.

Listing 15 - statics.xml
    ...
    <list id="languages">
      <item value="English" id="en" /> 
      <item value="Français" id="fr" /> 
    </list>
    ...

Now the Welcome.java file needs to be updated.

Listing 16 - Welcome.java
public class Welcome extends XPage {

  XModel currentCust;
  XComboBox languageList;
  boolean langClicked = false;

  public void pageActivated() {
    currentCust.set( "1" );
  }

  public void pageCreated() {
    currentCust = (XModel)rootModel.get( "temp/currentCust" );
    languageList = (XComboBox) findComponent("languageList");
  }
 
  public void changeLanguage()
  {
     if ( langClicked ) {    
    	String lang = ( String )languageList.getSelectedObject();
    	pageMgr.reset();
    	String langCode = lang.compareTo( "English" ) == 0 ? "en" : "fr";
    	project.setStartupParam( "Language", langCode );
    	pageMgr.loadFrames( "frames", true );
     }
  }

  public void langClicked()
  {
    langClicked = true;
  }

}

The reset function of the XPageManager class unloads any cached pages. The newly selected language code is set for the Language startup parameter and the loadFrames function of the XPageManager is called with the name of the frames file..

After recompiling, execute the run.bat file and the application will appear as in the screenshot.

Log in or register to download the source code for this step.