Localization

Localization is all about getting the right text to the user, in the right language.

Localization support is well integrated into Tapestry. Tapestry allows you to easily seperate the text you present to your users from the rest of your application ... pull it out of your Java code and even out of your component templates. You can then translate your messages into other languages and let Tapestry put everthing together.

Component Message Catalogs

Each component class may have a component message catalog. A component message catalog is a set of files with the extension ".properties". These property files are the same format used by java.util.ResourceBundle, just lines of key=value. These files are packaged with the component's HTML template.

So for a class named org.example.myapp.pages.MyPage, you would have a main properties file as org/example/myapp/pages/MyPage.properties.

If you have a translations of these values, you provide additional properties file, adding an ISO language code before the extension. Thus, if you have a French translation, you could create a file MyPage_fr.properties.

Any values in the more language specific file will override values from the main properties file. If you had an even more specific localization for just French as spoken in France, you could create MyPage_fr_FR.properties (thats a language code plus a country code, you can even go further and add variants ... but its unlikely that you'll ever need to go beyond just language codes in practice).

The messages in the catalog are accessed by keys. Tapestry ignores the case of the keys when accessing messages in the catalog.

Message Catalog Inheritance

If a component class is a subclass of another component class, then it inherits that base class' message catalog. Its own message catalog extends and overrides the values inherited from the base class.

In this way, you could have a base component class that contained common messages, and extend or override those messages in subclasses (just as you would extend or override the methods of the base component class). This, of course, works for as many levels of inheritance as you care to support.

Application Message Catalog

If the file WEB-INF/AppName.properties exists in the context, it will be used as an application-wide message catalog. The AppName is derived from the name of the filter inside the web.xml file. The search for the file is case sensitive. The properties file may be localized.

Individual pages and components can override the values defined in this message catalog.

Localized Component Templates

The same lookup mechanism applies to component templates. Tapestry will search for a localized version of each component template and use the closest match. Thus you could have MyPage_fr.html for French users, and MyPage.html for all other users.

Accessing Localized Messages

The above discusses what files to create and where to store them, but doesn't address how to make use of that information.

Messages can be accessed in one of two ways:

In the first case, you may use the message: binding prefix with component parameters, or with template expansions:

<t:layout title="message:page-title">

  ${message:greeting}, ${user.name}!
  
  . . .
</t:layout>

Here, the page-title message is extracted from the catalog and passed to the Border component's title parameter.

In addition, the greeting message is extracted and written into the response as part of the template.

As usual, "prop:" is the defalt binding prefix, thus user.name is a property path, not a message key.

You would extend this with a set of properties files:

page-title=Your Account
greeting=Welcome back

Or, perhaps, a French version:

page-title=Votre Compte
greeting=Bienvenue en arriere

Programatically, you may inject your component message catalog into your class, as an instance of the Messages interface:

  @Inject
  private Messages _messages;

You could then get() messages, or format() them:

  public String getCartSummary()     
  {
    if (_items.isEmpty())
      return _messages.get("no-items");
      
    return _messages.format("item-summary", _items.size());
  }

The format() option works using a java.text.Formatter, with all the printf-style loveliness you've come to expect:

no-items=Your shopping cart is empty.     
item-summary=You have %d items in your cart.

As easy as conditionals are to do inside a Tapestry template, sometimes its even easier to do it in Java code.

Missing Keys

If you reference a key that is not in the message catalog, Tapestry does not throw an exception (that would make initially developing an application very frustrating). When a key can not be located, a "placeholder" message is generated, such as "[[missing key: key-not-found]]".

Reloading

If you change a property file in a message catalog, you'll see the change immediately, just as with component classes and component templates.

Asset Localization

When injecting assets, the injected asset will be localized as well. A search for the closest match for the active locale is made, and the final Asset will reflect that.

Locale Selection

The locale for each request is determined from the HTTP request headers. The request locale reflects the environment of the web browser and possibly even the keyboard selection of the user on the client. It can be highly specific, for example, identifying British English (as en_GB) vs. American English (en).

Tapestry "narrows" the raw request locale, as specified in the request, to a known quantity. It uses the configuration symbol tapestry.supported-locales to choose the effective locale for each request. This value is a comma-separated list of locale names. Tapestry searches the list for the best match for the request locale; for example, a request locale of "fr_FR" would match "fr" but not "de". If no match is found, then the first locale name in the list is used as the effective locale (it is used as the default for non-matches). Thus a site that primarily caters to French speakers would want to list "fr" as the first locale in the list.

Changing the Locale

Tapestry does not yet support changing the locale, but that will be available shortly. The intent is to mimic Tapestry 4 behavior: store a cookie on the client to provide the default for the locale on the next visit, and store a locale name in the session (if a session exists). TODO: I believe this has been implemented by Kent.

Output Content Type and Charset

When Tapestry renders a page, the very first step is to determine the output content type and charset.

This information is obtained from meta data on the page itself. Meta data is specified using the Meta annotation.

First, the response content type is obtained via meta-data key "tapestry.response-content-type". This value defaults to "text/html".

Next, the encoding is obtained via meta-data key "tapestry.response-encoding". This value defaults to "UTF-8". This is only necessary if the content type does not provide a charset parameter (i.e., "text/html;charset=ISO-8559-1"). The encoding becomes the charset.