The Source for Java Technology Collaboration
User: Password:



John O'Conner

John O'Conner's Blog

I18n How-to: Just get started!

Posted by joconner on April 19, 2004 at 11:31 PM | Comments (6)

Developers and project managers make lots of excuses for not internationalizing an application. But it's easier to get started than you might imagine. Don't worry too much about all your difficult questions...you'll never begin. Just get started!

Although there are several steps to creating a fully internationalized application, you can start by separating localizable text from your core business logic. You should place that text in a separate file, either a PropertyResourceBundle or a ListResourceBundle. Since a PropertyResourceBundle is so much more simple, I'll start with that. Let's pretend you're creating a simple application with a single button and a text field. When the user presses the button, the application will display a simple greeting. Your application might start like this:

package com.joconner.demo;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Greeting extends JFrame {
    JButton btnGreeting = new JButton();
    JTextField tfGreeting = new JTextField();
    ResourceBundle res =  GridBagLayout layout = new GridBagLayout();

    public Greeting() {
        try {
            btnGreeting.setText("Show Greeting");
            this.setTitle("I18n Demo");
            btnGreeting.addActionListener(new ActionListener() {
    
                public void actionPerformed(ActionEvent event) {
                    tfGreeting.setText("Hello, world!");
                }
    
            });
            this.getContentPane().setLayout(layout);
            this.getContentPane().add(btnGreeting,    new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0
                ,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
            this.getContentPane().add(tfGreeting,     new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0
                ,GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        }
        catch(Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Greeting app = new Greeting();
        app.pack();
        app.show();
    }
}

To get started with internationalization, you first need to pull all the localizable text from the source code. For this app, it's simple; you just have 3 strings:

  • Show Greeting!
  • Hello, world!
  • I18n Demo

Put these strings in a PropertyResourceBundle. A PropertyResourceBundle is a simple text file with key/value pairs. Let's name our bundle "GreetingResources.properties" and we'll place it in the same package as our application's .java file. Proving 3 keys for each string, the property bundle will look like this:

BTN_TEXT=Show Greeting!
GREETING=Hello, world!
TITLE=I18n Demo

Now we have to touch up the original source code to use our new ResourceBundle. The modified code is below:

package com.joconner.demo;

import javax.swing.*;
import java.awt.*;
import java.util.ResourceBundle;
import java.awt.event.*;

public class Greeting extends JFrame {
    JButton btnGreeting = new JButton();
    JTextField tfGreeting = new JTextField();
    ResourceBundle res = ResourceBundle.getBundle("com.joconner.demo.GreetingResources");
    GridBagLayout layout = new GridBagLayout();

    public Greeting() {
        try {
            String btnGreetingText = res.getString("BTN_TEXT");
            btnGreeting.setText(btnGreetingText);
            String title = res.getString("TITLE");
            this.setTitle(title);
            btnGreeting.addActionListener(new ActionListener() {
    
                public void actionPerformed(ActionEvent event) {
                    String tfGreetingText = res.getString("GREETING");
                    tfGreeting.setText(tfGreetingText);
                }
    
            });
            this.getContentPane().setLayout(layout);
            this.getContentPane().add(btnGreeting,    new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0
                ,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
            this.getContentPane().add(tfGreeting,     new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0
                ,GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        }
        catch(Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Greeting app = new Greeting();
        app.pack();
        app.show();
    }
}

You'll notice that we've done 3 new things:

  1. load a PropertyResourceBundle containing key/value pairs for each piece of localizable text
  2. retrieve each piece of text by its key using the getString(String key) method of the bundle
  3. use the retrieved String to set the text of the appropriate GUI component.

Now believe it or not, you've started to internationalize your application with these easy steps! You can now localize the PropertyResourceBundle file into multiple languages, and the Java platform will find the localized bundle for you. In my next blog tip, I'll discuss how to name your bundles for multiple localizations of the same resources.

Want more information about resource bundles? Check this out:
Java Internationalization: Localization with ResourceBundles


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Suggested topics...
    In many ways a desktop app is the easiest case for I18N (and a good pick for a first article). But I'd like to hear more about the issues you hit down the line:
    - multilingual websites; in this case you need to match to the remote user's locale and not the system default. Since the default locale is per-VM not per-thread, and there's no standard API for passing locale to components, components nearly always use the *wrong* locale. (In our case, we found that most of our 3rd party components at least attempted to deal with a per-webapp locale, so we have 'clone' webapps for each supported locale)
    - exceptions. exceptions propogate up from components or the VM itself, and are most likely in the wrong locale (see above). My feeling is that unless the VM locale is the users locale, they shouldn't see any raw messages from exceptions.
    - bugs & workarounds... there are numerous I18N related bugs in popular products, which it'd be nice to be forewarned about; eg Tomcat 4 used the request encoding to decode URLs - incorrect as URLs in HTTP are always UTF-8; this is a problem if you have (say) Arabic portlets, since portlets use parameters in the URL and the POST body, one or the other will be decoded incorrectly. The workaround was to write a servlet filter to decode the parameters yourself - ouch. I think this one got fixed in TC5.

    In short, the difficulties I find with I18N are less with the java apis, and more with using 3rd party software... anyone got any best practices to share?

    Posted by: ba22a on April 20, 2004 at 03:12 AM

  • Eclipse Support for i18n
    Eclipse has a very helpful function for externalizing
    Strings. It's called Source/Externalize Strings;
    this looks through the current source file, gets the
    Strings and offers to put them into a properties file;
    Not only that: it also replaces the Strings in the file
    with calls that load the Strings from the property file;
    This is done inside a very well designed dialog where
    you can choose which Strings to treat that way and
    which not. All in all: simple, fast and flexible.
    If you have some source code that you want to i18n-alize, this feature is really a great aid;

    Posted by: murphee on April 20, 2004 at 05:46 AM

  • Suggested topics...
    ServletRequest has a getLocale() method which should return the default Locale to use for a user which is based on the relevant request parameter.
    I GUESS it will default to the default Locale for the server if none is supplied by the client.

    Does Tomcat not handle this correctly, I'd find that weird seeing as Tomcat is the official reference implementation for the Servlet standard? (I haven't tried, our product is at the moment meant to run only on a single language as we have no foreign customers and no marketting planned in other countries therefore no economic incentive to invest in internationalisation).

    Posted by: jwenting on April 20, 2004 at 06:12 AM

  • Suggested topics...
    Tomcat handles "Accept-Language" correctly - the tomcat bug I mentioned is to do with the 'charset' part of "Content-Type" in requests.

    Tomcat is only as good as the TCK tests; the bug I mentioned arises from something that isn't explicit in the servlet spec but shows up when you read the URL + HTTP RFCs, its not surprising some things get missed.

    Going back to your point, if you set the default locale using the locale from a request, it sets it for every concurrent user on the system - not a good idea, and not a problem isolated to tomcat, or even to servlets. To be totally safe, you can use a filter to redirect to a separate VM instance that will accept the requested locale.

    Most components you use to service that request (just pick anything at random that wasn't built specifically for the servlet API) will not expect the user locale to differ from the VM locale. You can see the issue if you go to Locale in your javadoc, click on 'Uses' and see how many packages /don't/ use it; eg java.sql, javax.xml. Which means that their exceptions always report errors in the VM locale, not the users.

    For example, if there is an schema validation error in an xml document uploaded by the user (or otherwise created on their behalf); something they did fails a database constraint; and so on. You simply don't get access to the messages in the user's locale, and since the messages you get back may just be plain 'SQLException' or 'SAXParseException' objects, no driver/parser independent way of figuring out what the message should be, either.

    The answer appears to me to be that the only error messages the user sees should be ones generated by validation in the UI layer, where you hopefully have access to the users locale.

    Posted by: ba22a on April 20, 2004 at 08:25 AM

  • J2EE i18n topics
    Your suggestions for enterprise i18n topics has been noted. I'll cover some of those topics in the near future.

    Posted by: joconner on April 20, 2004 at 04:50 PM

  • Next step
    The solution given is quite limited; I suggest to look at a full i18n framework solution that makes life amazingly easy.

    http://uic.sf.net/tutorials/i18n/

    Posted by: zander on April 21, 2004 at 12:54 PM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds