martedì 18 marzo 2008

The AgletsTranslator: considerations about the adoption of a ResourceBundle

The Java library provides the java.util.ResourceBundle class that can be used to localize within a Java run-time. Localizing means that each kind of resource can be adapted to the execution environment, in order to present to the users a more comprehensive resource. The simplest example is that of strings embedded in a program, that can be translated into the native language of the final users.

Since mobile agents can run on very different hosts, it is important that they can provide to the final users a localized version of messages and other resources (e.g., icons, images). In this brief post I present my implementation of the org.aglets.util.AgletTranslator class, that can be used both from agents and the platform to translate resources (mainly string messages).

The AgletsTranslator is a wrapper around the ResourceBundle that provides simplified services for the localization. Let's start considering the class implementation base:

public class AgletsTranslator implements Cloneable {

/**
* The resource bundle used to handle locale content.
*/
private transient ResourceBundle bundle = null;
public String translate(String text){
// be sure there is something to translate and I have a bundle to
// ask for translation
if( text != null && text.length() > 0
&& this.bundle != null
&& this.bundle.containsKey(text)){
String translated = null;
translated = this.bundle.getString(text);
logger.translation(text, translated);
return translated;
}
else
// nothing to do, return the string passed as argument
return text;
}

...

}
As readers can see, the class wraps an instance of a ResourceBundle, and then provides the translate(String) method that translates the text. The latter method works as follows: if the resource bundle contains the key (i.e., an identifier of the text that must be translated), then the returned text is provided by the resource bundle (and thus is a translated text). If the bundle does not contain the key (i.e., cannot provide a translation for the specified text), then the untraslated text is provided. This means that, if the key passed as a string has not been included in the translation configuration, then this text will be returned unmodified. This allows, in any moment, an agent or a developer can try to translate a string without worring too much about its availability in the translation system. This also means that a developer can use always the translate(..) method without worrying about the translation dictionary, that can become available even later the end of the development. So, the adoption of the translate(..) method for issuing strings is a good practice.

The AgletsTranslator is constructed knowing a Locale (i.e., an object that represents the configuration of the hosting platform) and a basename. A basename represent a configuration point for a resource bundle. The basename can be either a class name (fully qualified name) that provides a translation by its own, or a file name (e.g., a property file) that is within the classpath. In the case of the AgletsTranslator, the platform uses a property file called tahiti.properties and placed within the lib directory. The file contains a dictionary with entries like the followings:

com.ibm.aglets.tahiti.LoginDialog.usernameLabel = Username:
com.ibm.aglets.tahiti.LoginDialog.passwordLabel = Password:
com.ibm.aglets.tahiti.LoginDialog.okButton = Log in
com.ibm.aglets.tahiti.LoginDialog.okButton.tooltip = Performs the log-in into Aglets
com.ibm.aglets.tahiti.LoginDialog.okButton.icon = img/ok.png


It is possible to see the key (e.g., com.ibm.aglets.tahiti.LoginDialog.usernameLabel) and the appropriate translation (e.g., Username:). Please note that a resource bundle can be used also for icons and images, not only for text, as already stated before.
The convention of using the fully qualified class name as part of the key is just a good practice that allows a quick individuation of where a key is used in the application itself.

Having obtained an AgletsTranslator its use is quite simple:

AgletsTranslator translator = ...;
String translated = translator.translate("com.ibm.aglets.tahiti.LoginDialog.usernameLabel");
Icon icon = new ImageIcon( translator.translate("com.ibm.aglets.tahiti.LoginDialog.okButton.icon") );




How can a translator be obtained? The AgletsTranslator class provides a factory method to obtain a new instance:

public static AgletsTranslator getInstance(String localeBaseName, Locale currentLocale) {
String key = localeBaseName + currentLocale.toString();

// search first in the cache
if( translators.containsKey(key) )
return translators.get(key);
else{
AgletsTranslator translator = new AgletsTranslator(localeBaseName, currentLocale);
translators.put(key, translator);
return translator;
}

}

private static HashMap translators = new HashMap();




As readers can see the factory method searches first for an already constructed translated for the specified basename and locale. The adoption of such caching technique provides the same translator for the same code base, keeping the creation of translators under control and a lower memory footprint. In case the translator has not been created, it is created and a reference to it is stored in the translator map for caching.

Nessun commento: