| ---- |
| Localization |
| ---- |
| |
| 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 |
| {{{http://www.loc.gov/standards/iso639-2/englangn.html}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: |
| |
| * Using the {{{parameters.html}message: binding prefix}} in a component template |
| |
| * By injecting the comopnent's Messages object |
| |
| [] |
| |
| 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 {{{inject.html}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 {{{conf.html}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 |
| {{{../../apidocs/org/apache/tapestry5/annotations/Meta.html}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. |
| |
| |
| |