Table of ContentsChapter 16.            The LocalizationConclusion

Localizing

Different countries use distinct formatting for currency, date, time, and of course language. In the United States, for example, dates are formatted as MM/DD/YY, rather than the more common DD/MM/YY used in much of the world. Currency is another good examplethere are plenty of different symbols in use.

Probably the most important localization, however, is the language used to display the game content, such as the menus and the story. Most J2ME distributors will require support for translation of your game into different languages. While most don't need you to actually translate the text, you need to provide a mechanism that lets the distributor do it for you without recompiling the game.

Take a look at how to add support for this localization to Star Assault.

NOTE

Note

All MIDs have a special property called microedition.locale that indicates the current locale. You can access it using the getProperty method. For example:

System.getProperty("microedition.locale");

Typically you don't need to use this method because most of the time you build and distribute versions of the game for a single locale. If for some reason you want to make a game that dynamically adapts, feel free to use this property to select the correct language.

The Locale Class

To enable external translation, you need to move the language strings out of the game code and into an external file. When the game starts, you load all the strings into an internal map. Then, when you want to use a string, you simply grab it from the map using a key.

NOTE

Tip

When you're designing your game, keep in mind that translated text might be considerably longer than the English equivalent. Leave plenty of space.

To get started, create a text file map of all the strings you need for the game. Here's an excerpt from the locale.txt for the English version of Star Assault.

NOTE

Note

The following file is just an example of a specific language. Later in the section "The Build Step" you'll see how to use multiple files for different languages.

Application.Copyright=(C) 2003
Application.Author=Martin J. Wells
Application.Press key=Press a key

Main Menu.Title=Main Menu
Main Menu.New Game=New Game
Main Menu.Resume Game=Resume Game
Main Menu.Settings=Settings
Main Menu.Change Keys=Change Keys
Main Menu.Help=Help
Main Menu.About=About
...

The full file is quite a bit longer because it has to cater to every string you use in the game.

Okay, now you have your file ready, so let's take a look at your new Locale class. The first thing you need to do is load the contents of the text file. Take a look at the code:

import java.io.InputStream;
import java.io.IOException;
import java.util.Hashtable;

public class Locale
{
    private final static Locale instance = new Locale();
    private static Hashtable map;

    public Locale()
    {
        //#ifdef debug
        System.out.println("Loading resources from locale.txt");
        //#endif

        map = new Hashtable();

        // load up the resource strings
        InputStream is = null;

        try
        {
            is = this.getClass().getResourceAsStream("locale.txt");

            boolean done = false;
            byte buffer[] = new byte[1];

            StringBuffer line = new StringBuffer();

            // read a line and add the entries
            while (!done)
            {
                int bytesRead = is.read(buffer);
                if (bytesRead != -1)
                {
                if (buffer[0] == '\n')
                {
                    String s = line.toString().trim();
                // we ignore lines starting with #,   or if they don't have a
                // = in them, or if the length is smaller than the min of
                // 3 chars (ie. 'a=b')

                if (!s.startsWith("#") && s.length() > 2 &&
                    s.indexOf('=') != -1)
                {
                    String key = s.substring(0,
                        s.indexOf('=')).trim().toLowerCase();
                    String value = s.substring(s.indexOf('=')+1).trim();

                    map.put(key, value);

                    //#ifdef debug
                    System.out.println("Loading setting: " + key + "='" +
                                        value + "'");
                    //#endif
                }
                line.setLength(0);
            }
            else
                line.append((char)buffer[0]);
        } else
            done = true;

    }

}
catch(IOException io)
{
    System.out.println("Error loading resources: " + io);
}

finally
{
    try
    {
        is.close();
    }

    catch(IOException io) { }
}
    }
}

This code uses the (limited) I/O classes in MIDP to load the text file and store the values

in a hash table. Next you add a method to the Locale class to gain access to these values.

public static String getString(String key)
{
    String s = (String)map.get(key.toLowerCase());
    if (s != null) return s;
    //#ifdef debug
    System.out.println("Attempt to get an unknown resource string: " + key);
    //#endif
    return key;
}

Adapting Star Assault

Now that you have the locale system ready, you need to modify the code in Star Assault to use it. For example, instead of displaying the New Game option in your menu using the code:

items.addElement("New Game");

you grab the string corresponding to your locale using:

items.addElement(Locale.getString("Main Menu.New Game"));

The details displayed in the splash screen are another good example. Here's the revised version:

osg.drawString(Locale.getString("Application.Copyright"), getWidth() / 2,
                getHeight() - fontHeight * 3,
                Graphics.HCENTER | Graphics.TOP);
osg.drawString(Locale.getString("Application.Author"), getWidth() / 2,
                getHeight() - fontHeight * 2,
                Graphics.HCENTER | Graphics.TOP);

Adapting the rest of the game is basically just the same process. Wherever there is a string, you move it to the locale file and then add a call to getString using the key.

The Build Step

The next step you need to accomplish is the ability to support the building of different versions based on locale using your Ant script. The first thing you need to do is make a home for the different versions of locale.txt for each language.

To do this, you add an extra directory called locale under the res directory for the project. You then place a locale text file with a name corresponding to the language. For example, I named the English version en.txt.

Once the files are in place, you need to modify build.xml to set the locale for which you want to build in the init target. For example:

<target name="init" depends="setup">
    ...
    <property name="locale" value="en"/>
</target>
You then copy the text file corresponding to the language for which you are building.
<target name="device build" depends="init">
    <property name="bd" value="build/${dev}-${locale}"/>        <!-- build dir -->
    ...

    <copy tofile="${bd}/res/locale.txt">
        <fileset file="res/locale/${locale}.txt"/>
    </copy>             <!-- copy locale files -->

    <wtkpackage jarfile="${bd}/${midlet.name}.jar"
            jadfile="${bd}/${midlet.name}.jad"
            preverify="true"
            classpath="${midplib}" obfuscate="true">
        <fileset dir="${bd}/classes"/>
        <fileset dir="${bd}/res"/>
    </wtkpackage>
</target>

Notice that at the top I've changed the build destination directory to now include the locale code (in addition to the device name). This is so I can build different versions for language and device, such as nokia-en (English) and nokia-fr (French).

    Table of ContentsChapter 16.            The LocalizationConclusion