How do I avoid repetition in Java ResourceBundle strings?
We had a lot of strings which contained the same sub-string, from sentences about checking the log or how to contact support, to branding-like strings containing the company or product name. The repetition was causing a few issues for ourselves (prima开发者_Python百科rily typos or copy/paste errors) but it also causes issues in that it increases the amount of text our translator has to translate.
The solution I came up with went something like this:
public class ExpandingResourceBundleControl extends ResourceBundle.Control {
public static final ResourceBundle.Control EXPANDING =
new ExpandingResourceBundleControl();
private ExpandingResourceBundleControl() { }
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format,
ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
ResourceBundle inner = super.newBundle(baseName, locale, format, loader, reload);
return inner == null ? null : new ExpandingResourceBundle(inner, loader);
}
}
ExpandingResourceBundle
delegates to the real resource bundle but performs conversion of {{this.kind.of.thing}} to look up the key in the resources.
Every time you want to get one of these, you have to go:
ResourceBundle.getBundle("com/acme/app/Bundle", EXPANDING);
And this works fine -- for a while.
What eventually happens is that some new code (in our case autogenerated code which was spat out of Matisse) looks up the same resource bundle without specifying the custom control. This appears to be non-reproducible if you write a simple unit test which calls it with and then without, but it occurs when the application is run for real. Somehow the cache inside ResourceBundle
ejects the good value and replaces it with the broken one. I am yet to figure out why and Sun's jar files were compiled without debug info so debugging it is a chore.
My questions:
Is there some way of globally setting the default ResourceBundle.Control that I might not be aware of? That would solve everything rather elegantly.
Is there some other way of handling this kind of thing elegantly, perhaps without tampering with the ResourceBundle classes at all?
I think this is a fundamental flaw in the way ResourceBundles are designed to function: keys that reference other keys automatically violate the DRY (don't repeat yourself) principle. The way I got around this was similar to your method: create a ReflectiveResourceBundle class that allows you to specify Resource keys in the messages using EL notation.
THE WRONG WAY:
my.name.first=Bob
my.name.last=Smith
my.name.full=Bob Smith
THE RIGHT WAY:
my.name.first=Bob
my.name.last=Smith
my.name.full=${my.name.first} ${my.name.last}
I've uploaded the code to GitHub so you or anyone else can download it. Additionally, I've added some sample code for anyone using the Stripes Framework (http://www.stripesframework.org/) to get you quickly up-and-running.
The trick to getting this to work with standard JSTL fmt taglibs was to set up an interceptor that replaced the HttpServletRequest's resource with our own. The code looks something like this:
ResourceBundle bundle = MyStaticResourceHoldingTheBundle.getBundle();
Config.set(request, Config.FMT_LOCALIZATION_CONTEXT, new LocalizationContext(bundle, locale));
Take a look at the stripes.interceptor package in the link above for more details.
If the string repetitions are localized in the sense that you know a certain string will be repeated but only within the same project such that sharing resource bundles is not a design nightmare, then you might consider breaking the strings down into multiple key-value parts. Separate the parts that repeat from those that do not and reuse the repeated parts. For example, lets say you have the following two strings you need to display:
- "The Red-capped Robin is a small passerine bird native to Australia. "
- "The Red-capped Robin is found in dryer regions across much of the continent."
The resource bundle could be as follows:
robin.name=The Red-capped Robin
robin.native=is a small passerine bird native to Australia.
robin.region=is found in dryer regions across much of the continent.
and then combine the required parts where needed bundle.getString("robin.name")+bundle.getString(robin.native).
One thing you need to be careful about though is that the grammar rules like subject predicate order etc. might not be the same in all languages. So you would need to be a little careful when splitting sentences.
精彩评论