Oct 3, 2009

GWT Internationalization Tutorial

Introduction

Internationalization (i18n for short) is the process of adding a framework to support different national languages to your program. Localization (l10n) occurs when you use that framework to customize the program for each language. National language support is only going to get more important over time as internet usage grows in countries like China, India, and Brazil. Luckily GWT provides full and flexible support as of version 1.1.10 of the toolkit. Even if your application is not intended for a global audience, it pays to put all your human readable text in one place. You can more easily spell check your messages and ensure consistency between them if they’re all in the same file. It also lets non-programmers change the messages to correct grammatical errors or trademark usages without having to modify the code. The standard Java way to accomplish this is through resource bundles and property files. GWT lets you use these familiar concepts in your web applications as well.


Constants, Messages, and Dictionary


GWT provides four alternatives for localized text:
  • Constants : This type can only be used for text that has no substitutions, such as field labels or the names of menu items. It can also be used for numbers, Booleans, and Maps.
  • ConstantsWithLookup : This is the same as the Constants interface except you can look up a constant with a dynamic string (more on this later).
  • Messages : These are general purpose strings that can include placeholders for substitutions.
  • Dictionary : The most flexible but least efficient of all the choices, the Dictionary interface supports dynamically specifying the locale.


Constants, ConstantsWithLookup, and Messages are more efficient than Dictionary because the locale can be determined ahead of time and compiled into the application. The GWT compiler produces a different .cache.html file for each locale, and the appropriate version is loaded at run time. Constants and Messages gain a little more efficiency by pruning resources that aren’t actually used in the program.


The Messages interface is the best choice for most applications, so the rest of this chapter will show you how to use that. The API for the other interfaces is similar, and it’s possible to use more than one style in the same application.


Creating the properties file


We’ll start with a simple program and convert it to use Messages instead of hard-coded strings.


Here is the original program:
package com.xyz.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* Entry point classes define onModuleLoad().
*/
public class I18NOrig implements EntryPoint {
private Button m_clickMeButton;
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
{
m_clickMeButton = new Button();
rootPanel.add(m_clickMeButton);
m_clickMeButton.setText("Click me!");
m_clickMeButton.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
Window.alert("Hello, GWT World!");
}
});
}
}
}


The first step is to find all the strings and copy them into a properties file. Use the standard name=value format. Comments (for example, notes to translators) start with a pound sign (#). Placeholders for substitution parameters are specified with {0}, {1}, and so forth. Here is the properties file for the converted program:


m_clickMeButton_text=Click me!
m_helloAlert_text=Hello, {0} World! 

The file will be needed on the client so put it in the com.xyz.client package. This is the default language file, which will be in English (or your native language). To provide translations for other languages, use ISO language and country code suffixes, for example "_fr" for French or "_fr_CA" for Canadian French. Here’s the French version:


m_clickMeButton_text=Cliquez-moi!
m_helloAlert_text=Bonjour, Monde de {0}!


Creating the accessor class


If you’re familiar with standard Java messages you know they’re accessed by string. To get the translated message you call a function and pass it the key string, for example getString("m_clickMeButton_text"). There are two problems with this approach. First, the message keys may be as long as or longer than the message values. Second, if you make a mistake in the message key it can’t be caught until run time.


To address these problems and make the web application as small as possible, GWT tweaks the way messages work. Instead of referring to the message using a string, you refer to it using a Java method. This requires you to create a new Java class that has a method corresponding to each message in your properties file. Here is the class for this simple example:
package com.xyz.client;
import com.google.gwt.i18n.client.Messages;
public interface AppMessages extends Messages {
String m_clickMeButton_text();
String m_helloAlert_text(String toolkit);
}
Note that there is only one accessor class, no matter how many languages you support in your application.


Referring to messages


The next step is to replace the static strings with references to your new messages in the code. After getting a reference to the accessor class using GWT.create, you call the new methods to retrieve the message text. Here’s the finished version:
package com.xyz.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.xyz.client.AppMessages;
/**
* Entry point classes define onModuleLoad().
*/
public class I18N implements EntryPoint {
private static final AppMessages MESSAGES = (AppMessages) GWT
.create(AppMessages.class);
private Button m_clickMeButton;
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
{
m_clickMeButton = new Button();
rootPanel.add(m_clickMeButton);
m_clickMeButton.setText(MESSAGES.m_clickMeButton_text());
m_clickMeButton.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
Window.alert(MESSAGES.m_helloAlert_text("GWT"));
}
});
}
}
}


Making module change


Two minor changes are needed to your module file to complete the transition. First, you need to tell GWT that you are inheriting functionality from the I18N module, and second, you need to define which languages you have provided translations for. Here is the final modules file after these changes have been made:


Running the example


Now it’s time to try out the sample. Run it in hosted mode first. If you’ve imported the I18NProject sample project in Eclipse, select Run ! Debug..., and click on the launch configuration titled I18NProject (under Java Application). Then click on Debug. The default (English) version should appear.


To try the French version, edit the URL in the browser to add the suffix ?locale=fr. Press return and the page should refresh, showing French instead of English. Click on the button to see French text in the dialog (See output ).


Another way to select the locale is to embed it as a meta tag in the HTML file, for example


<meta content="locale=ja_JP" name="gwt:property"></meta>







This could be handy if the HTML was dynamically generated on the server, for example by a JSP file, based on session settings.


Some developers argue that internationalization should be done from the start of a project, while others say it should be saved until the end. In practice it doesn’t really matter. Given maintenance releases and newer versions, "the end" is never really the end. Converting an existing project to use message bundles is fairly quick and painless, especially if you have good tool support.


References


This article has been prepared by referring to the following:

1 comments:

Rob Coops said...

Thanks for the simple explanation it helped me a lot...

Text Widget

Copyright © Vinay's Blog | Powered by Blogger

Design by | Blogger Theme by