Modern desktop applications often need to support multiple languages if they serve a multi-lingual user base. Therefore the texts to be displayed in the graphical user interface cannot be hard-coded in the source code, but have to be externalized in some way. But even if an application addresses only users speaking the same language, it is good practice to keep the texts separated from the rest of the program code. This simplifies changes and keeps a possible upgrade path: if the application turns out to be a big success, a multi-lingual version may be required sooner than the developers thought - you never know.
The Java SDK used to provide support for multi-lingual applications from the
very beginning. Typically the ResourceBundle
class is used for
accessing data that may be translated into different languages. The actual
texts are frequently stored in resource properties files, and
ResourceBundle
takes care that the correct texts for the
currently active language (represented as a java.util.Locale
object) are loaded. Many developers are familiar with this easy yet
powerful approach.
The JGUIraffe library also supports resources in various forms.
It is especially easy to integrate resource properties files as used by the
well-known ResourceBundle
class; so developers do not have to
change their way of thinking about resources dramatically. However, by
providing alternative implementations for some of the fundamental interfaces
that deal with access to resources, it is possible to load resources from
different sources, e.g. from configuration files or from a database.
In the following sub sections the interfaces and classes for loading and obtaining resource data are explained. Applications that need direct access to their resources can make use of these components. If the builder framework is used, it is often not necessary to deal with these classes directly, as builder scripts are inherently capable of accessing resources and configuring objects (e.g. GUI components or service beans) with them.
The interfaces and classes related to resources are located in the
net.sf.jguiraffe.resources
packages. In this section we will
introduce the most important interfaces.
ResourceGroup
interface. A ResourceGroup
contains texts in a specific
language. Single elements of the group can be accessed using the
getResource()
method passing in an arbitrary object as resource
key. The getKeys()
method returns a set with all known keys
in this ResourceGroup
. A ResourceGroup
is similar
to a resource bundle with a specific base name. An application can create an
arbitrary number of resource groups organizing its resources in several
logic chunks. For smaller applications it can also make sense to store all
resources in a single group.
ResourceLoader
object. A ResourceLoader
is rarely
accessed by client code directly, it works behind the scenes. However, it
plays an important role for the extensibility of the resources framework:
By specifying an alternative ResourceLoader
implementation a
different strategy for locating resources can be activated. The
ResourceLoader
interface is pretty lean. It defines a single
method for obtaining a ResourceGroup
for a given name and
Locale
. An implementation can do whatever is necessary to
retrieve the underlying resources and create a ResourceGroup
object with this information. For instance, the default
ResourceLoader
implementation for resource bundles obtains
the corresponding java.util.ResourceBundle
for the group name
and locale and creates a wrapper object around this bundle.
ResourceManager
interface, which is probably the most important
interface from a client's point of view. ResourceManager
provides several methods for accessing whole resource groups or single
elements from specific resource groups. With these methods arbitrary
resources can be obtained. The JGUIraffe library provides a default
implementation of the ResourceManager
interface. This
implementation is fully functional, and it should hardly be necessary to
provide a different implementation as customization can be done by
injecting another ResourceLoader
(ResourceManager
has a setResourceLoader()
for switching to a different
ResourceLoader
implementation).This completes the introduction of the fundamental interfaces for dealing with resources. The following section is about making use of these interfaces.
The
ResourceManager
interface provides a low-level API for accessing
resources. For making use of this API, a ResourceManager
instance has to be obtained first. This can be achieved by calling the
getResourceManager()
method of the current
ApplicationContext
. (The ApplicationContext
should
be available to all interested components in the application. The
ResourceManager
has been created and initialized from the
application's configuration during the startup phase.) Actually, the
getResourceManager()
method is defined in the
TransformerContext
interface, which is extended by
ApplicationContext
. Because a TransformerContext
is passed to all validators and transformers
these objects have convenient access to resources, too.
For accessing a specific resource element a combined key consisting of two components has to be provided:
java.lang.Object
.
So there is literally no constraint for resource keys. In practice however,
most frequently strings will be used as keys. Of course, in addition to the
key of the resource element the desired Locale
must be passed in.
ResourceManager
defines two methods for obtaining single
resource elements:
Object getResource(Locale locale, Object group, Object key)
returns a resource element as plain object.String getText(Locale locale, Object group, Object key)
works like getResource()
, but performs a conversion of the
resource object to string.java.util.MissingResourceException
if the resource element cannot be resolved.
If most of the resources are contained in a single resource group, it is
cumbersome to always specify the resource group ID when calling the
getResource()
or getText()
methods.
ResourceManager
provides the setDefaultResourceGroup()
method. Here the ID of a resource group can be set. This ID is always used
if no group ID is provided when querying a resource element.
With the getResourceGroup()
method a whole
ResourceGroup
can be obtained at once. From the
ResourceGroup
the single elements it contains can be queried
later on. This can make sense if groups have a special meaning for the
logic of an application. For instance, resource groups could be used as a
kind of enumeration.
Applications often have the use case to obtain an element from the resources, insert some parameters, and finally display it to the user. An example can be messages for validation errors: A validator might check whether an input field contains a valid number in a configurable range. If not, an error message like Please enter a number between 0 and 100! should be generated, where the numbers are obtained from the validator's configuration.
For use cases like this the
Message
class can be used. A Message
instance stores
the ID of a resource element (consisting of a resource group ID and a
resource key) and an arbitrary number of parameters. Creating the instance
does not actually involve a resource look-up, rather the resource ID and
the parameters are stored. When the final text is needed the
resolve()
method has to be called. resolve()
expects the current ResourceManager
and the Locale
as parameters. It obtains the resource text from the
ResourceManager
, processes the parameters, and returns the
result.
Internally, Message
uses the java.text.MessageFormat
class for dealing with parameters. So the typical message format
patterns used by this class can be placed in the resources. For instance,
the error message of the numeric range validator could be defined in a
resource properties file as follows:
ERR_RANGE = Please enter a number between {0} and {1}!
Message
object two parameters for the
minimum and maximum value must be passed in.
Message msg = new Message("validationerrors", "ERR_RANGE", getMinimum(), getMaximum());
There are multiple constructors for different use cases. The easiest
constructor only expects a resource key and assumes that the default
resource group is used. Another constructor takes the ID of the resource
group, the resource key and an arbitrary number of parameter objects as a
vararg argument. Once created, Message
instances are
immutable and thus can be shared safely between multiple threads. By the
way: the default implementations of the resources interfaces shipped with
the library are all thread-safe.
Because GUI applications typically access resources frequently there are
some convenience methods to simplify this task. They are defined by the
ApplicationContext
interface, which plays an important role for
the whole application.
ApplicationContext
defines some overloaded getResource()
and getResourceText()
methods which are thin wrappers around
the corresponding methods of ResourceManager
. Because the
context already knows the current Locale
and has a reference
to the ResourceManager
, all the caller has to specify is the
ID of the resource to be obtained. This can be done in several ways:
Message
object: in this case the resource ID is stored in the
Message
object, which can also define additional parameters
to be inserted into the resource text,Message
object, it is evaluated as described above; otherwise the object is
interpreted as resource key, and the default resource group is assumed.
Another method of ApplicationContext
that is related to
resource handling is the messageBox()
method. As the name
implies, this method displays a typical message dialog, which can be used
for instance for error or confirmation messages. The title and the text
content of this dialog are also obtained from the application's resources.
Again, both can be specified as resource keys or as Message
objects. (Note: If the title or the text of the message box should
not be obtained from resources but specified as plain text, the
MessageOutput
object maintained by the ApplicationContext
can be used directly.)
JGUIraffe ships with an implementation of the
ResourceLoader
interface that obtains its data from the standard
resource bundles available in Java (i.e. the
java.util.ResourceBundle
class). This implementation is used
per default, so no additional configuration is needed. The implementation
class is
BundleResourceLoader
. Using this default implementation is
straight forward. The resource group names used by the
ResourceManager
are mapped directly to names of resource
bundles that are passed to the getBundle()
method of
java.util.ResourceBundle
. So resources can be created in the
usual way, e.g. as properties files.
As an example we create a very simple resource bundle using properties files.
testresources.properties
(shown below) contains the default
resource texts. (Note: when building the application the build process has
to ensure that this file is copied to the class path on the root level, i.e.
it must not be added to a specific package.)
# Test resources in English test1 = Hello test2 = OK test3 = Cancel test4 = Hello world!
Now texts for resource have been defined in English. For users in different
countries additional properties files can be created that contains the texts
in their specific language. The following listing shows a properties file
with German resource texts. This file must be stored under the name
testresources_de.properties
. Note that the language code is
appended to the name of the resource bundle. This has nothing to do with
the implementation provided by JGUIraffe, but is a requirement of
the java.util.ResourceBundle
class; please refer to the
documentation of this class for further information. Here is the content of
the German resource properties file:
# Test resources in German test1 = Guten Tag test2 = Fertig test3 = Abbrechen
Note that this file does not define the key test4
as does the
default version of the resource file. This is totally valid, you can choose
to provide translations for only a subset of the resource texts. If the
application requests the resource for the key test4
for the
German Locale
, ResourceBundle
finds out that this
key is not defined in the specific properties file with the German
translation; it then returns the value from the default properties file
(i.e. the English text Hello world! in this example).
Accessing these resource texts from a JGUIraffe application is very
easy: Just use the name of the resource bundle as resource group name. The
bundle name is testresources
, so we can write (provided that
the variable appCtx
refers to the ApplicationContext
object):
String msg = appCtx.getResourceText("testresources", "test1");
msg
should now contain the text
Hello. If the major part of our resource texts was stored in the
testresources
bundle, we could make this bundle to the
default resource group. Then we would only have to provide the resource key
when accessing resources and could ommit the resource group name. This can
be done either programmatically as in the following fragment;
appCtx.getResourceManager().setDefaultResourceGroup("testresources"); // now access to resources is simplified: String msg = appCtx.getResourceText("test1");
<config> <framework> <appctx> <locale>en</locale> <defaultResourceGroup>testresources</defaultResourceGroup> </appctx>
Locale
is set.