Skip to content
Francis Galiegue edited this page Jun 17, 2013 · 8 revisions

Preliminary note

This documents version 0.5. You can see sample usages here

Features

Full i18n support

When you .getMessage() from a MessageBundle, the current JVM locale is tried first, then other, more generic locales are tried. For instance, if your locale is ja_JP_JP, the following locales are tried in order:

  • ja_JP_JP;
  • ja_JP;
  • ja;
  • the root locale (Locale.ROOT in Java parlance).

You can also specify the locale you want using dedicated methods.

Message source, message provider extensibility

A MessageBundle works as follows when trying to find a message for a given locale/key pair:

  • it loops through all of its registered MessageSourceProviders in order (see below);
  • for one provider, it tries and sees if it has a MessageSource for this locale;
  • if one is found, it queries the source for the key.

You can implement these interfaces the way you want. The implementation provides two implementations of MessageSourceProvider (one including dynamic lookup with caching and configurable timeout and expiry) and two implementations of MessageSource, including one to read from property files.

And as to property files...

Property files are read in UTF-8

This is unlike the JDK's ResourceBundle, which reads property files in ISO-8859-1.

Message providers can be stacked

Using the builder class for a MessageBundle, you can append or prepend message providers:

MessageProvider p1, p2, p3;

MessageBundleBuilder builder = MessageBundle.newBuilder();

builder = builder.appendProvider(p1).appendProvider(p2); // Order is now: p1 -> p2
builder = builder.prependProvider(p3); // Order is now: p3 -> p1 -> p2

// Build the final bundle
final MessageBundle bundle = builder.build();

Note that MessageBundles are immutable. However, You can reuse an existing one and extend it:

MessageProvider p4;

// New bundle with order p3 -> p1 -> p2 -> p4
final MessageBundle newBundle = bundle.thaw().appendProvider(p4).freeze();

Unknown keys are returned as is

This is unlike a ResourceBundle, which, in this case, throws an (unchecked!!) exception. In the event when a key does not exist, the key itself is returned. This allows for more graceful failures, and an easy spotting of missing keys.

Example:

final Map<String, String> map = new HashMap<String, String>();
map.put("foo", "bar");

final MessageSource source = new MapMessageSource(map);
// .appendSource() is a convenience method to append a provider with a single source for all locales
final MessageBundle bundle = MessageBundle.newBuilder().appendSource(source).freeze();

bundle.getKey("foo"); // returns "bar"
bundle.getKey("baz"); // returns "baz"

ServiceLoader support

You can now automatically register bundles using this API. For this, you need to have one or more implementation(s) of com.github.fge.msgsimple.serviceprovider.MessageBundleProvider and register them where appropriate.

See here for a good, detailed explanation. Also note that there is a Maven plugin which can generate the appropriate file in your build output directory for you.

printf() support

One thing a ResourceBundle does not support! You can use format strings in your property files and use the available .printf() methods on a bundle to get a message just like what String.format() does.

Builtin assertions

There are two family of assertion methods: null checks and condition checks. Failure of a null check throws a NullPointerException; failure of a condition check throws an IllegalArgumentException. In both cases, the message associated with the exception is looked up/built in the same way as regular messages. For instance:

bundle.checkNotNull(foo, "cfg.error.nullFoo");
bundle.checkNotNullPrintf(bar, "cfg.error.nullBar", arg1, arg2);
bundle.checkArgument(cond1, "cfg.cond.fail1");
bundle.checkArgumentPrintf(cond2, "cfg.cond.fail2", arg);