Multiple Resource Bundles
Multiple Resource Bundles
A lot of projects require more than one resource bundle to keep the localized text organized. This example will show you how to customize Stripes to use any number of resource bundles. For example :
application.properties
(general messages and form field labels)images.properties
(src and alt attributes for s:images)errors.properties
(Stripes validation and general error messages)
To make Stripes aware of our bundle names we will have to specify a custom parameter (for our bundle names) and specify a custom LocalizationBundleFactory
which will return a custom ResourceBundle
which will search our specified bundles.
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>Extension.Packages</param-name> <param-value>ext</param-value> </init-param> <init-param> <param-name>ResourceBundles.BaseNames</param-name> <param-value>application,images,errors</param-value> </init-param> </filter>
The first 2 init-param
tags belong to Stripes, and the last one ResourceBundles.BaseNames
is our custom init-param
. The ActionResolver.Packages
parameter simply tells Stripes where to find our ActionBeans and the Extension.Packages
parameter tells Stripes where to find our custom extension classes.
Configuration Reference
For more information on the configuration of Stripes take a look at the Configuration Reference.
Custom Localization Bundle Factory
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); } }
Here we tell Stripes to use the same custom-multi-bundle-class all the time. For more info see the JavaDoc for LocalizationBundleFactory and DefaultLocalizationBundleFactory The BootstrapPropertyResolver
is used to get the param-value
associated with our custom param-name
ResourceBundles.BaseNames
. Then Stripes' StringUtil#standardSplit(String)
method is used to break up the separate bundle names.
Custom Resource Bundle
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; } }
We can now reorganise our resource bundles to contain something like the following :
app.title=Multiple Resource Bundle Application my.label=My Label # Resource strings used by the stripes:messages tag stripes.messages.header=<ul class="messages"> stripes.messages.beforeMessage=<li> stripes.messages.afterMessage=</li> stripes.messages.footer=</ul> ...
image.logo.src=images/logo.jpg image.logo.alt=M.R.B. App Logo ...
# Resource strings used by the <stripes:errors> tag stripes.errors.header=<div style="color:#b72222; font-weight: bold">\ Please fix the following errors:</div><ol> stripes.errors.beforeError=<li style="color: #b72222;"> stripes.errors.afterError=</li> stripes.errors.footer=</ol> # Validation error messages produced by # Stripes' annotation based validations. validation.required.valueNotPresent={0} is a required field validation.minlength.valueTooShort={0} must be at least {2}\ characters long validation.maxlength.valueTooLong={0} must be no more than {2}\ characters long ...
JSTL
The only thing that's missing is to somehow tell the JSTL fmt tags which of our bundles to use. Since JSTL tags don't really accept multiple bundles we can configure our main bundle in the web.xml
file :
<context-param> <param-name> javax.servlet.jsp.jstl.fmt.localizationContext </param-name> <param-value>application</param-value> </context-param>
then, just in case we need to use the other bundles outwith the Stripes tags, we can specify the following once in a JSP which we can include in all our other JSPs :
<fmt:setBundle var="images" basename="images" scope="application" /> <fmt:setBundle var="errors" basename="errors" scope="application" />
JSTL fmt
For more information on the JSTL fmt bundles consult the Tag Reference :
Putting It All Together
<!-- found in application.properties --> <fmt:message key="app.title" /> <br /> <!-- found in errors.properties --> <s:errors /> <s:form beanclass="action.TestAction"> <!-- found in application.properties --> <s:label for="my.label" /> <br /> <!-- found in images.properties --> <!-- .src and .alt are added automatically to generate --> <!-- <input alt="M.R.B. Logo" name="image.logo" --> <!-- src="images/logo.jpg" type="image" /> --> <s:image name="image.logo" /> <br /> <!-- override the default JSTL bundle --> <!-- found in images.properties --> The source of this image is : <fmt:message bundle="${images}" key="image.logo.src" /> </s:form>
Of course we could just cheat and instead of using <fmt:message key="" bundle="${}" />
we could use <s:label for="" />
which already uses our MultipleResourceBundle
class, but alas, that would be cheating! ;o)