Info on JMRI:
Development tools
Structure
Techniques and Standards
How To
Functional Info
Background Info

JMRI: Internationalization

This page discusses how the JMRI libraries handle internationalization.

The JMRI libraries are intended to be usable world-wide. To make this possible, they make use of the "internationalization" features built into the Java language and libraries.

Use of Locales

JMRI uses the default Locale for locating internationalization information. That means that JMRI will present its user interface in the language Java has defined as the default for that computer.

Locals are specified by a language, and optionally a country. The language is a two letter lower-case code; the country is a two letter upper case code. "en" is English, "fr" is French, "de" is German, and "de_CH" is German as spoken in Switzerland.

When Java looks for resources (see below), it searches first for a file with the complete current locale at the end of it's name (e.g. foo_de_CH.properties). If that fails, it tries for a file ending in just the current locale's language: foo_de.properties. And if that fails, it goes to the defaults with no suffix: foo.properties. A similar mechanism is used within XML files.

By installing appropriate files and allowing the user to select a default locale (as part of the advanced preferences), we can customize the program to different countries and languages.

Use of Resource Bundles

The text for menus, buttons and similiar controls is contained in property files, which are accessed via the Resource Bundle mechanism of java.util.

For example, the property file that's used to configure the Roster panel contains lines like:

FieldRoadName       = Road Name:
To the left of the equal sign is the resource name that the program uses to refer to the string; to the right of the equals sign is the string that will be displayed.

By convention, resouce names for GUI elements start with one of

  1. Field - for a visible field, e.g. label, on the GUI
  2. Button - for a GUI button
  3. Menu - the name of a top-level menu
  4. MenuItem - an item in a menu (may be a nested item)
  5. ToolTip - contents of a tooltip
  6. Error - for an error message displayed as part of the GUI
Other resources are named so as not to conflict with these.

Adapting to a new language

The primary steps to adapt JMRI to a new language are:

Get a clean copy of the source code using SVN. (For more info on using SVN, please see the page on getting a copy of the code.)

Translating Properties Files

If they don't exist already, start by making copies of the properties files with suffix for your new locale. On a MacOS X or Unix machine, this would be:
  cd java/src/apps
  cp AppsBundle.properties AppsBundle_xy.properties
and so on. The easiest way to find the proper suffix letters is to set the program to your particular language via the advanced preferences, quit and restart the program, and then look at the suffix that it displays on the main window. You can also check the official list of languages (first part of the suffix) and list of countries/regions (optional second part of the suffix).

You then edit the language-specific files to enter text in your own language. Please don't edit the lines in the file that contain things like $Release: $; those are used by SVN to keep track of the history of changes.

Languages that involve non-roman letters require some extra care. The property files must contain only ISO 8859-1 characters. If you want to use unicode characters, these must be manually escaped. (The 'native2ascii' command-line tool can help with this.) Please see the Java internationalization FAQ for more info on how to include those characters in your property files, particularly the question on "How do I specify non-ASCII strings in a properties file?". An example is the java/src/apps/AppsBundle_cs_CZ.properties file, which contains diacritical letters for the Czech translation.

There are several properties files that are used for internal control, and should not be translated. These are marked by a comment at the top of the file. Examples are the apps/AppsStructureBundle.properties, jmri/web/server/Services.properties and jmri/web/server/FilePaths.properties files.

To check your work:

  1. Rebuild your copy of the program.
  2. Start the program and select "Preferences" from the Edit menu
  3. Click the "show advanced preferences" box
  4. Select your language from the "Locale" drop-down box,
  5. Click "Save", quit and restart
  6. You should immediately seen the items you've translated.

If there's a problem at this point, check to see what language is listed on the application startup screen. Is it showing the same suffix (e.g. _fr or _cs_CZ) as you gave to your files? The suffix the program uses is determined by the Locale you selected in the preferences above.

To make your work available to other JMRI users, please share it with us. To do that:

By using this tracker and providing the patch file, it's easy for us to merge your new and/or changed files into the code repository. If anything goes wrong, please don't hesitate to ask for help with this.

Translating XML files

XML files can also be internationalized. There are examples in the decoder definition directories. Look for elements with a xml:lang="fr" attribute. Basically, you create additional elements with that attribute to specify the language used:
      <variable label="Vmid" CV="6" default="22" item="Vmid">
        <decVal max="64"/>
        <label>Vmid</label>
        <label xml:lang="fr">Vmoy</label>
      </variable>

In the XML files, the 'item' attributes have to stay untranslated, as does the entire xml/names.xml file.

Translating help files

(This has only been done once, so these instructions may not be complete)

The English help files are found in the help/en directory. If you want to create a complete set of files:

  • Create a copy of the existing files from the help/en directory in a new help/LL directory, where LL is the language code for your language, e.g. help/fr. (Please be careful doing this in SVN, and ask a developer for help if needed)
  • Rename the help/fr/JmriHelp_en.hs file you just created to help/fr/JmriHelp_fr.hs
  • Edit the help/fr/format.xsl to create a <HTML LANG="fr"> tag.
  • Translate the .shtml files below the help/fr directory. Do not translate any .xml, .jhm files or the web*.shtml files in the top directory, as they are automatically produced.

Internationalization for Developers

For internationalization to work, you have to do a few things in the code you write.

Some web references on how to do this:

JMRI is moving toward a set of conventions on how to structure and use the large amount of I18N information required. You'll still find code with older approaches, but you should write new code using the new conventions.

JMRI resource bundles are organized in a heirarchical tree. For example, code in the jmri.jmrit.display package may find a resource within a bundle in the jmri.jmrit.display package, jmri.jmrit package or finally the jmri package. As a special case in this, the apps package is viewed as being below the jmri package itself, so code in the apps tree also can reference the jmri package.

Cross-package references, e.g. between jmri.jmrit and jmri.jmrix, are discouraged and existing ones are being removed.

Access is via a Bundle class local to teach package. A typical one is jmri.jmrit.Bundle. It provides two key methods you use to access (translated) resource strings:

static String getMessage(String key)

static String getMessage(String key, Object... subs)
The first provides direct access to a string via

String msg = Bundle.getMessage("Title").

The second is used to insert specific information into a message like


  System name LT1 is already in use

Here "LT1" can't be in the properties file, because it's only known which name to display when the program is running. Different languages may put that part of the message in different places, and supporting that is important. That's handled by putting a placeholder in the message definition:


Error123 = System name {0} is already in use
(You can have more than one insertion, called {1}, {2}, etc)

Next, format the final message by inserting the content into it:


  String msg = Bundle.getMessage("Error123", badName);

The first argument is the message key followed by one or more strings to be inserted into the message. (This is better than creating your own output string using e.g. String.format() because it allows the inserted terms to appear in different orders in different languages.)

Different languages may need a different number of lines to express a message, or may need to break it before or after a particular value is inserted. It's therefore better to use "\n" within a single message from the properties file to create line breaks, rather than providing multiple lines in the code itself.

Some parts of JMRI remain English only due to our developer population. In particular, comments and variable names in the code should remain in English, as should messages sent to the logging system. In the Java code, these strings can be marked with a "// NOI18N" comment at the end of the line.

Adding a new Bundle

If your package does not already have a Bundle class, you can add it by:

Older code

Older code typically references the bundles directly:

  java.util.ResourceBundle.getBundle("jmri.jmrit.beantable.LogixTableBundle");

The getBundle argument is the complete package name (not file name) for the properties file this class will be using. You can have reference more than one of these objects if you'd like to look up strings in more than one properties file.

You can then retrieve particular strings like this:


java.util.ResourceBundle.getBundle("jmri.jmrit.beantable.LogixTableBundle").getString("ButtonNew");

We no longer recommend defining a class-static variable to hold the reference to the Bundle object, as this ends up consuming a lot of permanent memory in a program the size of JMRI. Go ahead and call the getBundle() each time, it's fast because it works through a weakly-referenced and garbage-collected cache.

XML Access

Second, you have to retrieve XML elements and attributes properly. The jmri.util.jdom.LocaleSelector provides a getAttribute(...) method that replaces the JDOM getAttribute element when the content of the attribute might have been internationalized. You use it like this:
String choice = LocaleSelector.getAttribute(choiceElement, "choice")
where "choiceElement" is a JDOM Element object containing a (possible translated) "choice" attribute. Null will be returned if nothing is found.

Testing

You should check that you've properly internationalized your code. We provide a tool for doing this which creates and automatically translated version of your properties files, following the ideas of Harry Robinson and Arne Thormodsen. (Their
paper on this is recommended reading!) To use it: If all is well, all the message text will have been translated to upper case. Anything you wrote that remains in lower case has not been completely internationalized.