Manual

Author: Ceki Gülcü Copyright © 2009-2013, QOS.ch |
This document is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License |

# CAL10N Manual

The goal of CAL10N project is to enhance the existing internationalization functionality offered by the Java platform with consistency and verification primitives. It is assumed that you are already somewhat familiar with using ResourceBundles.

### Acknowledgment

The original idea behind CAL10N is attributed to Takeshi Kondo. It consolidated into what it is today subsequent to a discussion involving Ralph Goers, Ceki Gülcü, Takeshi Kondo and Pete Muir on the slf4j-dev mailing list.

## Core idea

Instead of using String-typed keys for each message, CAL10N uses `enums`.

For example, let us assume that you wanted to internationalize color names. We are using a very small set of colors just as an example. In CAL10N you could start by writing an enum type, named Colors. You can choose any name for the enum type. Colors is just the name this particular author picked.

    package com.foo.somePackage;

    import ch.qos.cal10n.LocaleData;
    import ch.qos.cal10n.Locale;
    import ch.qos.cal10n.BaseName;

    @BaseName("colors")
    @LocaleData( { @Locale("en_UK"), @Locale("fr") })
    public enum Colors  {
      BLUE,
      RED,
      YELLOW;
    }

Once you define a few color keys, you can create a regular resource bundle named after the value of the @BaseName annotation in Colors and the appropriate locale. For example, for the UK locale, you would name your resource bundle as colors_en_UK.properties. It should also be placed in the appropriate folder on your class path or in the root folder of an appropriate jar file. (It is assumed that you know how to work with resource bundles.)

Here is a sample a colors_en_UK.properties file:

    BLUE=violets are blue
    RED=roses are red
    GREEN={0} are green

For the french locale, the resource bundle would be named colors_fr.properties. Here are sample contents.

    BLUE=les violettes sont bleues
    RED=les roses sont rouges
    GREEN=les {0} sont verts

Strictly speaking, the @LocaleData annotation is optional. It is used by verification tools discussed below.

## Retrieving internationalized messages

In your application, you would retrieve the localized message via an IMessageConveyor instance.

    // obtain a message conveyor for France
    IMessageConveyor mc = new MessageConveyor(Locale.FRANCE);

    // use it to retrieve internationalized messages
    String red = mc.getMessage(Colors.RED);
    String blue = mc.getMessage(Colors.BLUE);
    String green = mc.getMessage(Colors.GREEN, "pommes");  // note the second argument

CAL10N leverages the existing resource bundle infrastructure you have been accustomed to, but adds compiler verification on top. The default IMessageConveyor implementation, namely MessageConveyor, uses the standard Java convention for parameter substitution as defined by the java.text.MessageFormat class. In particular, for messages with parameters, the rules for using quotes defined by the `MessageFormat` class apply as is.

An astute reader will comment that even if the messages keys are now verified by the compiler, it is still possible to have mismatching message keys in the resources bundles. For this reason, CAL10N comes with additional tools, including support for writing Junit test cases and a maven plugin.

## Requirements / Installation

CAL10N requires JDK version 1.5.

In order to use CAL10N in a project, all you need is to add cal10n-api-0.7.7.jar onto your project's class path.

For Maven users, this is done by adding the following dependency in a project's pom.xml file:

    <dependency>
      <groupId>ch.qos.cal10n</groupId>
      <artifactId>cal10n-api</artifactId>
      <version>0.7.7</version>
    </dependency>

## Simplified bundle look-up procedure

The `ResourceBundle` class defines a rather involved look-up procedure for finding resource bundles. While formally this procedure is deterministic, it is also error-prone. More importantly, it clashes with CAL10N's goal of verifiability. We thus took the bold initiative to define a simplified bundle look up procedure, described below.

Given a locale, the simplified look up procedure only takes into account that locale, ignoring the default locale and the resource bundle corresponding to the naked base name, i.e. the default resource bundle. However, if the locale has both a language and a country, and corresponding bundle files exist, then CAL10N will take into account both bundles, establishing the same parent child relationship as the JDK `ResourceBundle` class.

For example, for base name "colors" if the following bundles exist on the class path:

    colors.properties
    colors_en_US.properties
    colors_en.properties
    colors_fr_FR.properties

and the system's default locale is "fr_FR", when CAL10N is asked to find resource bundles corresponding to the "en_US" locale, it will systematically ignore the colors.properties (i.e. the default bundle), and combine the colors_en_US.properties and colors_en.properties bundles in the usual parent-child relationship. Since CAL10N is asked to lookup resource bundles corresponding to the "en_US" locale, the bundle corresponding to the default locale, i.e. colors_fr_FR.properties will also be ignored. Thus, the CAL10N bundle lookup procedure differs from the standard by ignoring the default bundle, the one with the naked base name, e.g. colors.properties.

We hope that the simplified look-up procedure, while deviating from the conventions of the Java platform as defined in the `ResourceBundle` class, will still cause less surprise.

## Pick your charset, per locale (no native2ascii)

CAL10N allows you to encode the resource bundle for a given locale in the charset of your choice. The set of supported charsets depends on your Java platform. See this java encoding and charset list for more details.

Assume you have four resource bundles, for the English, French, Turkish and Greek languages. You decide to encode all of them in UTF-8. To tell CAL10N that UTF-8 is the default encoding for all locales, one would write:

    @BaseName("colors")
    @LocaleData(
      defaultCharset="UTF8",
      value = { @Locale("en_UK"),
                @Locale("fr_FR"),
                @Locale("tr_TR"),
                @Locale("el_GR")
               }
       )
    public enum Colors {
     BLUE,
     RED,
     GREEN;
    }

If for some reason the Turkish bundle was encoded in ISO8859_3, but the others locales in UTF8, you would write:

    @BaseName("colors")
    @LocaleData(
      defaultCharset="UTF8",
      value = { @Locale("en_UK"),
                @Locale("fr_FR"),
                @Locale(value="tr_TR", charset="ISO8859_3"),
                @Locale("el_GR")
               }
       )
    public enum Colors {
     BLUE,
     RED,
     GREEN;
    }

The `defaultCharset` directive specified in the `@LocaleDat`a annotation applies to all nested @Locale annotations, unless the value is overriden by a `charset` directive (as in the "tr_TR" locale in the example above). If not specified, the default value for `defaultCharset` is the empty string. In the absence of a `defaultCharset` directive, the default value for the `charset` directive is also the empty String.

If both `charset` and `defaultCharset` are empty, CAL10N will use the default encoding for your Java platform to read resource bundles.

## Automatic reloading of resource bundles upon change

When a resource bundle for a given locale is changed on the file system, `MessageConveyor` will automatically reload the resource bundle. It may take up to 10 minutes for the change to be detected.

Automatic reloading applies if the resource bundle is a regular file but not if nested within a jar file.

## Deferred localization

Under certain circumstances, the appropriate locale is unknown at the time or place where the message key and message args are emitted. For example, a low level library might wish to emit a localized message but it is only at the UI (user interface) layer that the locale is known. As another example, imagine that the host where the localized messages are presented to the user is in a different locale, e.g. Japan, than the locale of the source host. e.g. US.

The MessageParameterObj class is intended to support this particular use case. It allows you to bundle the message key (an enum) plus any message arguments. At a later time, when the locale is known, you would invoke the getMessage(MessageParameterObj) method in `IMessageConveyor` to obtain the localized message.

## Verification as a test case

A convenient and low hassle method for checking for mismatches between a given enum type and the corresponding resource bundles is through Junit test cases.

Here is a sample Junit test for the Colors enum discussed above.

    package foo.aPackage;

    import static org.junit.Assert.assertEquals;

    import java.util.List;
    import java.util.Locale;

    import org.junit.Test;

    import ch.qos.cal10n.verifier.Cal10nError;
    import ch.qos.cal10n.verifier.IMessageKeyVerifier;
    import ch.qos.cal10n.verifier.MessageKeyVerifier;

    public class MyColorVerificationTest {

      @Test
      public void en_UK() {
        IMessageKeyVerifier mkv = new MessageKeyVerifier(Colors.class);
        List<Cal10nError> errorList = mkv.verify(Locale.UK);
        for(Cal10nError error: errorList) {
          System.out.println(error);
        }
        assertEquals(0, errorList.size());
      }

      @Test
      public void fr() {
        IMessageKeyVerifier mkv = new MessageKeyVerifier(Colors.class);
        List<Cal10nError> errorList = mkv.verify(Locale.FRANCE);
        for(Cal10nError error: errorList) {
          System.out.println(error);
        }
        assertEquals(0, errorList.size());
      }
    }

The above unit tests start by creating a `MessageKeyVerifier` instance associated with an enum type, `Colors` in this case. The test proceeds to invoke the `verify()` method passing a locale as an argument. The `verify()` method returns the list of errors, that is the list of discrepancies between the keys listed in the enum type and the corresponding resource bundle. An empty list will be returned if there are no errors.

The unit test verifies that no errors have occurred by asserting that the size of the error list is zero.

Suppose the key "BLUE" was misspelled as "BLEU" in the colors_fr.properties resource bundle. The unit test would print the following list of errors and throw an `AssertionError`.

    Key [BLUE] present in enum type [ch.qos.cal10n.sample.Colors] but absent in resource bundle \
       named [colors] for locale [fr_FR]
    Key [BLEU] present in resource bundle named [colors] for locale [fr_FR] but absent \
       in enum type [ch.qos.cal10n.sample.Colors]

### One test to rule them all

Instead of a separate unit test case for each locale, assuming you declared the locales in the enum type via the @LocaleData and nested @Locale annotations, you can verify all locales in one fell swoop.

    package foo.aPackage;

    import static org.junit.Assert.assertEquals;

    import java.util.List;
    import java.util.Locale;

    import org.junit.Test;

    import ch.qos.cal10n.verifier.Cal10nError;
    import ch.qos.cal10n.verifier.IMessageKeyVerifier;
    import ch.qos.cal10n.verifier.MessageKeyVerifier;

    public class MyAllInOneColorVerificationTest {

      // verify all locales in one step
      @Test
      public void all() {
        IMessageKeyVerifier mkv = new MessageKeyVerifier(Colors.class);
        List<Cal10nError> errorList = mkv.verifyAllLocales();
        for(Cal10nError error: errorList) {
          System.out.println(error);
        }
        assertEquals(0, errorList.size());
      }
    }

## Maven Plugin

The CAL10N project ships with a maven plugin designed to verify that the keys specified in a given enum type match those found in the corresponding resource bundles and for each locale. Our plugin is unsurprisingly called mvn-cal10n-plugin.

Using maven-cal10n-plugin is pretty easy. To verify enums in a given project, just declare the maven-cal10n-plugin in the `<build>` section, enumerating all the enum types you would like to see checked.

Here is a sample pom.xml snippet.

    <build>
      <plugins>
       ... other plugins
        <plugin>
          <groupId>ch.qos.cal10n.plugins</groupId>
          <artifactId>maven-cal10n-plugin</artifactId>
          <executions>
            <execution>
              <id>aNameOfYourChoice</id>
              <phase>verify</phase>
              <goals>
                <goal>verify</goal>
              </goals>
              <configuration>
                <enumTypes>
                   <!-- list every enum type you would like to see checked -->
                   <enumType>some.enumTpe.Colors</enumType>
                   <enumType>another.enumTpe.Countries</enumType>
                </enumTypes>
              </configuration>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>

After you add the above snippet to the pom.xml file of your project, maven-cal10n-plugin will make sure that your resource bundles and your enum type are in synchronized.

The plugin will iterate through every resource bundle for every locale listed in the enum type via the `@LocaleData` and `@Locale` annotations.

## Eclipse plug-in

We are looking for volunteers willing to implement IDE support for CAL10N. Below is a list of possible features of this IDE which we think could have added value.

- mismatch highlighting

  While editing a property file, highlight any keys that do not match any keys defined in the corresponding enum type.

- auto-correction

  The mismatch highlighting feature could be enhanced by making correction suggestions. For example, if in the property file the user misspells the key "red_elephant" as "red_elefant", by measuring the Levenshtein distance the plug-in could propose the nearest neighbor of "red_elefant" in the enum type, which should be "red_elephant" with high probability.

- Skeleton generation

  Given an enum type, generate the skeleton of the corresponding resource bundle

If interested please contact the cal10n-dev mailing list.

## Ant task

We are looking for volunteers to implement an Ant task to verify resource bundles. The Ant task could be modeled after the maven-cal10n-plugin although the Ant plugin is likely to be simpler. If interested please contact the cal10n-dev mailing list.