Plusieurs Fichiers de Ressource

Skip to end of metadata
Go to start of metadata

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

web.xml
<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é.

Référence de Configuration
Pour plus d'info sur la configuration de Stripes jetez un coup d'œil au Référence de Configuration.

Factory de Localisation de ResourceBundle Customisé

ext.CustomLocalizationBundleFactory.class
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é

ext.MultipleResourceBundle.class
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://www.pragprog.com/titles/fdstr
 * 
 * @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) {
            // Look in each configured bundle
            for (String bundleName : bundleNames) {
                if (bundleName != null) {
                    result = getFromBundle(locale, bundleName, key);
                    if (result != null) {
                        break;
                    }
                }
            }
        }
        if (result == null) {
            // Try the application's default bundle
            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 :

application.properties
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>

...
images.properties
image.logo.src=images/logo_fr.jpg
image.logo.alt=Logo de l'Appli M.R.B.

...
erreurs.properties
# 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 :

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

<!-- trouvé dans application.properties -->
<fmt:message key="app.title" /> <br />

<!-- trouvé dans erreurs.properties -->
<s:errors />

<s:form beanclass="action.TestAction">
    <!-- trouvé dans application.properties -->
    <s:label for="my.label" /> <br />

    <!-- trouvé dans images.properties -->
    <!-- .src et .alt sont ajoutés automatiquement pour générer -->
    <!-- <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 />

    <!-- outrepasser le ResourceBundle de defaut de JSTL -->
    <!-- trouvé dans images.properties -->
    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)

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.