-
Notifications
You must be signed in to change notification settings - Fork 16
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 by default. This means you can "write normally", and this is unlike
ResourceBundle
(orProperties
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.
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...
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
-
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.
A MessageBundle
is a list of MessageSourceProvider
s; in turn, these MessageSourceProvider
s can provide one MessageSource
for each of the locales they support. It is these MessageSource
s 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.
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...).
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)
.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).
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
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();
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!
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
.
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");
}
}
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.