blob: 92b3eecf600bd6260478600ec75d90233a0036ca [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2005 The Apache Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<document>
<properties>
<title>Localization</title>
</properties>
<body>
<section name="Localization">
<p>
Proper localization is a pervasive aspect of web application development. Supporting
users from different countries, with different languages, can be a tricky
proposition ... it is more than just text that must be localized, but more subtle
aspects of the application such as date and currency formats. It is also more than
text ... in some cases, a localized application will want to change images or even
color schemes.
</p>
<p>Localization support in Tapestry is likewise pervasive.</p>
<subsection name="Component Message Catalogs">
<p>
The most fundamental aspect of localization in Tapestry are component message
catalogs (remember that pages are components too). A message catalog is a
mapping from a logical key (that may appear in Java code or in OGNL expressions)
to a literal string. Tapestry message catalogs are similar to Java's
ResourceBundle class, except there is more flexibility in the character set of
the files, and the location of the files.
</p>
<p>
Each component
<em>may</em>
have a message catalog, consisting of a set of localized message properties
files.
</p>
<p>
These files are stored with the page or component specification file. They are
named the same as the specification file, but with a different extension
(".properties" instead of ".jwc" or ".page").
</p>
<p>
In addition, this is a
<em>set</em>
of files; a locale string may be inserted just before the extension. For
example,
<code>WEB-INF/Home_fr.properties</code>
to contain the French language localization of the keys.
</p>
<p>
As with Java's ResourceBundle, resolution of a key to a message starts with the
most
<em>specific</em>
properties file. Any key not found there will be searched for in less specific
files. For example, the search path could be
<code>Home_fr_BE.properties</code>
,
<code>Home_fr.properties</code>
,
<code>Home.properties</code>
.
</p>
<p>
If a properties file does not exist, that's perfectly ok, the search will
continue.
</p>
<p>
When a key can not be found even in the most general properties file, a search
occurs in the
<a href="#localization.namespace">namespace</a>
. In this way, very common strings can be stored and localized once, and used
throughout a library or application.
</p>
<p>
We'll describe how to use the message catalog shortly, but first some notes on
how the message catalogs are read.
</p>
<subsection name="Properties file encoding">
<p>
For Java's ResourceBundle, the properties files must be in UTF-8 character
set. This can be problematic, as in non-western languages it is necessary to
use Java's native2ascii tool to convert from non-native files into an ASCII
encoding of UTF-8.
</p>
<p>
Tapestry can read properties files in alternate character sets, but must be
told what character set the file is encoded in (internally, the contents
must be converted into standard multi-byte Unicode).
</p>
<p>
This is accomplished by providing some metadata inside the component (or
page) specification. Metadata is specified using the
<a href="spec.html#spec.meta">&lt;meta&gt;</a>
element.
</p>
<p>
The resolution of the character set is somewhat complicated; it is possible
that each properties file will use a different character set. At the same
time, repetition is bad ... therefore it is possible to specify some of this
information in the namespace meta data (in the containing application or
library specification) so that it can apply to all pages and components
within the namespace.
</p>
<p>
The basic meta-data property name searched for is
<code>org.apache.tapestry.messages-encoding</code>
. The value for this name is the name of the charset for the properties
file.
</p>
<p>
However, the base name is modified to reflect the locale for the file being
read; the locale string is appended to the key, thus
<code>org.apache.tapestry.messages-encoding_fr</code>
will define the character set for the file
<code>WEB-INF/Home_fr.properties</code>
</p>
<p>
For each localization of the base property name, a search of the following
locations takes place.
</p>
<ul>
<li>The page or component specification.</li>
<li>
The namespace (library or application) specification for the namespace
containing the page or component.
</li>
<li>
The
<a href="configuration.html#configuration.global-property-source">
global property source
</a>
.
</li>
</ul>
<p>
Because localization of templates is similar to localization of message
properties files, a second search occurs if the search for (variations of)
<code>org.apache.tapestry.messages-encoding</code>
fails; this time for
<code>org.apache.tapestry.template-encoding</code>
occurs (again, with variations for each locale).
</p>
<p>
The ultimate default for encoding character set is ISO-8859-1; in other
words, the same behavior as reading an ordinary Java ResourceBundle.
</p>
</subsection>
</subsection>
<subsection name="Missing keys">
<p>
While developing, you may occasionally reference a key that does not exist.
Rather than fail with an exception, Tapestry will fabricate a missing key value.
This is the key, converted to upper-case, and surrounded with brackets. For
example,
<code>[A-MISSING-KEY]</code>
. This allows missing key values to stand out an demand to be fixed, without
completely subverting your application.
</p>
</subsection><!-- localization.missing-keys -->
<subsection name="Namespace message catalogs">
<p>
It is very likely that you'll have a number of strings that are used, and
re-used, throughout your application. Rather than duplicate the same message
keys and localized values in all your page and component message catalogs, you
can put these into your
<em>namespace</em>
catalog.
</p>
<p>
Each page and component is part of a
<code>namespace</code>
, identified by a library specification or component specification.
</p>
<p>
The specification may also have a message catalog; for instance, for
<code>WEB-INF/myapp.application</code>
, the files would be named
<code>WEB-INF/myapp.properties</code>
, etc. Again, the name of the file is based on the servlet name ("myapp").
</p>
<p>
Very simple applications may not have an application specification, but may
still have properties, just as if the application specification existed.
</p>
</subsection><!-- localization.namespace -->
<!-- localization.component-catalog -->
<subsection name="Template text localization">
<p>
As described in
<a href="template.html#template.directives.l10n">
the discussion of Tapestry templates
</a>
, static text in an HTML template can be enclosed in a specialized &lt;span&gt;
tag.
</p>
</subsection>
<subsection name="Localized templates">
<p>
In some cases, the entire layout of a page (or component) must change due to
locale. For example, because of differences between western languages (which
read left to right) and many eastern languages (which read right to left).
</p>
<p>
In this case, it is possible to have multiple HTML templates. If a localized
template (e.g.,
<code>Home_jp.html</code>
for a Japanese locale) exists, it will be used as appropriate.
</p>
<p>
Page and component
<em>specifications</em>
are never localized, just
<em>templates</em>
.
</p>
<p>
It is a good idea to make use of declared components, rather than implicit
components, when using localized templates ... it reduces duplication in the
templates.
</p>
</subsection>
<subsection name="Template encoding">
<p>
Like
<a href="#localization.component-catalog.encoding">message catalogs</a>
, each template may be written in a different character set.
</p>
<p>
For each localization of the base key (
<code>org.apache.tapestry.template-encoding</code>
, a search of the following locations takes place.
</p>
<ul>
<li>The page or component specification.</li>
<li>
The namespace specification for the namespace containing the page or
component.
</li>
<li>
The
<a href="configuration.html#configuration.search-path">
application property search path
</a>
</li>
</ul>
</subsection>
<subsection name="Using the message: binding prefix">
<p>
When specifying a parameter binding, the
<code>message:</code>
prefix is used to reference a localized message key. For example:
</p>
<source xml:space="preserve">
&lt;html jwcid="@<a href="../components/general/shell.html">Shell</a>" title="message:page-title"&gt;
. . .
&lt;/html&gt;
</source>
</subsection>
<subsection name="Localization of Assets">
<p>
Assets may also be localized. Classpath and context assets will automatically
search for a locale-specific match (this is very similar to how localized
templates work).
</p>
</subsection><!-- localization.assets -->
<subsection name="Formatting messages">
<p>
Messages may contain
<em>arguments</em>
, strings of the form
<code>{0}</code>
(or some other number). The argument are handled exactly the same as with Java's
MessageFormat class (in fact, under the covers, MessageFormat does the work).
</p>
<p>
Components include a
<code>messages</code>
property for accessing localized messages. This property is of type Messages,
and includes two methods:
</p>
<ul>
<li>
<code>getMessage()</code>
takes a string parameter and returns a localized message
</li>
<li>
<code>format()</code>
takes a string parameter (the key) and then takes a number of additional
parameters as arguments. The arguments are just objects. If you have more
than three arguments, then specify them as an object array.
</li>
</ul>
<p>It is common to format messages using OGNL expessions, i.e.:</p>
<source xml:space="preserve">
&lt;span jwcid="@<a href="../components/general/insert.html">Insert</a>" value="ognl:messages.format('billing-info', amountDue)"/&gt;
</source>
<p>
The above example would get the amountDue property and pass it in as argument 0
to the message format retrieved from the message catalog as key 'billing-info'.
</p>
</subsection>
<subsection name="Changing the locale">
<p>
In order to change the locale, you must obtain the
<a href="../apidocs/org/apache/tapestry/IEngine.html">
IEngine
</a>
and invoke
<code>setLocale()</code>
on it. This will change the value stored in the engine (which is used when
loading new pages), and:
</p>
<ul>
<li>
Update the hivemind.ThreadLocale service, allowing localized messages from
services to be generated in the correct locale
</li>
<li>
Cause an HTTP Cookie to be added to the request so that future requests from
the same client will be in the same locale
</li>
</ul>
<p>
Changing the locale
<a href="#localization.engine-locale">
does not affect any pages loaded in the current request.
</a>
</p>
</subsection>
<subsection name="Engine locale vs. page locale">
<p>
When pages are created, or obtained from the page pool, the engine's locale is
taken into account. Pages are obtained when they are used by a service, or when
accessed via
<a
href="../apidocs/org/apache/tapestry/IRequestCycle.html">
IRequestCycle
</a>
.getPage().
</p>
<p>
A page is loaded for a particular locale, and the page's locale never changes.
This is because of the degree to which localization creeps into the properties
of the page and the components within the page.
</p>
<p>
Additionally, once a page is loaded during a request cycle, it is kept for the
duration of the cycle ... even if the engine locale changes.
</p>
<p>
If you have a listener method on a page that changes the engine's locale, it is
necessary to activate a
<em>different</em>
page to render the response. This new page will be loaded in the new locale.
</p>
<span class="info">
<strong>Note:</strong>
<p>
This may be addressed somewhat in Tapestry 4.0. Two options are possible: a
service for changing the locale before rendering a page, and a way to force
Tapestry to re-load a page, in a new locale.
</p>
</span>
</subsection>
<subsection name="Limiting accepted locales">
<p>
By default, Tapestry accepts incoming locales (as specified in the request HTTP
header) as-is. The requested locale is used as-is. This has some implications,
primarily in terms of resource usage.
</p>
<p>
Imagine an application that is being accessed by users in the US, the UK and in
Canada. The incoming request locales will be "en_US", "en_UK" and "en_CA"
(respectively). However, it is likely that you will only have created a single
localization, for English in general (locale "en"). Despite this, there will be
several different versions of each page in the page pool: one for each of the
above locales, even though they will be functionally identical.
</p>
<p>
Ideally, what we want is to limit incoming requests so that all of the listed
locales ("en_US", "en_UK" and "en_CA") will be 'filtered down' to just "en".
</p>
<p>
That functionality is controlled by the org.apache.tapestry.accepted-locales
<a href="configuration.html#configuration.properties">configuration property</a>
. By setting this property to a comma-seperated list of local names, incoming
requests will be converted to the closest match. For example, the the property
could be configured to "en,fr,de" to support English, French and German.
</p>
<p>
Matching takes place by stripping off "terms" (the locale variant, then the
locale country code) from the locale name. So "en_US" would be stripped to "en"
(which would match). When no match can be found, the
<em>first</em>
locale in the list is treated as the default. In the prior example, Russian
users would be matched to the "en" locale.
</p>
</subsection>
</section>
</body>
</document>