See: Description
| Interface | Description |
|---|---|
| I18NBoundMessage |
A bound message, representing the combination of an
I18NMessage and its parameters, if any. |
| Messages |
The internationalization constants used by this package.
|
| Class | Description |
|---|---|
| ActiveLocale |
Manages the active locale.
|
| I18NBoundMessage0P |
A bound message, representing a
I18NMessage0P. |
| I18NBoundMessage1P |
A bound message, representing the combination of an
I18NMessage1P and its one parameter. |
| I18NBoundMessage2P |
A bound message, representing the combination of an
I18NMessage2P and its two parameters. |
| I18NBoundMessage3P |
A bound message, representing the combination of an
I18NMessage3P and its three parameters. |
| I18NBoundMessage4P |
A bound message, representing the combination of an
I18NMessage4P and its four parameters. |
| I18NBoundMessage5P |
A bound message, representing the combination of an
I18NMessage5P and its five parameters. |
| I18NBoundMessage6P |
A bound message, representing the combination of an
I18NMessage6P and its six parameters. |
| I18NBoundMessageBase<T extends I18NMessage> |
A bound message implementation, representing the combination of an
I18NMessage and its parameters, if any. |
| I18NBoundMessageNP |
A bound message, representing the combination of an
I18NMessageNP and its arbitrary number of parameters. |
| I18NLoggerProxy |
A logger which supports internationalized messages.
|
| I18NMessage |
An internationalized message, represented using a pair of textual
keys referencing the message text; keys-text maps are stored
outside the source code.
|
| I18NMessage0P |
An internationalized message, requiring exactly zero parameters.
|
| I18NMessage1P |
An internationalized message, requiring exactly one parameter.
|
| I18NMessage2P |
An internationalized message, requiring exactly two parameters.
|
| I18NMessage3P |
An internationalized message, requiring exactly three parameters.
|
| I18NMessage4P |
An internationalized message, requiring exactly four parameters.
|
| I18NMessage5P |
An internationalized message, requiring exactly five parameters.
|
| I18NMessage6P |
An internationalized message, requiring exactly six parameters.
|
| I18NMessageNP |
An internationalized message, accepting an arbitrary number of
parameters.
|
| I18NMessageProvider |
An internationalized message provider, mapping instances of
I18NMessage onto text. |
| LogUtils |
General-purpose utilities.
|
| SLF4JLoggerProxy |
SLF4J proxy with automatic logger selection and variable number of
arguments.
|
Multi-tiered logging and internationalization (i18n) framework.
SLF4JLoggerProxyAt the lowest layer, the framework offers a simple proxy to SLF4J
via SLF4JLoggerProxy, wherein the
proxy automatically determines the right category under which a
message should be logged. At this level, string are directly logged
(there is no i18n). Calls to logging methods can be guarded via the
provided SLF4JLoggerProxy.isErrorEnabled(Object)
method and its counterparts for other logging levels. But such guards
are not essential because the logging methods allow for a placeholder
string and varargs object arguments (e.g. a message such
as Hello {}! and argument world is logged
as Hello world!). If the numbers of placeholders and
arguments are unequal, then all extra occurences of {}
are displayed as {}, or extra arguments are ignored.
I18NMessageAt the next layer, strings are not directly used. Instead, message
handles are used, and they are translated into strings at run-time;
this is the basis for composing i18n code that is localized at
run-time. Message handles are instances of I18NMessage. The translation into strings
is done via I18NMessageProvider, at
which time the varargs object arguments are also supplied. That is,
retrieving the actual localized string and replacing placeholders with
their actual values is done in a single step. Similarly, i18n messages
can be logged via I18NLoggerProxy by
supplying a message handle and the varargs object arguments to the
logger.
Calls to logging methods need not be guarded; if guarding is
desired, use the methods of the SLF4JLoggerProxy tier.
I18NMessagexPI18NMessage0P, I18NMessage1P, ..., I18NMessageNP are classes that enable the
compiler to confirm that the number of arguments supplied at
localization matches the number of placeholders. That is, the message
handle is created as an instance of
a I18NMessagexP class instead of the generic
I18NMessage; and localization and
logging is done via methods of the
same I18NMessagexP class. The methods
of I18NMessagexP accept exactly x
arguments. I18NMessageNP is a
special case that accepts an arbitrary number of arguments (to allow
for a large x for which no
class I18NMessagexP is provided).
I18NBoundMessagexPI18NBoundMessage0P, I18NBoundMessage1P, ..., I18NBoundMessageNP are classes that combine
a corresponding I18NMessagexP instance and the
message arguments into a single
object. A I18NBoundMessagexP instance is not
localized yet, but the arguments to the message are fixed. These
instances are used when a message and its arguments need to be
supplied to another object for delayed localization (e.g. the message
of an exception). Localization is done via methods of the I18NBoundMessage interface, which all
the I18NBoundMessagexP implement.
Note that I18NBoundMessage0P is
conceptually identical to a I18NMessage0P because there are no
placeholders in such a message. This is why I18NMessage0P also implements the I18NBoundMessage interface, and can thus be
used directly where an I18NBoundMessage is needed.
The following design pattern is recommended for users of this framework.
First, define an interface called Messages in each of
your packages. This is what such an interface looks like; note that
all message-related fields must be static and non-null (otherwise the
localization utilities in org.marketcetera.util.l10n will
ignore them):
public interface Messages
{
static final I18NMessageProvider PROVIDER=
new I18NMessageProvider("util_file");
static final I18NLoggerProxy LOGGER=
new I18NLoggerProxy(PROVIDER);
static final I18NMessage0P CLOSING_FAILED=
new I18NMessage0P(LOGGER,"closing_failed");
static final I18NMessage1P CANNOT_GET_TYPE=
new I18NMessage1P(LOGGER,"cannot_get_type");
... more messages
}
The util_file string above points to a file that
contains the localized messages, in the commons-i18n syntax (in which
placeholders are of the form {n},
not {} as we saw earlier for SLF4J). The full file name sought
is util_file_messages.properties, and it should reside in
your project's src/main/resources. Here is a sample
file:
closing_failed.msg=Closing failed
cannot_get_type.msg=Cannot determine type of file ''{0}''
The odd syntax ''{0}'' (two single quotes on each
side) produces a message that reads 'a' when the first
message parameter is the
string a. Similarly, '{0}'
produces {0}; "{0}" (one double quote on
each side) produces "a" (because a double quote is not a
special character); and {0} produces a. If
the numbers of placeholders and arguments are unequal, then all extra
occurences of {n} are displayed
as {n}, or extra arguments are ignored.
Additional files can be provided for additional locales, using the
standard Java resource bundle system. For example, the
file util_file_messages_fr.properties can contain
messages in French;
or util_file_messages_fr_CA.properties can be used for
French in Canada and util_file_messages_fr_FR.properties
for French in France.
You should also define a test class
called MessagesTest, which ensures each of your message
handles can be resolved in the fallback locale; see the org.marketcetera.util.l10n package for the recommended approach.
Here is sample code to localize or log a message:
class MyClass {
...
// Lowest tier.
SLF4JLoggerProxy.debug(this,"My message is {}","hello");
// Middle tier.
String text=Messages.PROVIDER.getText(Messages.CANNOT_GET_TYPE,"myfile.xml");
Messages.LOGGER.error(this,Messages.CANNOT_GET_TYPE,"myfile.xml");
// Top tier (unbound).
text=Messages.CANNOT_GET_TYPE.getText("myfile.xml");
Messages.CANNOT_GET_TYPE.error(this,"myfile.xml");
// Top tier (bound).
myMethod(new I18NBoundMessage1P(Messages.CANNOT_GET_TYPE,"myfile.xml"));
...
void myMethod(I18NBoundMessage message) {
String text=message.getText();
message.error(this);
}
...
}
Another aspect of i18n is proper handling of locales. In single-threaded client environment, a process-wide JVM locale is typically sufficent. But in multi-threaded server applications, where different threads may be servicing clients in different locales, a per-thread active locale is essential. Generalizing this concept further, the active locale can be scoped in the same manner that permissions checks are scoped, with a locale stack that mirrors the call stack.
The ActiveLocale class manages
the active locale in this scoped fashion. I18NMessageProvider uses that class to
determine the right translation of a handle into a string (when an
explicit locale is not supplied). Similarly, all code that needs to
get/set the active locale should use that class.
Messages and their providers can be serialized. Upon successful
deserialization, they are guaranteed to be resolvable (localizable)
insofar as the message files are guaranteed to exist: that is, if the
appropriate localization message files do not exist in the
deserialization context (e.g. the classloader employed by I18NMessageProvider is not be the right
one), or the message parameters are instances of classes that are not
available, deserialization will fail. Note that there is no guarantee
that the message handle will be correctly mapped to text within the
existing message file: deserialization will succeed even if the handle
cannot be mapped. The implied simplifying assumption is that, if the
message file can be located in the deserialization context, it is
assumed to be complete.
Conduct under deserialization demonstrates a certain asymmetry. A message and its provider can be created and used even when the necessary message files are not available or the message is not mapped therein; the system will simply translate the message to a string containing the raw (unlocalized) message handle and its parameters. However, deserialization of messages and their providers fails altogether if the message file/handle cannot be located. Why the asymmetry? For one, the asymmetry is unavoidable when the message parameters are instances of classes that are not available in the deserialization context; however, there is more to this asymmetry than this unavoidable scenario.
The reason is that in both cases we want to achieve the same
end-goal: maximize the information available to the end-user, and to
do so with minimal disruption to the system's operation. In the former
case, the only information we have on a message is its handle and
parameters, so, if we cannot localize, we log an error message and do
the best we can to translate the message. In the latter case, a
failure is desirable because this enables the deserializer to provide
a more meaningful translation than the above simplistic
handle-and-parameters text. Specifically, deserialization occurs in
the context of client/server communications, and it is possible that
the client may not have access to the server's message files. However,
the server typically does have access to its message files (as well as
the classes used by the message parameters). So, if the client cannot
localize a message received from the server, the best fallback is to
use the server's localization of the same message: to do this, the
deserialization must fail (and, in the case of parameters whose
classes are unavailable on the client, deserialization will certainly
fail), so that the client can use the server's localization. The
org.marketcetera.util.ws.wrappers package offers proxy objects
for messages (or exceptions which can contain messages) that implement
this fallback transparently.
Copyright © 2015. All Rights Reserved.