tapestry-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Tapestry > Localization
Date Mon, 21 Jan 2013 18:01:00 GMT
    <base href="https://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/2042/9/12/_/styles/combined.css?spaceKey=TAPESTRY&amp;forWysiwyg=true"
<body style="background: white;" bgcolor="white" class="email-body">
<div id="pageContent">
<div id="notificationFormat">
<div class="wiki-content">
<div class="email">
    <h2><a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Localization">Localization</a></h2>
    <h4>Page <b>edited</b> by             <a href="https://cwiki.apache.org/confluence/display/~bobharner">Bob
                         <h4>Changes (1)</h4>
<div id="page-diffs">
                    <table class="diff" cellpadding="0" cellspacing="0">
            <tr><td class="diff-unchanged" >h1. Localization <br> <br></td></tr>
            <tr><td class="diff-added-lines" style="background-color: #dfd;">{float:right|background=#eee}
<br>{contentbylabel:title=Related Articles|showLabels=false|showSpace=false|space=@self|labels=component-templates,localization}
<br>{float}  <br> <br></td></tr>
            <tr><td class="diff-unchanged" >Localization is all about getting
the right text to the user, in the right language. <br> <br></td></tr>
            <tr><td class="diff-snipped" >...<br></td></tr>
    </div>                            <h4>Full Content</h4>
                    <div class="notificationGreySide">
        <h1><a name="Localization-Localization"></a>Localization</h1>

<div class='navmenu' style='float:right; background:#eee; margin:3px; padding:3px'><table
class="tableview" width="100%">
            <tr><th style="padding: 3px 3px 3px 0px">Related Articles</th></tr>
                                 <span class="icon icon-page" title=Page>Page:</span>
                         <a href="/confluence/display/TAPESTRY/Templating+and+Markup+FAQ">Templating
and Markup FAQ</a>
                                 <span class="icon icon-page" title=Page>Page:</span>
                         <a href="/confluence/display/TAPESTRY/Component+Classes">Component
                                 <span class="icon icon-page" title=Page>Page:</span>
                         <a href="/confluence/display/TAPESTRY/Component+Parameters">Component

<p>Localization is all about getting the right text to the user, in the right language.</p>

<p>Localization support is well integrated into Tapestry. Tapestry allows you to easily
separate 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 everything together.</p>

<h2><a name="Localization-ComponentMessageCatalogs"></a>Component Message

<p>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 <tt>key=value</tt>. These files
are stored on the classpath, in the same package folder as the page or component's compiled
Java class.</p>

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

<p>If you have a translations of these values, you provide additional properties file,
adding an <a href="http://www.loc.gov/standards/iso639-2/englangn.html" class="external-link"
rel="nofollow">ISO language code</a> before the extension. Thus, if you have a French
translation, you could create a file <tt>MyPage_fr.properties</tt>.</p>

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

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

<h2><a name="Localization-PropertiesFileCharset"></a>Properties File Charset</h2>

<p>Tapestry uses the <tt>UTF-8</tt> charset when reading the properties
files in a message catalog. This means that you don't have to use the Java <tt>native2ascii</tt>
tool. Make sure that your properties files don't contain <a href="http://en.wikipedia.org/wiki/Byte_order_mark"
class="external-link" rel="nofollow">byte order marks (BOM)</a> as Java - and thus
Tapestry - doesn't support BOM in properties files (see <a href="http://bugs.sun.com/view_bug.do?bug_id=4508058"
class="external-link" rel="nofollow">http://bugs.sun.com/view_bug.do?bug_id=4508058</a>).
Some editors write them out when saving a file in UTF-8, so watch out.</p>

<h3><a name="Localization-MessageCatalogInheritance"></a>Message Catalog

<p>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.</p>

<p>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.</p>

<h2><a name="Localization-ApplicationMessageCatalog"></a>Application Message

<p>If the file <tt>WEB-INF/</tt><em>AppName</em><tt>.properties</tt>
exists in the context, it will be used as an application-wide message catalog. The <em>AppName</em>
is derived from the name of the filter inside the web.xml file; this is most often just "app",
thus <tt>WEB-INF/app.properties</tt>. The search for the file is case sensitive.
The properties files may be localized.</p>

<p>Individual pages and components can override the values defined in the message catalog.</p>

<h2><a name="Localization-LocalizedComponentTemplates"></a>Localized Component

<p>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
<tt>MyPage_fr.html</tt> for French users, and <tt>MyPage.html</tt>
for all other users.</p>

<h2><a name="Localization-AccessingLocalizedMessages"></a>Accessing Localized

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

<p>Messages can be accessed in one of two ways:</p>

	<li>Using the "message:" <a href="/confluence/display/TAPESTRY/Component+Parameters"
title="Component Parameters">binding expression</a> in a component template</li>
	<li>By injecting the component's Messages object<br/>
In the first case, you may use the message: binding prefix with component parameters, or with
template expansions:</li>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
&lt;t:layout title=<span class="code-quote">"message:page-title"</span>&gt;

  ${message:greeting}, ${user.name}!
  . . .

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

<p>In addition, the <tt>greeting</tt> message is extracted and written into
the response as part of the template.</p>

<p>As usual, "prop:" is the default binding prefix, thus <tt>user.name</tt>
is a property path, not a message key.</p>

<p>You would extend this with a set of properties files:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
page-title=Your Account
greeting=Welcome back

<p>Or, perhaps, a French version:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
page-title=Votre Compte
greeting=Bienvenue en arriere

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

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
  <span class="code-keyword">private</span> Messages messages;

<p>You could then <tt>get()</tt> messages, or <tt>format()</tt>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">

  <span class="code-keyword">public</span> <span class="code-object">String</span>
    <span class="code-keyword">if</span> (items.isEmpty())
      <span class="code-keyword">return</span> messages.get(<span class="code-quote">"no-items"</span>);
    <span class="code-keyword">return</span> messages.format(<span class="code-quote">"item-summary"</span>,

<p>The format() option works using a <tt>java.util.Formatter</tt>, with
all the printf-style loveliness you've come to expect:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
no-items=Your shopping cart is empty.     
item-summary=You have %d items in your cart.

<p>As easy as conditionals are to use inside a Tapestry template, sometimes it's even
easier to do it in Java code.</p>

<h2><a name="Localization-MissingKeys"></a>Missing Keys</h2>

<p>If you reference a key that is not in the message catalog, Tapestry does not throw
an exception (because 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:

<h2><a name="Localization-Reloading"></a>Reloading</h2>

<p>If you change a property file in a message catalog, you'll see the change immediately,
just as with component classes and component templates (provided you're not running in <a

<h2><a name="Localization-AssetLocalization"></a>Asset Localization</h2>

<p>When <a href="/confluence/display/TAPESTRY/Injection" title="Injection">injecting
assets</a>, 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.</p>

<h2><a name="Localization-LocaleSelection"></a>Locale Selection</h2>

<p>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).</p>

<p>Tapestry "narrows" the raw request locale, as specified in the request, to a known
quantity. It uses the <a href="/confluence/display/TAPESTRY/Configuration" title="Configuration">configuration
symbol</a> <tt>tapestry.supported-locales</tt> 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 (that is, the first locale is used as the default for non-matching
requests). Thus a site that primarily caters to French speakers would want to list "fr" as
the first locale in the list.</p>

<h2><a name="Localization-ChangingtheLocale"></a>Changing the Locale</h2>

<p>The <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/PersistentLocale.html"
class="external-link" rel="nofollow">PersistentLocale service</a> can be used to
programmatically override the locale. Note: You should be careful to only set the persistent
locale to a supported locale.</p>

<div class="code panel" style="border-width: 1px;"><div class="codeHeader panelHeader"
style="border-bottom-width: 1px;"><b>Toggle between English and German</b></div><div
class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">private</span> PersistentLocale persistentLocale;

void onActionFromLocaleToggle() {
    <span class="code-keyword">if</span> (<span class="code-quote">"en"</span>.equalsIgnoreCase(persistentLocale.get().getLanguage()))
        persistentLocale.set(<span class="code-keyword">new</span> Locale(<span
    } <span class="code-keyword">else</span> {
        persistentLocale.set(<span class="code-keyword">new</span> Locale(<span
    <span class="code-keyword">return</span> <span class="code-keyword">this</span>;
<span class="code-keyword">public</span> <span class="code-object">String</span>
getDisplayLanguage() {
    <span class="code-keyword">return</span> persistentLocale.get().getDisplayLanguage();

<p>Once a persistent locale is set, you will see the locale name as the first virtual
folder in page render and component event requests URLs. In this way, a persistent locale
will, in fact, persist from request to request, or in a user's bookmarks.</p>

<p>You will see the new locale take effect on the next request. If it is changed in
a component event request (which is typical), the new locale will be used in the subsequent
page render request.</p>

<p>Note that the locale for a page is fixed (it can't change once the page instance
is created). In addition, a page may only be attached to a request once. In other words, if
code in your page changes the persistent locale, you won't see a change to the page's locale
(or localized messages) <em>in that request</em>.</p>

<h2><a name="Localization-BuiltinLocales"></a>Built-in Locales</h2>

<p>While your application can support any locale (and thus any language) that you want,
Tapestry provides only a limited set of translations for its own built-in messages. As of
Tapestry 5.3, the following locales have translations provided:</p>

<div class='table-wrap'>
<table class='confluenceTable'><tbody>
<td class='confluenceTd'> en (English)    </td>
<td class='confluenceTd'> es (Spanish)   </td>
<td class='confluenceTd'> ja (Japanese)    </td>
<td class='confluenceTd'> ru (Russian)</td>
<td class='confluenceTd'> bg (Bulgarian)  </td>
<td class='confluenceTd'> fi (Finnish)   </td>
<td class='confluenceTd'> mk (Macedonian)  </td>
<td class='confluenceTd'> sr (Serbian)</td>
<td class='confluenceTd'> da (Danish)     </td>
<td class='confluenceTd'> fr (French)    </td>
<td class='confluenceTd'> nl (Dutch)       </td>
<td class='confluenceTd'> sv (Swedish)</td>
<td class='confluenceTd'> de (German)     </td>
<td class='confluenceTd'> hr (Croatian)  </td>
<td class='confluenceTd'> no (Norwegian)   </td>
<td class='confluenceTd'> zh (Chinese)</td>
<td class='confluenceTd'> el (Greek)      </td>
<td class='confluenceTd'> it (Italian)   </td>
<td class='confluenceTd'> pt (Portuguese)  </td>

<h3><a name="Localization-ProvidingtranslationsforTapestrybuiltinmessages"></a>Providing
translations for Tapestry built-in messages</h3>

<p>Fortunately, Tapestry uses all the same mechanisms for its own locale support as
it provides for your application. So, to support other locales, just translate the built-in
message catalog (property) files yourself:</p>

<style type="text/css">table.sectionMacro { width: auto; }</style>

<table class="sectionMacro" border="0" cellpadding="5" cellspacing="0" width="100%"><tbody><tr>
<td class="confluenceTd" valign="top">
<div class='table-wrap'>
<table class='confluenceTable'><tbody>
<th class='confluenceTh'> Tapestry 5.4 and later </th>
<td class='confluenceTd'> <a href="https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=blob;f=tapestry-core/src/main/resources/org/apache/tapestry5/core.properties;hb=HEAD"
class="external-link" rel="nofollow">core.properties</a> </td>
<td class='confluenceTd'> <a href="https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=blob;f=tapestry-kaptcha/src/main/resources/org/apache/tapestry5/kaptcha/tapestry-kaptcha.properties;hb=HEAD"
class="external-link" rel="nofollow">tapestry-kaptcha.properties</a> </td>
<td class="confluenceTd" valign="top">
<div class='table-wrap'>
<table class='confluenceTable'><tbody>
<th class='confluenceTh'> Tapestry 5.3.x </th>
<td class='confluenceTd'> <a href="http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/BeanEditForm.properties?view=markup"
class="external-link" rel="nofollow">BeanEditForm.properties</a> </td>
<td class='confluenceTd'> <a href="http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/DateField.properties?view=markup"
class="external-link" rel="nofollow">DateField.properties</a> </td>
<td class='confluenceTd'> <a href="http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Errors.properties?view=markup"
class="external-link" rel="nofollow">Errors.properties</a> </td>
<td class='confluenceTd'> <a href="http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/GridColumns.properties?view=markup"
class="external-link" rel="nofollow">GridColumns.properties</a> </td>
<td class='confluenceTd'> <a href="http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/GridPager.properties?view=markup"
class="external-link" rel="nofollow">GridPager.properties</a> </td>
<td class='confluenceTd'> <a href="http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/components/Palette.properties?view=markup"
class="external-link" rel="nofollow">Palette.properties</a> </td>
<td class='confluenceTd'> <a href="http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-core/src/main/resources/org/apache/tapestry5/internal/ValidationMessages.properties?view=markup"
class="external-link" rel="nofollow">ValidationMessages.properties</a> </td>
<td class='confluenceTd'> <a href="http://svn.apache.org/viewvc/tapestry/tapestry5/branches/5.3/tapestry-kaptcha/src/main/resources/org/apache/tapestry5/kaptcha/tapestry-kaptcha.properties?view=markup"
class="external-link" rel="nofollow">tapestry-kaptcha.properties</a> </td>

<p>To have Tapestry use these new files, just put them in the corresponding package-named
directory within your own app (for example, src/main/resources/org/apache/tapestry5/core.properties).</p>

<p>Finally, please open a new feature request <a href="https://issues.apache.org/jira/browse/TAP5"
class="external-link" rel="nofollow">here</a> and attach the translated files so
that they can be included in the next release of Tapestry.</p>

<div class='panelMacro'><table class='infoMacro'><colgroup><col width='24'><col></colgroup><tr><td
valign='top'><img src="/confluence/images/icons/emoticons/information.gif" width="16"
height="16" align="absmiddle" alt="" border="0"></td><td>Please note that a
patch is always preferred over an archive of properties files.</td></tr></table></div>

        <div id="commentsSection" class="wiki-content pageSection">
        <div style="float: right;">
            <a href="https://cwiki.apache.org/confluence/users/viewnotifications.action"
class="grey">Change Notification Preferences</a>
        <a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Localization">View
        <a href="https://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=22872114&revisedVersion=12&originalVersion=11">View

View raw message