-
Notifications
You must be signed in to change notification settings - Fork 15
Examples
This page provides a usage guide for this library. You can view the full javadoc here.
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. This means you can "write normally", and this is unlike
ResourceBundle(orPropertiesfor that matter) which read using the ISO-8859-1 encoding; - parameterized messages do not use
MessageFormatbut the much more convenient (and older!)printf()format.
Now you can...
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 localeIn 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 localeFinally, 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);-
Querying a missing key causes the key itself to be returned.
ResourceBundlethrows an (unchecked...) exception in that case. -
Using a mismatched format string/arguments combination causes the format string itself to be returned. Here again,
ResourceBundlethrows an (unchecked...) exception in that case.
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,ptthen 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.
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
// Other loading methods: File, InputStreamNote 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...).
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.
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();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)
.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 permanent.
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 bundleThe 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();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).build();Job done!
This library uses the builtin JDK ServiceLoader mechanism. In order to use it, you need two things:
- create one, or more, implementation(s) of
MessageBundleProvider; - create, and package, the service file.
While this mechanism, as you will see below, is a little complex to use, it has the merits of being standard, and it allows you not to have to write your own class for this.
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");
}
}The file you create must be named META-INF/services/com.github.fge.msgsimple.serviceloader.MessageBundleProvider. In this, you list your implementations of the interface:
# Example of a service file
com.myproject.bundle.MyBundle
Note that if you use Maven, you can use the serviceloader-maven-plugin plugin to generate the file automatically for you.
At runtime, you will then be able to get a reference to the bundle(s) you have registered using this:
final MessageBundle mainBundle = MessageBundleFactory.getBundle(MyBundle.class);