Internationalisation i18N avec GWT

gwt-logo.pngMe voila arrivé dans mon projet ou j'ai envie d'externaliser les chaînes de caractères pour pallier aux différents problèmes de locale qui peuvent être soit la locale utilisé dans les composants GWT, soit un quelconque problème d'enc@$ding dans les libellés.

Venant du monde JSF j'ai d'abord pensé qu'il fallait passer par des ressources bundle... que nenni !!!

Vous avez deux options ; faire tout à la main comme indiqué dans la documentation officielle utiliser l'outil de génération fourni en standard avec le compilateur GWT

J'exposerai dans ce billet deux types d'externalisation :

  1. l'externalisation des messages/constantes utilisés dans le code
  2. l'externalisation des messages contenus dans les fichiers ui.xml

Configuration préalable

Pour activer la prise en compte de l'internationalisation ou i18n pour les intimes, il faut d'abord ajouter la configuration dans le fichier module.gwt.xml

<inherits name="com.google.gwt.i18n.I18N" />
	<extend-property name="locale" values="fr" />
	<extend-property name="locale" values="en" />

On obtient donc le fichier suivant :

<?xml version="1.0" encoding="UTF-8"?>
<module rename-to="admin">
	<inherits name="com.google.gwt.user.User" />
	<inherits name="com.google.gwt.logging.Logging" />
	<inherits name="com.google.gwt.i18n.I18N" />
	<inherits name='com.google.gwt.user.theme.chrome.Chrome' />
	<inherits name="info.touret.winecellar.userinfo.UserInfo" />
	<inherits name="info.touret.winecellar.dao.persistencecommon" />
	<inherits name="com.googlecode.objectify.Objectify" />
	<inherits name='com.google.gwt.activity.Activity' />
	<inherits name="com.google.web.bindery.requestfactory.RequestFactory" />
	<source path="client" />
	<entry-point class="info.touret.winecellar.admin.client.Admin"></entry-point>
	<extend-property name="locale" values="fr" />
	<extend-property name="locale" values="en" />
</module>

l'externalisation des messages/constantes utilisés dans le code

Les messages et constantes

l'API i18n de GWT embarque de nombreuses classes avec des responsabilités bien déterminées :

  • Constants : des constantes externalisées
  • Messages : les messages et phrases externalisées ( des paramètres sont possibles )
  • ConstantsWithLookup : équivalent à Constants mais avec un comportement dynamique
  • Dictionary : Un dictionnaire embarqué dans la page hôte
  • Localizable : Spécialise le comportement d'une classe en fonction d'une ressource donnée
  • DateTimeFormat, NumberFormat : Formattage des des dates et nombres en fonction de la locale.

Il est également possible d'externaliser les messages contenus dans les page ***.ui.xml.

Création des messages

Voici ce que j'ai fait avec l'API Messages

J'ai crée une interface AdminMessages

import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
import com.google.gwt.i18n.client.LocalizableResource.Generate;
import com.google.gwt.i18n.client.Messages;
 
 
@Generate(
format = { "com.google.gwt.i18n.rebind.format.PropertiesFormat" }, 
fileName = "AdminMessages", 
locales = {"fr","en","default"})
@DefaultLocale("en")
public interface AdminMessages extends Messages {
	@DefaultMessage("insert failed")
	@Key("insertko")
	public String insertko();
 
	@DefaultMessage("insert is ok")
	@Key("insertok")
	public String insertok();
 
	@DefaultMessage("The parameters are wrong")
	@Key("udpatefailwithparameters")
	public String udpatefailwithparameters();
 
	@DefaultMessage("Update failed")
	@Key("udpatefail")
	public String updatefail();
}

J'ai indiqué au niveau de la classe que je souhaitai que le compilateur génère les fichiers properties correspondants avec pour base le nom AdminMessages. J'ai aussi spécifié les locales disponibles et celle par défaut.

Ensuite, pour chaque méthode, j'ai spécifié le message par défaut ainsi que la clé utilisée lors de la génération.

Dans mon module, j'accède aux messages de la manière suivante :

AdminMessages constants ;
[...]
constants= GWT.create(AdminMessages.class);
 
[...]
 
Window.alert(constants.insertok());

Génération des resourcebundle

GWT permet la génération des fichiers properties avec les clés définies précédemment dans l'interface. Lors de la compilation, il faut ajouter le paramètre -extra pour la génération soit effective. Si vous utilisez maven, la configuration de votre plugin devrait ressembler à quelque chose comme ça :

<plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>gwt-maven-plugin</artifactId>
                <version>2.3.0-1</version>
                <dependencies>
                    <dependency>
                        <groupId>com.google.gwt</groupId>
                        <artifactId>gwt-user</artifactId>
                        <version>${gwt.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>com.google.gwt</groupId>
                        <artifactId>gwt-dev</artifactId>
                        <version>${gwt.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>com.google.gwt</groupId>
                        <artifactId>gwt-servlet</artifactId>
                        <version>${gwt.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <appEngineVersion>${gae.version}</appEngineVersion>
                    <logLevel>INFO</logLevel>
                    <style>${gwt.style}</style>
                    <runTarget>/Winecellar.html</runTarget>
                    <extraJvmArgs>-Xmx512M -Xss1024k</extraJvmArgs>
                    __<extraParam>true</extraParam>__
                    <server>com.google.appengine.tools.development.gwt.AppEngineLauncher</server>
                    <copyWebapp>true</copyWebapp>
                    <modules>
                        <module>info.touret.winecellar.admin.Admin</module>
                        <module>info.touret.winecellar.winecellar</module>
                    </modules>
                    <i18nMessagesBundle>info.touret.winecellar.admin.client.AdminMessages</i18nMessagesBundle>
                </configuration>
                <executions>
                    <execution>
                        <id>gwtcompile</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

J'obtiens donc les fichiers suivants dans le répertoire target/extra/

  • AdminMessages_fr.properties
  • AdminMessages_default.properties
  • AdminMessages_en.properties

Avec un contenu ressemblant à ça :

# Generated from info.touret.winecellar.admin.client.AdminMessages
# for locale en

insertko=insert failed

insertok=insert is ok

udpatefail=Update failed

udpatefailwithparameters=The parameters are wrong

Il ne vous reste plus qu'à copier les fichiers dans le répertoire src/main/resources/mon package de l'interface

Petit bémol sur la version 2.3 du toolkit de google, si vous mettez des apostrophes, il y aura une exception bloquante à l'execution

 java.text.ParseException: Unterminated single quote
...

Le problème est connu coté google, à voir pour la suite ...

Internationalisation des fichiers ui.xml

Tout d'abord, je vous conseille de lire attentivement la documentation officielle , elle est bien faite ;)

Voila ce que j'ai fait dans mon code

Pour paramétrer la génération :

à la racine de mon fichier ui.xml

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
    ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"
    ui:generateLocales="default,en,fr"

Pour la description du message

On ajoute les libellés avec la balise ui:msg

<g:header><ui:msg description="users title">Users </ui:msg></g:header>

Ensuite, a l'instar des messages, on lance la compilation gwt avec le paramètre -extra . On obtient les fichiers présents avec pour nom le chemin absolu ( package inclus ) . Ex : info.touret.....AdminConsoleAdminConsoleUiBinderImplGenMessages_default.properties . Il nous reste plus qu'à le copier dans le répertoire src/main/resources/ mon package du fichier ui.xml

Forcer l'application d'une locale

Contrairement à JSF/JSP , l'application de la locale n'est pas automatique suivant la locale envoyée par le navigateur . On peut forcer la locale en appliquant le paramètre locale dans l'url (ex. &locale=fr ). A voir si je mets une combobox de sélection , un filtre (API Servlet ) pour appliquer la locale directement ou si je gère ça dans le point d'entrée GWT.

Encoding

Attention à l'encoding des fichiers! Il faut impérativement que les fichiers soient encodés au format UTF-8.

Exemple et code source

Le code est disponible sur le référentiel google à cette adresse : http://code.google.com/p/my-wine-cellar/source/browse/

Vus : 1912
Publié par Littlewing : 368