Plusieurs Fichiers de Ressource
Beaucoup de projets requissent plus qu'un seul ResourceBundle (fichier de traductions au format "Java Properties") pour mieux organiser des messages localisés. Cet exemple montrera comment customiser Stripes afin de définir n'importe quel nombre de ResourceBundle. Par ex :
- application.properties (messages générals et libellés des champs de formulaire)
- images.properties (attributs src et alt pour stripes:images)
- erreurs.properties (Validation Stripes et messages générals)
Pour informer Stripes de nos noms de ResourceBundle nous allons créer une classe de configuration customisée pour qu'on puisse ajouter un nouveau paramètre (pour les noms des nos ResourceBundle) puis spécifier un LocalizationBundleFactory customisé qui retournera notre ResourceBundle customisé qui cherchera dans tous nos ResourceBundle spécifiés tel que l'on a spécifié.
Configuration
<filter>
<display-name>Stripes Filter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>
net.sourceforge.stripes.controller.StripesFilter
</filter-class>
<init-param>
<param-name>ActionResolver.Packages</param-name>
<param-value>action</param-value>
</init-param>
<init-param>
<param-name>Configuration.Class</param-name>
<param-value>ext.CustomRuntimeConfiguration</param-value>
</init-param>
<init-param>
<param-name>LocalizationBundleFactory.Class</param-name>
<param-value>ext.CustomLocalizationBundleFactory</param-value>
</init-param>
<init-param>
<param-name>ResourceBundles.BaseNames</param-name>
<param-value>application,images,erreurs</param-value>
</init-param>
</filter>
Les 3 premiers init-param appartiennent à Stripes, et le dernier : ResourceBundles.BaseNames est notre init-param customisé.
Factory de Localisation de ResourceBundle Customisé
package ext;
import java.util.*;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.localization.LocalizationBundleFactory;
import net.sourceforge.stripes.util.StringUtil;
/**
* Permits cycling through multiple named ResourceBundles instead of
* just StripesResources.properties using the init-param
* ResourceBundles.BaseNames (
* {@link #RESOURCE_BUNDLES_BASE_NAMES})
*
* @author DJDaveMark
*/
public class CustomLocalizationBundleFactory
implements LocalizationBundleFactory {
/**
* The Configuration Key which specifies
* multiple resource bundles.
*/
public static final String RESOURCE_BUNDLES_BASE_NAMES =
"ResourceBundles.BaseNames";
private String[] bundles;
public ResourceBundle getFormFieldBundle(Locale locale) {
return new MultipleResourceBundle(locale, getBundleNames());
}
public ResourceBundle getErrorMessageBundle(Locale locale) {
return new MultipleResourceBundle(locale, getBundleNames());
}
public void init(Configuration config) {
String bundleNames = config.getBootstrapPropertyResolver()
.getProperty(RESOURCE_BUNDLES_BASE_NAMES);
bundles = StringUtil.standardSplit(bundleNames);
}
public List<String> getBundleNames() {
return Arrays.asList(bundles);
}
}
Ici nous informons Stripes d'utiliser la même classe-multi-bundle-customisée tout le temps. Pour plus d'info voir le JavaDoc pour LocalizationBundleFactory et DefaultLocalizationBundleFactory. Le BootstrapPropertyResolver permet de récupérer le param-value associé avec notre param-name customisé : ResourceBundles.BaseNames. Puis la méthode StringUtil#standardSplit(String) de Stripes permet de séparer tous le noms de ResourceBundle.
ResourceBundle Customisé
package ext;
import java.util.*;
import net.sourceforge.stripes.controller.StripesFilter;
import net.sourceforge.stripes.localization.DefaultLocalizationBundleFactory;
/**
* With thanks to Freddy's Stripes Book http: *
* @author DJDaveMark
* @author Fred Daoud
*/
public class MultipleResourceBundle extends ResourceBundle {
private Locale locale;
private List<String> bundleNames;
public MultipleResourceBundle(Locale locale, List<String> bundleNames) {
this.locale = locale;
this.bundleNames = bundleNames;
}
@Override
public Enumeration<String> getKeys() {
return null;
}
@Override
protected Object handleGetObject(String key) {
Object result = null;
if (bundleNames != null) {
for (String bundleName : bundleNames) {
if (bundleName != null) {
result = getFromBundle(locale, bundleName, key);
if (result != null) {
break;
}
}
}
}
if (result == null) {
String bundleName = DefaultLocalizationBundleFactory.BUNDLE_NAME;
result = getFromBundle(locale, bundleName, key);
}
return result;
}
/**
* Returns null if the bundle or key is not found. No exceptions thrown.
*/
private String getFromBundle(Locale loc, String bundleName, String key) {
String result = null;
ResourceBundle bundle = ResourceBundle.getBundle(bundleName, loc);
if (bundle != null) {
try {
result = bundle.getString(key);
} catch (MissingResourceException exc) {
}
}
return result;
}
}
Nous pouvons désormais réorganiser nos
ResourceBundle comme ceci :
app.title=Application Multi-ResourceBundle
my.label=Mon libellé
# Chaine de caractères utilises par la balise <stripes:messages />
stripes.messages.header=<ul class="messages">
stripes.messages.beforeMessage=<li>
stripes.messages.afterMessage=</li>
stripes.messages.footer=</ul>
...
image.logo.src=images/logo_fr.jpg
image.logo.alt=Logo de l'Appli M.R.B.
...
# Chaine de caractères utilises par la balise <stripes:errors />
stripes.erreurs.header=<div style="color:#b72222; font-weight: bold">\
Veuillez corriger les erreurs suivantes :</div><ol>
stripes.erreurs.beforeError=<li style="color: #b72222;">
stripes.erreurs.afterError=</li>
stripes.erreurs.footer=</ol>
# Messages d'erreurs utilisés par les
# annotations de validation de Stripes.
validation.required.valueNotPresent={0} est un champ requis
validation.minlength.valueTooShort={0} doit contenir au \
moins {2} caractères
validation.maxlength.valueTooLong={0} ne doit contenir plus \
que {2} caractères
...
JSTL
Tout ce qu'il manque est une façon d'informer les balises JSTL fmt (JSTL taglibs) lesquels de nos ResourceBundle qu'il faut utiliser. Vu que les balises JSTL ne permettent pas vraiment de spécifier plusieurs ResourceBundle nous pouvons au moins configurer notre ResourceBundle principal dans le fichier web.xml :
<context-param>
<param-name>
javax.servlet.jsp.jstl.fmt.localizationContext
</param-name>
<param-value>application</param-value>
</context-param>
puis, au cas où nous avons besoin d'utiliser les autres ResourceBundle hors des balises Stripes, nous pouvons spécifier ce qui suit une fois dans une JSP qui sera inclus dans toutes nos JSP :
<fmt:setBundle var="images" basename="images" scope="application" />
<fmt:setBundle var="erreurs" basename="erreurs" scope="application" />
 | JSTL fmt
Pour plus d'info sur comment les balises JSTL fmt gèrent des ResourceBundle consulter les Références de Balises :
|
Rassemblons le tout
<fmt:message key="app.title" /> <br />
<s:errors />
<s:form beanclass="action.TestAction">
<s:label for="my.label" /> <br />
<!-- <input alt="Logo de l'Appli M.R.B."
name="image.logo"
src="images/logo_fr.jpg" type="image" /> -->
<s:image name="image.logo" /> <br />
Le source de l'image est :
<fmt:message bundle="${images}" key="image.logo.src" />
</s:form>
Bien sûr nous aurions pu tricher et au lieu d'utiliser <fmt:message key="" bundle="${}" /> nous aurions pu très bien utiliser <s:label for="" /> qui se dirige vers notre classe MultipleResourceBundle, mais hélas, cela serait de la triche! ;o)