Internationalization

The Java VM lets us write code that executes in the same way on any Java platform. But in a global marketplace, that is only half the battle. A big question remains: will the application content and data be understandable to end users worldwide? Must users know English to use your application? The answer is that Java provides thorough support for localizing the text of your application for most modern languages and dialects. In this section, we’ll talk about the concepts of internationalization (often abbreviated “I18N”) and the classes that support them.

The java.util.Locale Class

Internationalization programming revolves around the Locale class. The class itself is very simple; it encapsulates a country code, a language code, and a rarely used variant code. Commonly used languages and countries are defined as constants in the Locale class. (Maybe it’s ironic that these names are all in English.) You can retrieve the codes or readable names, as follows:

    Locale l = Locale.ITALIAN;
    System.out.println(l.getCountry());            // IT
    System.out.println(l.getDisplayCountry());     // Italy
    System.out.println(l.getLanguage());           // it
    System.out.println(l.getDisplayLanguage());    // Italian

The country codes comply with ISO 3166. You will find a complete list of country codes at the RIPE Network Coordination Centre. The language codes comply with ISO 639. A complete list of language codes is online at the US government website. There is no official set of variant codes; they are designated as vendor-specific or platform-specific. You can get an array of all supported Locales with the static getAvailableLocales() method (which you might use to let your users choose). Or you can retrieve the default Locale for the location where your code is running with the static Locale.getDefault() method and let the system decide for you.

Many classes throughout the Java API use a Locale to decide how to represent text. We ran into one earlier when talking about sorting text with the Collator class. We’ll see more later in this chapter used to format numbers and currency strings, and again in the next chapter with the DateFormat class, which uses Locales to determine how to format and parse dates and times. Without getting into the details yet, here is a quick example:

    System.out.printf( Locale.ITALIAN, "%f\n", 3.14 ); // "3,14"

The preceding statement uses the Italian Locale to indicate that the decimal number 3.14 should be formatted as it would in Italian, using a comma instead of a decimal point. We’ll talk more about formatting text later in this chapter.

Resource Bundles

Before we move on to the details of formatting messages and values, we might take a step back and ask a bigger question: what about the messages themselves? How can we write and manage applications that are truly multilingual in their user interfaces and in all the messages they display to the user? We can discover our locale, but how do we manage all of the application text in our code? The ResourceBundle class offers a clean, flexible solution for factoring out the text and resources of your application into language-specific classes or text files.

A ResourceBundle is a collection of objects that your application can access by name. It acts much like the Hashtable or Map collections we’ll discuss in Chapter 11, looking up objects based on Strings that serve as keys. A ResourceBundle of a given name may be defined for many different Locales. To get a particular ResourceBundle, call the factory method ResourceBundle.getBundle(), which accepts the name of the ResourceBundle and a Locale. The following example gets the ResourceBundle named “Message” for two Locales; from each bundle, it retrieves the message whose key is “HelloMessage” and prints the message:

    import java.util.*;

    public class Hello {
      public static void main(String[] args) {
        ResourceBundle bun;
        bun = ResourceBundle.getBundle("Message", Locale.ITALY);
        System.out.println(bun.getString("HelloMessage"));
        bun = ResourceBundle.getBundle("Message", Locale.US);
        System.out.println(bun.getString("HelloMessage"));
      }
    }

The getBundle() method throws the runtime exception MissingResourceException if an appropriate ResourceBundle cannot be located.

You can provide ResourceBundles in two ways: either as compiled Java classes (hard-coded Java) or as simple property files. Resource bundles implemented as classes are either subclasses of ListResourceBundle or direct implementations of ResourceBundle. Resource bundles backed by a property file are represented at runtime by a PropertyResourceBundle object. ResourceBundle.getBundle() returns either a matching class or an instance of PropertyResourceBundle corresponding to a matching property file. The algorithm used by getBundle() is based on appending the country and language codes of the requested Locale to the name of the resource. Specifically, it searches for resources in this order:

    name_language_country_variant
    name_language_country
    name_language
    name
    name_default-language_default-country_default-variant
    name_default-language_default-country
    name_default-language

In this example, when we try to get the ResourceBundle named Message, specific to Locale.ITALY, it searches for the following names (no variant codes are in the Locales we are using):

    Message_it_IT
    Message_it
    Message
    Message_en_US
    Message_en

Let’s define the Message_it_IT ResourceBundle as a hardcoded class, a subclass of ListResourceBundle:

    import java.util.*;

    public class Message_it_IT extends ListResourceBundle {
      public Object[][] getContents() {
        return contents;
      }

      static final Object[][] contents = {
        {"HelloMessage", "Buon giorno, world!"},
        {"OtherMessage", "Ciao."},
      };
    }

ListResourceBundle makes it easy to define a ResourceBundle class; all we have to do is override the getContents() method. This method simply returns a two-dimensional array containing the names and values of its resources. In this example, contents[1][0] is the second key (OtherMessage), and contents [1][1] is the corresponding message (Ciao.).

Let’s define a ResourceBundle for Locale.US. This time, we’ll take the easy way and make a property file. Save the following data in a file called Message_en_US.properties:

    HelloMessage=Hello, world!
    OtherMessage=Bye.

So what happens if somebody runs your program in Locale.FRANCE and no ResourceBundle is defined for that Locale? To avoid a runtime MissingResourceException, it’s a good idea to define a default ResourceBundle. In our example, you can change the name of the property file to Message.properties. That way, if a language- or country-specific ResourceBundle cannot be found, your application can still run (by falling back to this English representation).

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.