Resource Bundles

One of the first steps in internationalizing an application is th externalizing of text.  This simplifies the translation process and makes localization far easier. 
The standard way of handling resources in Java has been by the use of properties files.  A properties file is simply a text file containing key value pairs.  It is named with the syntax, name_language_country_variant.properties.  So a bundle for a Japanese bundle called errors would be titled, errors_ja.properties.
An example file might look like:


firstName=First Name
lastName=Last Name

 
 
There are a few limitations of properties files.  The biggest problem is that they are not Unicode encoded.  This means that many characters, like Japanese or Chinese double-byte, will need to be escaped in the properties files.  This makes them un-readable to the common user.  The native2ascii tool packaged with Java can convert your unicode to ascii for you.
 
To load a resource bundle and retrieve a key from it:


ResourceBundle rb = PropertyResourceBundle.getBundle("com.cookbook.bundles.foo");
System.out.println(rb.getString("firstName"));

 
 
To load a resource bundle for a particular locale if available:


Locale en = Locale.ENGLISH;
ResourceBundle rb = PropertyResourceBundle.getBundle("com.cookbook.bundles.foo", en);
System.out.println(rb.getString("firstName"));

 
 

Handle plural text in a localizable manner.

Problem:

You want to use the correct forms for plurals in a sentence including dynamic values.

Solution:

Plurals can be very difficult to deal with.  It is not always as easy as adding an s to the end of a word.  Consider the sentence, "Enter your child(rens)'s names(s)."  That looks pretty ugly, and not human at all.

ChoiceFormat allows us to spcify blocks of text that appear depending upon a numeric value passed in.  For example:

//Create a collection of options separated by pipes, and preceded by numeric markers.
String msg = "0#no dogs| 1#a dog| 2#a couple dogs| 3#several dogs| 3<many dogs";
ChoiceFormat fmt = new ChoiceFormat(msg);
//Pass a choice into the format and that option will be selected.
System.out.println("I have " + fmt.format(2) + ".");

Now obviously our hard coded sentence in the previous example is not a good thing for localization.  We can combine a MessageFormat and ChoiceFormat to solve that issue and give us an easily localizable solution.

//Create an array of arguments.  In this case a ChoiceFormat array
Format[] choices = {new ChoiceFormat("0#no dogs| 1#a dog| 2#a couple dogs| 3#several dogs| 3<many dogs")};
//Create a message format with the type of choice
String msg = "I have {0,choice}.";
MessageFormat mft = new MessageFormat(msg);
//call the setFormats method on the MessageFormat and pass the ChoiceFormat array in
mft.setFormats(choices);
for(int x =0; x < 5; x++){
       Object[] args = {x};
        System.out.println(mft.format(args));
}

How to combine dynamic and static data

Problem:

You want to combine dynamic values with your static resource files.

Solution:

There are many situations where part of a sentence comes from a dynamic source, or is provided by the user.  Obviously translatng a sentence for every possible scenario is not an option.  Thankfully Java provides a solution for this situation.  It is called MessageFormat.

A message format is a way of replacing variables in a sentence in a locale sensitive manner.

A simple message format:

String msg = "Hello, {0}. My name is {1}.";
Object[] args = {"John", "Tom"};
System.out.println(MessageFormat.format(msg, args));

You can also specify formatting information with the variables, and they will be formatted in a locale appropriate manner.

//In the message we specify a format style and type.
//Type can be specified as number,time, date, or choice.
//Style can be short, medium, long, or full for date and time types.  It can be
//integer, currency, or percent for number types.
String msg = "Today is {0,date,full}. I have {1,number,currency} in my wallet";
//The arguments to replace the values are specifed as elements in an object array
Object[] args = {new Date(), 1876};
//Create a MessageFormat instance
MessageFormat mf = new MessageFormat(msg,new Locale("fr","FR"));
//call format passing in the argument array
System.out.println(mf.format(args));

Load properties from outside the classpath

 

 Problem:

You want to locate your resource bundles outside your application classpath.

Solution:

Create an InputStream and use it to create the PropertyResourceBundle directly using the constructor.

To loop through a collection of locales and look for a matching properties file outside the classpath then extract a resource from it:

try {
   //Create an arra of locale ids.  This should be created programmaticaly
    String[] localeChain = {"fr_CA","fr","en_CA","en"};
    //Get the root directory you will locate the resources in
    File directory = new File("C:\\temp\\");
    //Get a List of files contained in the director.  We could use a FileFilter to
    //reduce this list
    List<String> files = Arrays.asList(directory.list());
    //The InputStream will be used to load the properties file.  Here we initialize as null;
    InputStream is = null;
    //Loop through the locales in the chain
    for(int x = 0; x < localeChain.length; x++) {
        //Check if a properties file exists for that locale.
        //Naturally in a real usage the file name would be specified programmaticaly.
        //If a match is found we create the InputStream and break out of the loop
        String fileName = "foo_"+localeChain[x]+".prop";
        if(files.contains(fileName)){
            is = new FileInputStream(new File(directory,fileName));
            break;
        }
    }
    //If the InputStream is still null look for a root file
    if(is == null && files.contains("foo.prop") ) {
        is = new FileInputStream(new File(directory,"foo.prop"));
    }
    //If a file has been found create the ResourceBundle and output some text
    if(is != null) {
        PropertyResourceBundle rb2 = new PropertyResourceBundle(is);
        System.out.println(rb2.getString("foo"));
    }
}
catch (MalformedURLException e) {
    e.printStackTrace();
}
catch (IOException e) {
    e.printStackTrace();
}

 

Spell out the numeric value in a concatenated string

 Problem:

You want to spell out a dynamic numeric value being combined with a static resource.

Solution:

Java MessageFormat provides a number of options for formatting the values passed in.  ICU4J provides even more options.

One of the more interesting formatting options is the spellout format.  Spell out will actually write out a numeric value, such as thirty-five vs. 35.

ICU4J's MessageFormat is used similar to the code Java class.

To format a pattern using a spellout pattern:

//Create an Array of Objects
Object[] tmp = {345};
//Create our pattern.  We specify that we will replace using the first element in the array
//and use a spellout pattern
String msg = "I have {0,spellout} coffee cups in my car.";
//Retrieve the MessageFormat
MessageFormat mf = new MessageFormat(msg,ULocale.ENGLISH);
//Call format passing in the array
System.out.println(mf.format(tmp));
//Now lets do it in Japanese
String jMsg = "私の車の中に{0,spellout}個のコーヒーカップがあります。";
MessageFormat mf2 = new MessageFormat(jMsg,ULocale.JAPANESE);
System.out.println(mf2.format(tmp));


The output is:

I have three hundred and forty-five coffee cups in my car.

私の車の中に三百四十五個のコーヒーカップがあります。