Skip to content
Francis Galiegue edited this page Feb 14, 2014 · 15 revisions

Introduction

This page provides a usage guide for this library. You can view the full javadoc here.

Quick start: using a property bundle

This library uses the same convention for property files as does a ResourceBundle. That is, if your base resource file is called messages.properties, then messages_fr_FR.properties will provide messages for the French language, etc.

As this is quite a common scenario, a helper class exists to directly generate a bundle from such a set of files:

// All these are equivalent
final MessageBundle bundle = PropertiesBundle.forPath("/com/mypackage/messages");
final MessageBundle bundle = PropertiesBundle.forPath("/com/mypackage/messages.properties");
final MessageBundle bundle = PropertiesBundle.forPath("com/mypackage/messages");
final MessageBundle bundle = PropertiesBundle.forPath("com/mypackage/messages.properties");

Two things are important to note:

  • property files are read using UTF-8 by default. This means you can "write normally", and this is unlike ResourceBundle (or Properties for that matter) which read using the ISO-8859-1 encoding;
  • parameterized messages do not use MessageFormat but the much more convenient (and older!) printf() format.

Loading a legacy ResourceBundle

Since 0.5, there is also a method to directly load a legacy ResourceBundle (ISO-encoded files):

final MessageBundle bundle = PropertiesBundle.legacyResourceBundle("com/mypackage/messages");

Now you can...

Using your bundle

As a ResourceBundle, a MessageBundle uses keys to retrieve messages. The two basic methods are:

bundle.getMessage("myKey");              // Uses the default JVM locale
bundle.getMessage(someLocale, "myKey");  // Uses a specific locale

In order to print a message with arguments, this library does not use the horrendous MessageFormat but the more modern (in Java, that is; C has had it for 30+ years) Formatter:

bundle.printf("myKey", arg1, arg2);             // Uses the default JVM locale
bundle.printf(someLocale, "myKey", arg1, arg2); // Uses a specific locale

Bundles have builtin precondition checks:

// Throws a NullPointerException with the computed message if "ref" is null
bundle.checkNotNull(ref, "myKey");
bundle.checkNotNullPrintf(ref, "myKey", arg);
bundle.checkNotNull(ref, locale, "myKey");
bundle.checkNotNullPrintf(ref, locale, "myKey", arg);

// Throws an IllegalArgumentException if the boolean expression is false
bundle.checkArgument(condition, "myKey");
bundle.checkArgumentPrintf(condition, "myKey", arg);
bundle.checkArgument(condition, locale, "myKey");
bundle.checkArgumentPrintf(condition, locale, "myKey", arg);

Finally, since 0.5, the API also supports MessageFormat-type messages:

// Render a message using MessageFormat
bundle.format("myKey", arg1, arg2);
bundle.format(locale, "myKey", arg1, arg2);
bundle.checkNotNullFormat(ref, "myKey", arg1, arg2);
// etc

Key usage differences to ResourceBundle

  • Querying a missing key causes the key itself to be returned. ResourceBundle throws an (unchecked...) exception in that case.
  • Using a mismatched format string/arguments combination causes the format string itself to be returned. Here again, ResourceBundle throws an (unchecked...) exception in that case.

Advanced usage

Introduction

A MessageBundle is a list of MessageSourceProviders; in turn, these MessageSourceProviders can provide one MessageSource for each of the locales they support. It is these MessageSources which ultimately hold the key/message pairs.

When querying a key from a bundle, what happens is the following:

  • a list of locales, from the more specific to the more general, is built (for instance: pt_BR, pt then the root locale); they are queried in this order until a match is found;
  • for one locale, the list of source providers is walked; each provider is asked whether it has a source for this locale;
  • when a source is found, it is queried for that key; if the key is not found, the next source provider is tried;
  • if all providers and sources have been exhausted without finding a match the key itself is returned.

In this section, you will learn how to build the three components which make up a MessageBundle, from the lowest level (MessageSource) up to the highest level (MessageBundle); you will also learn how to register a MessageBundle so that it be available at load time.

Creating MessageSources

The library provides two message source implementations: a very basic one based on a Map, and another which loads a property file (which is used above).

Here are some examples:

MessageSource source;
// Map message source
source = MapMessageSource.newBuilder()               // Create builder
    .put("key1", "value1").put("key2", "value2")     // Put individual entries
    .putAll(otherMap)                                // Put a whole map
    .build();                                        // Build the source

// Properties message source
source = PropertiesMessageSource.fromPath("/path/to/some.properties"); // filesystem
source = PropertiesMessageSource.fromResourcePath("/com/mycompany/messages.properties"); // classpath
// Load a properties file with an alternate character set
source = PropertiesMessageSource.fromPath("/path/to/some.properties", Charset.forName("Windows-1252"));
// Other loading methods: File, InputStream

Note that MessageSource is an interface. This means you can implement it so as to read messages from whichever source you can think of (database, online service...).

Creating MessageSourceProviders

Message source providers are the next level; they allow to provide message sources to the final product (a message bundle) according to a given locale.

MessageSourceProvider is also an interface, and the library provides two implementations:

  • StaticMessageSourceProvider: a message source provider with fixed locale/source mappings;
  • LoadingMessageSourceProvider: a message source provider loading sources on demand.

Static providers

Here are some examples:

MessageSourceProvider provider;
// One same source for every locale
provider = StaticMessageSourceProvider.withSingleSource(source);
// One source for one locale
provider = StaticMessageSourceProvider.withSingleSource(source, LocaleUtils.parseLocale("it_IT"));

// More complicated scenario: use a builder
provider = StaticMessageSourceProvider.newBuilder()
    .addSource(Locale.CANADA, source1)
    .addSource(LocaleUtils.parseLocale("it_IT_sicily"), source2)
    .setDefaultSource(otherSource)
    .build();

On demand loading providers

In order to create a loading provider, you first need to create an implementation of MessageSourceLoader. This interface is defined like this:

public interface MessageSourceLoader
{
    /**
     * Load a message source for a locale
     *
     * @param locale the locale (guaranteed never to be {@code null}
     * @return a message source ({@code null} if not found)
     * @throws IOException loading error
     */
    MessageSource load(final Locale locale)
        throws IOException;
}

The library has one implementation of this interface, used to load property files on demand; this is what is used in the very first example.

You then need to inject this implementation (called loader below) into a LoadingMessageSourceProvider via its builder class:

final MessageSourceProvider provider = LoadingMessageSourceProvider.newBuilder()
    .setLoader(loader)                 // set the MessageSourceLoader implementation
    .setDefaultSource(source)          // (optional) set the default source, if any
    .setTimeout(10L, TimeUnit.SECONDS) // optional: set timeout (default is 5 seconds)
    .setExpiryTime(2L, TimeUnit.HOURS) // optional: set expiry time (default is 10 minutes), or...
    .neverExpires()                    // ... tell the provider to never expire instead
    .build();

Some notes about the behaviour of LoadingMessageSourceProvider:

  • if a loading attempt times out, it is retried the next time the locale is queried;
  • successful loads, as well as failures, are permanently stored until the expiry time kicks in (if you chose to expire entries).

Creating a MessageBundle

Now that you have your message sources and providers, you can build your bundle:

final MessageBundle bundle = MessageBundle.newBuilder()
    .appendProvider(provider1)    // Append a provider to the list
    .prependProvider(provider2)   // Prepend a provider to the provider list
    .build();                     // Build the bundle

Convenience builder methods

The builder class for MessageBundle also has convenience methods if you want to inject static message sources without having to build a MessageSourceProvider:

// Append/prepend sources for all locales
MessageBundle.newBuilder()
    .appendSource(source1)
    .prependSource(source2)
    .build();
// Append/prepend sources for a specific locale
MessageBundle.newBuilder()
    .appendSource(locale1, source1)
    .prependSource(locale2, source2)
    .build();

Reusing bundles

If you are provided with a bundle from an external library, for instance, and you want to reuse it for your own needs but modify it, you have the ability to do so. Here is for example how to create a new bundle from an existing one, and prepending a source of your own:

final MessageBundle myBundle = otherBundle.thaw().prependSource(mySource).freeze();

Job done!

Loading your bundle at runtime

Principle of operation

This library ca load bundles at runtime for you. mechanism. In order to use it, you need to create one, or more, implementation(s) of MessageBundleProvider.

Creating a MessageBundleProvider

The MessageBundleProvider interface is pretty simple:

public interface MessageBundleProvider
{
    MessageBundle getBundle();
}

If we take the example of a properties bundle, an implementation could be:

public final class MyMessageBundle
    implements MessageBundleProvider
{
    @Override
    public MessageBundle getBundle()
    {
        return PropertiesBundle.forPath("/com/myproject/bundle/messages.properties");
    }
}

Loading it at runtime

At runtime, you will then be able to get a reference to the bundle(s) you have registered using this:

final MessageBundle mainBundle = MessageBundles.getBundle(MyBundle.class);

Note that for a single implementation, the bundle will be loaded only once for your entire application.