Internationalisation i18N avec GWT
Me 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 :
- l'externalisation des messages/constantes utilisés dans le code
- 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éesMessages
: 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éeDateTimeFormat
,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/