Java 6's ResourceBundle, Properties, and ResourceBundle.Control objects create an extensible framework for implementing your own multilingual text lookups. As I explained in the Create Database-driven Resource Bundles with Java and MySQL article, storing your display text in a relational database is a viable alternative to having a different class for every locale, country and variant you want to provide. With a database, adding a new locale can be as easy as adding some rows to a table. Moreover, it provides the option of dynamically adding a new locale to an existing application.
In the same article, I demonstrated how to communicate between Java and a MySQL database using the MySQL Connector/J driver, create a test database and table, review some specialized string utilities and override the
newBundle() method to fetch locale-aware strings from a database. In this follow up and conclusion, I illustrate what is happening behind the scenes by writing the DBResourceBundle class and creating a test program that will output text for different locales. Last but not least, I'll discuss some ways to fine tune the loading of your ResourceBundles.
How New Bundles Are Instantiated
In the ResourceBundle.Control class, there is a method called
getFormats() that returns a list of strings containing formats for loading resource bundles for the given
ResourceBundle.getBundle() factory method tries to load resource bundles with formats in the order specified by the list. There are currently only two supported formats:
java.class for class-based resource bundles and
java.properties for properties-based ones.
Strings starting with
java are reserved for future extensions and must not be used by application-defined formats. Therefore, if you want to perform a lookup on a relational database, you need to override this behavior in the
ResourceBundle.Control class. The method in which to do this is
The DBResourceBundle Class
In the Create Database-driven Resource Bundles with Java and MySQL article, I wrote my own implementation of the
newBundle() method to fetch multilingual display text from a database table. The last line in the method returns a new DBResourceBundle:
The main difference between my DBResourceBundle class and its parent, the ResourceBundle, is how it accomplishes the loading of the properties. The ResourceBundle has only the default, no-argument constructor, whose sole purpose is for invocation by subclass constructors, which is usually implicit. Typically, you would add at least one additional constructor to pass in data. The constructor parameter type(s) will depend on the mechanism that fetches the properties. For instance, in the Using Java 6 to Access Translatable Text in XML Files article, I provided a constructor that accepts an InputStream. I was thus able to take advantage of the
In the DBResourceBundleControl class, I populated the Properties object from the resultset of a query, so all I have to do here is pass it to the DBResourceBundle via the constructor. The required implementations of the
getKeys() methods are unchanged from those included in the XMLResourceBundle class from the previous article:
Taking the DBResourceBundle for a Test Run
It's a good practice to try out your bundles on a few different locales to confirm that your implementation behaves as you expect.
Recall from the Isolating Locale-specific Text in International Java Applications Using ResourceBundles article that Java retrieves bundles based on a pre-determined strategy: It uses the base name, the specified locale, and the default locale to generate a sequence of candidate bundle names. If the specified locale's language, country, and variant are all empty strings, then the base name is the only candidate bundle name. Otherwise, the following sequence is generated from the attribute values of the specified locale (
variant1) and of the default locale (
- baseName + "_" + language1 + "_" + country1 + "_" + variant1
- baseName + "_" + language1 + "_" + country1
- baseName + "_" + language1
- baseName + "_" + language2 + "_" + country2 + "_" + variant2
- baseName + "_" + language2 + "_" + country2
- baseName + "_" + language2
As the above series demonstrates, the candidate bundle names are iterated over by successively removing variant, country, and language from the bundle name. With each of the candidate bundle names, the ResourceBundle's
getBundle() factory method attempts to instantiate a resource bundle as described above. Whenever it succeeds, it calls the previously instantiated resource bundle's
setParent() method with the new resource bundle, unless the previously instantiated resource bundle already has a non-null parent.
Here's a class that tests our new classes against the
println() calls are made at various points to show what is happening in the code: