Introduction to Rincl
In today's connected world, it is paramount that applications support languages, writing systems, and representation customs from around the globe. Yet until now there has been no easy way to add localized resource access to a Java program, similar to how SLF4J standardizes logging calls across different implementations. Rincl, the Resource I18n Concern Library, aims to fill the resource internationalization gap by providing a unifying API for resource lookup across frameworks, with plug-and-play implementations for your favorite resource storage. Rincl can be found at https://rincl.io/
.
Resource Internationalization
Internationalization (or simply I18n) is the broad term for the process of making your application aware of differences in its users around the world, from the language used in the user interface to the customs for displaying numbers; the number 123,456.789
as represented in the United States would be represented as 123.456,789
in Brazil, as just one example. One of the primary i18n tasks is to separate resources such a user-oriented messages into bundles that can be localized for independent access based regional user expectations.
Regional expectations in Java are grouped and identified by locale, or what the the Internet Engineering Task Force (IETF) refers to as a language tag in BCP 47. In most common use a language tag specifies a language
and an optional region
, so that pt
would specify the Portuguese language in general, while pt-BR
would specify the specific Portuguese dialects as spoken in Brazil. The class java.util.Locale
represents the language tag identifier, although the Java implementation prefers to use an underscore; for example pt_BR
.
ResourceBundle
For sure Java has provided the java.util.ResourceBundle
class from the very early days. ResourceBundle
in fact provides functionality that is invaluable, primarily in the elegant way in which storage files are named and discovered. Resource are stored in .properties
files with an ending specifying a particular locale. Thus a hierarchy of resource bundles may be provided, overriding specific strings only when needed. For example a call to ResourceBundle.getBundle("app-resources", new Locale("pt", "BR"))
would create a ResourceBundle
instance that provided access to strings stored in the following files, in order of priority:
app-resources_pt_BR.properties
- Words and phrases specific to Brazilian Portuguese.
app-resources_pt.properties
- Applications messages in the Portuguese language.
app-resources.properties
- Default application resources.
For example while app-resources_pt.properties
might define the resource key teacup
as "chávena", the app-resources_pt_BR.properties
file might override the teacup
key with the preferred Brazilian word, as shown in the figure.
Nevertheless adding localized access to resources in Java is not as simple as popping in a ResourceBundle
. A real-life application has to deal with class hierarchies and modularization through a network of dependencies, neither of which ResourceBundle
on its own addresses. Plain resource bundles have several shortcomings:
ResourceBundle.getBundle(…)
does not resolve up class and interface hierarchies.ResourceBundle
does not support properties files stored in anything but ISO-8859-1.ResourceBundle
does recognize XML-based properties files.- The
ResourceBundle
API is limited; it has no support for retrieving integer values, for example. - The
ResourceBundle
API does not provide for formatting message patterns. - Java provides no mechanism for multiple, compartmentalized locales on a single JVM (such as needed on a web server).
Rincl addresses all of these deficiencies while still working with the locale and resource bundle paradigm you're familiar with.
Class Hierarchies
The first shortcoming of ResourceBundle
is that most modern applications are object-oriented. There is a need to specify resources for a base class and let subclasses inherit those resources, override them, and/or add to them as necessary. Take for example a user interface that has several pages for user settings. The UserProfilePage
and UserAddressPage
may both extend from BaseUserSettingsPage
so that certain functionality can be consolidated. It would be desirable for the UserSettingsPage
to provide resources common to all subclasses, such as a label for the user name in the figure.
The Apache Wicket Internet application framework, which is based around hierarchical user interface components, has base class resource resolution built in. A call to UserProfilePage.getString("user-label")
in the above scenario automatically load resources from the correct UserProfilePage.properties
file (based upon the session locale), using the resource in the BaseUserSettingsPage.properties
as a default. (See Internationalization with Wicket - Reference Documentation.) The now defunct JSR 296: Swing Application Framework had a similar resource resolution mechanism. (See Using the Swing Application Framework.) Unfortunately using plain ResourceBundle
there is no simple way for UserProfilePage
to use resources defined in UserProfilePage.properties
, falling back to UserProfilePage.properties
by default.
Unified API
Once a ResourceBundle
instance is obtained, its interface for accessing resources leaves much to be desired. Resources of only two types are recognized: strings
and objects
. Furthermore ResourceBundle.getString(String key)
makes no allowance for using the resulting string as a formatting template, such as the pattern "Settings for user {0}"
in the example above.
The Swing Application Framework provided a rich set of resource access methods such as ResourceMap.getInteger(String key)
to accommodate types of resources other than strings. The Apache Wicket API provides ways to provide arguments for formatting strings that are stored in resources. But none of this helps the developer who isn't using one of these frameworks.
The developer who is attempting to create a library that could be used by one of these frameworks encounters a more perplexing dilemma: what resource access API should be used to allow the library to provide its own resources in a way compatible with other frameworks, yet allow its resources to be overridden in a standardized way? Existing frameworks do not provide a rich, unified i18n resource access API that can be used across dependencies.
Rincl Your Application
Rincl attempts to rectify all of these shortcomings by 1) providing a rich, unifying internationalization API via io.rincl.Resources
, and 2) providing pluggable resource storage implementations that make internationalization as easy as including a dependency. Similar to SLF4J, Rincl is distributed in two components: the main module io.rincl:rincl:x.x.x
comprising the main API and core logic; and a separate module that indicates the resource storage mechanism, such as io.rincl:rincl-resourcebundle:x.x.x
for accessing resources stored in resource bundles. Usually merely including the preferred implementation module will transitively include the main rincl
module, as well as automatically register the implementation module to handle Rincl requests.
The following example shows the few steps needed to add Rincl to your own application, accessing resources stores in resource bundles.
1. Include Rincl Dependency
The first step is to include the appropriate Rincl dependency. To support resource bundles, simply add io.rincl:rincl-resourcebundle:x.x.x
to your Maven POM. Check the Maven Central Repository for the the latest io.rincl
version.
2. Provide Resource Properties Files
Provide localized resources using properties files as you normally would for resource bundles. Feel free to consolidate resources into properties associated with base classes, such as with BaseUserSettingsPage
continued from the example in the previous section.
3. Implement Rincled
This step is optional, but it is the quickest and easiest way to resource access with Rincl. For each class which uses localized resources, simply implement the io.rincl.Rincled
interface. The following code continues the UserProfilePage
example from the sections above.
4. Access Resources
via the Rincl API
Implementing Rincled
allows easy acquisition of the central io.rincl.Resources
interface for requesting resources. A wide variety of resource types may be retrieved, including Resources.getString(String key)
, Resources.getInteger(String key)
, and Resources.getUri(String key)
. String pattern substitution using MessageFormat
is built into Rincl, as shown in the following example.
Configuring Rincl with Csar
The power of Rincl's unified Resources
lookup mechanism is evident in the flexibility it provides when used across dependencies. The main coordinator is io.rincl.Rincl
; the Rincled
interface is but a convenience mixin to retrieve Resources
via the the Rincl.getResources(Class<?> contextClass)
method. Under the covers Rincl uses a small library named Csar (pronounced /zɑːr/) for making configurations accessible throughout the codebase without the need for global static singletons. When resources are needed, Rincl asks Csar for the registered io.rincl.ResourceI18nConcern
, which in turn produces the actual Resources
.
Rincled Resources across Dependencies
The result is that for all libraries requesting Resources
either directly through Rincl or indirectly through Csar, resource configuration can be performed independently of the dependencies themselves. Consider a report generation library Reporter
(in the com.example.reporter
package) that by default includes the word Report
at the top of generated documents. This default title is stored in the library distribution Jar in a .properties
file under the resource key report-default-title
. To change this title, an application could provide a custom properties file in the com.example.reporter
package, in a location in the classpath that takes priority over the one in the Reporter
distribution Jar. The Reporter
library contents would not need to be changed.
Alternatively Rincl provides the application the option of bypassing Reporter's .properties
files altogether. The application could create its own custom implementation of Resources
(e.g. AppDbResources
) that would look up resources such as report-default-title
from another source altogether, such as the application database. To effect the switch, the application would install a custom ResourceI18nConcern (e.g. AppDbResourceI18nConcern
) as the application-wide default:
All resources retrieved through Rincl, whether in the application or in dependencies, would now be retrieved from the application database.
Compartmentalized Internationalization
Another benefit of using Csar is that it allows for compartmentalized configurations for different code contexts on the same JVM. Imagine that the application's user, for whatever reason, has decided that all reports should be generated in French, even though the main application's user interface is set to English. Traditionally the report generation library would use one of the Locale.getDefault()
methods that retrieves the JVM-wide default locale. If the library were instead to use Rincl for resource access, however, the hosting application could use Csar to provide a specially configured ResourceI18nConcern
just for report-generation.
In the example below, the Csar.run(Concern concern, Runnable runnable)
method is used to run Reporter.generateReport()
in a separate thread but in the context of a ResourceBundleResourceI18nConcern
configured to provide French resources. The java.lang.Thread.join()
invocation is optional, and provides a way to wait until the reporting operation has finished before continuing.
The Initial Rincl
Until now there has been no unifying API for resource access. While the resource bundle mechanism provided by Java has innovative features, by itself it is inadequate for modern, highly modularized applications. Rincl straightens out the shortcomings of resource bundles. It even addressing the irony that Java's native file format for internationalization was restricted to ISO-8859-1: Rincl supports additional properties file charsets UTF-8, UTF-16, and UTF-32. With the addition of pluggable resource storage implementations and a flexible configuration approach based on Csar, using Rincl means that internationalizing an application should feel natural. Perhaps now that the mechanism of internationalization is no longer a hardship, you'll come to enjoy adding an extra internationalization Rincl to your application.
By Garret Wilson. Copyright © 2016 GlobalMentor, Inc. All Rights Reserved. Content may not be published or reproduced by any means for any purpose besides Rincl education without permission.