Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 (primarily 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:

  1. Is there some way of globally setting the default ResourceBundle.Control that I might not be aware of? That would solve everything rather elegantly.

  2. Is there some other way of handling this kind of thing elegantly, perhaps without tampering with the ResourceBundle classes at all?

like image 238
Hakanai Avatar asked Apr 09 '10 06:04

Hakanai


People also ask

How does resource bundle work in Java?

Resource bundles contain locale-specific objects. When your program needs a locale-specific resource, a String for example, your program can load it from the resource bundle that is appropriate for the current user's locale.

What is resource bundle class in Java?

ResourceBundle class is used to store text and objects which are locale sensitive. Generally we use property files to store locale specific text and then represent them using ResourceBundle object. Following are the steps to use locale specific properties file in a java based application.

What is resource bundles?

A resource bundle is a set of properties files with the same base name and a language-specific suffix. For example, if you create file_en. properties and file_de. properties, IntelliJ IDEA will recognize and combine them into a resource bundle.

What is resource bundle locale?

Each resource bundle has a different locale. The locale indicates the specific human language of the strings in the resource bundle. The locale is indicated by the end of the file name, before the . properties extension. Locales consist of one to three identifiers.


1 Answers

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.

like image 105
Matt Brock Avatar answered Oct 12 '22 22:10

Matt Brock