ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Internationalization, Part 2
Pages: 1, 2

Formatted Messages

We've seen that in order to internationalize programs, you must place all user-visible messages into resource bundles. This is straightforward when the text to be localized consists of simple labels such as those on buttons and menu items. It is trickier, however, with messages that are composed partially of static text and partially of dynamic values. For example, a compiler might have to display a message such as "Error at line 5 of file "hello.java"", in which the line number and filename are dynamic and locale-independent, while the rest of the message is static and needs to be localized.



The MessageFormat class of the java.text package helps tremendously with these types of messages. To use it, you store only the static parts of a message in the ResourceBundle and include special characters that indicate where the dynamic parts of the message are to be placed. For example, one resource bundle might contain the message: "Error at line {0} of file {1}". And another resource bundle might contain a "translation" that looks like this: "Erreur: {1}: {0}".

To use such a localized message, you create a MessageFormat object from the static part of the message and then call its format( ) method, passing in an array of the values to be substituted. In this case, the array contains an Integer object that specifies the line number and a String object that specifies the filename. The MessageFormat class knows about other Format classes defined in java.text. It creates and uses NumberFormat objects to format numbers and DateFormat objects to format dates and times. In addition, you can design messages that create ChoiceFormat objects to convert from numbers to strings. This is useful when working with enumerated types, such as numbers that correspond to month names, or when you need to use the singular or plural form of a word based on the value of some number.

Example 8-5 demonstrates this kind of MessageFormat usage. It is a convenience class with a single static method for the localized display of exception and error messages. When invoked, the code attempts to load a ResourceBundle with the basename "Errors". If found, it looks up a message resource using the class name of the exception object that was passed. If such a resource is found, it displays the error message. An array of five values is passed to the format( ) method. The localized error message can include any or all of these arguments.

The LocalizedError.display( ) method defined in this example was used in Example 8-2 at the beginning of this chapter. The default Errors.properties resource bundle used in conjunction with this example is shown following the code listing. Error message display for the program is nicely internationalized. Porting the program's error message to a new locale is simply a matter of translating (localizing) the Errors.properties file.

Example 8-5. LocalizedError.java
package je3.i18n;
import java.text.*;
import java.io.*;
import java.util.*;

/**
 * A convenience class that can display a localized exception message
 * depending on the class of the exception.  It uses a MessageFormat,
 * and passes five arguments that the localized message may include:
 *   {0}: the message included in the exception or error.
 *   {1}: the full class name of the exception or error.
 *   {2}: the file the exception occurred in
 *   {3}: a line number in that file.
 *   {4}: the current date and time.
 * Messages are looked up in a ResourceBundle with the basename
 * "Errors", using a the full class name of the exception object as
 * the resource name.  If no resource is found for a given exception
 * class, the superclasses are checked.
 **/
public class LocalizedError {
    public static void display(Throwable error) {
        ResourceBundle bundle;
        // Try to get the resource bundle.
        // If none, print the error in a nonlocalized way.
        try {
            String bundleName = "com.davidflanagan.examples.i18n.Errors";
            bundle = ResourceBundle.getBundle(bundleName);
        }
        catch (MissingResourceException e) {
            error.printStackTrace(System.err);
            return;
        }
        
        // Look up a localized message resource in that bundle, using the
        // classname of the error (or its superclasses) as the resource name.
        // If no resource was found, display the error without localization.
        String message = null;
        Class c = error.getClass( );
        while((message == null) && (c != Object.class)) {
            try { message = bundle.getString(c.getName( )); }
            catch (MissingResourceException e) { c = c.getSuperclass( ); }
        }
        if (message == null) { error.printStackTrace(System.err);  return; }
        
        // Get the filename and linenumber for the exception
        // In Java 1.4, this is easy, but in prior releases, we had to try
        // parsing the output Throwable.printStackTrace( );
        StackTraceElement frame = error.getStackTrace( )[0];  // Java 1.4
        String filename = frame.getFileName( );
        int linenum = frame.getLineNumber( );

        // Set up an array of arguments to use with the message
        String errmsg = error.getMessage( );
        Object[  ] args = {
            ((errmsg!= null)?errmsg:""), error.getClass( ).getName( ),
            filename, new Integer(linenum), new Date( )
        };

        // Finally, display the localized error message, using
        // MessageFormat.format( ) to substitute the arguments into the message.
        System.err.println(MessageFormat.format(message, args));
    }

    /** 
     * This is a simple test program that demonstrates the display( ) method.
     * You can use it to generate and display a FileNotFoundException or an
     * ArrayIndexOutOfBoundsException
     **/
    public static void main(String[  ] args) {
        try { FileReader in = new FileReader(args[0]); }
        catch(Exception e) { LocalizedError.display(e); }
    }
}

The following listing shows the resource bundle properties file used to localize the set of possible error messages that can be thrown by the ConvertEncoding class of Example 8-2:

#
# This is the file Errors.properties
# One property for each class of exceptions that our program might
# report.  Note the use of backslashes to continue long lines onto the
# next.  Also note the use of \n and \t for newlines and tabs
#
java.io.FileNotFoundException: \
Error: File "{0}" not found\n\t\
Error occurred at line {3} of file "{2}"\n\tat {4}

java.io.UnsupportedEncodingException: \
Error: Specified encoding not supported\n\t\
Error occurred at line {3} of file "{2}"\n\tat {4,time} on {4,date}

java.io.CharConversionException:\
Error: Character conversion failure.  Input data is not in specified format.

# A generic resource.  Display a message for any error or exception that
# is not handled by a more specific resource.
java.lang.Throwable:\
Error: {1}: {0}\n\t\
Error occurred at line {3} of file "{2}"\n\t{4,time,long} {4,date,long}

With a resource bundle like this, ConvertEncoding produces error messages like the following:

Error: File "myfile (No such file or directory)" not found
        Error occurred at line 64 of file "FileInputStream.java"
        at 7/9/00 9:28 PM

Or, if the current locale is fr_FR:

Error: File "myfile (Aucun fichier ou repertoire de ce type)" not found
        Error occurred at line 64 of file "FileInputStream.java"
        at 09/07/00 21:28

Exercises

  • Exercise 8-1.Several internationalization-related classes, such as NumberFormat and DateFormat, have static methods named getAvailableLocales( ) that return an array of the Locale objects they support. You can look up the name of the country of a given Locale object with the getDisplayCountry( ) method. Note that this method has two variants. One takes no arguments and displays the country name as appropriate in the default locale. The other version of getDisplayCountry( ) expects a Locale argument and displays the country name in the language of the specified locale.

    Write a program that displays the country names for all locales returned by NumberFormat.getAvailableLocales( ). Using the static locale constants defined by the Locale class, display each country name in English, French, German, and Italian.


View catalog information for Java Examples in a Nutshell, 3rd Edition

Return to ONJava.com.