Easy Resource Internationalization With Rincl

DZone 's Guide to

Easy Resource Internationalization With Rincl

Garret Wilson shows how to use Rincl, the Resource I18n Concern Library, which provides a unifying API to fill the resource internalization gap.

· Java Zone ·
Free Resource

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 http://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.


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:

Words and phrases specific to Brazilian Portuguese.
Applications messages in the Portuguese language.
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

user-label=Settings for user {0}.

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 when 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.

user-label=Settings for user {0}.
user-label=Definições para usuário {0}.

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.

public class UserProfilePage extends BaseSettingsPage implements Rincled {

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.

public class UserProfilePage extends BaseSettingsPage implements Rincled {

    final String userName = "Jane Doe";
    //Retrieve the formatted user label based upon the current locale.
    //en-US: "Settings for user Jane Doe."
    //pt-BR: "Definições para usuário Jane Doe."
    String userLabel = getResources().getString("user-label", userName);

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:

Setting the application-wide default ResourceI18nConcern.
Rincl.setDefaultResourceI18nConcern(new AppDbResourceI18nConcern());

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 ResourceI18nConcernjust 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.

Using Csar to configure a specialized ResourceI18nConcern for a single thread.
ResourceBundleResourceI18nConcern reportI18n = new ResourceBundleResourceI18nConcern();
Csar.run(resortI18n, Reporter::generateReport).join();

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 addresses 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.

applications, internationalization, java, resource bundles

Published at DZone with permission of Garret Wilson . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}