<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like
We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">
<h3>Localization By Template</h3>
This page demonstrates localization by "localized component template".
There are two templates for this page:
<ul>
<li><strong>LocalizationByMessageCatalog_fr.tml</strong> provides a French page (actually, only parts of it are in French).</li>
<li><strong>LocalizationByMessageCatalog.tml</strong> provides a default page, which is English.</li>
</ul>
<div class="eg">
The current template is the second one. Using the current locale, we get:
<dl class="dl-horizontal well">
<dt>Locale code</dt>
<dd>${currentLocale.toString()}</dd>
<dt>Locale name</dt>
<dd>${currentLocale.getDisplayName()}</dd>
<dt>A date</dt>
<dd><t:output value="date" format="prop:dateFormat"/></dd>
<dt>A number</dt>
<dd><t:output value="number" format="prop:numberFormat"/></dd>
</dl>
The supported locales are defined in AppModule. Our AppModule has ${supportedLocales.size()}.<br/><br/>
Choose one:
<t:loop source="supportedLocales" value="supportedLocale" index="var:index">
<t:if test="var:index">, </t:if>
<t:eventlink event="switchLocale" context="supportedLocale.toString()">${supportedLocale.getDisplayName()}</t:eventlink>
</t:loop>.
</div>
<strong>UTF-8 File Encoding</strong><br/>
For a seamless experience when working with non-ASCII characters in properties files, it's probably best to set the file to UTF-8 encoded.<br/>
In Eclipse, right-click on the file, choose Properties > Resource > Text file encoding > Other > UTF-8.<br/><br/>
References:
<a href="http://tapestry.apache.org/localization.html">Localization</a>,
<a href="http://en.wikipedia.org/wiki/UTF-8">UTF-8</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/LocalizationSetter.html">LocalizationSetter</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/ioc/services/ThreadLocale.html">ThreadLocale</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/PersistentLocale.html">PersistentLocale</a>,
<a href="http://docs.oracle.com/javase/6/docs/api/java/util/Locale.html">Locale</a>,
<a href="http://docs.oracle.com/javase/6/docs/api/java/text/DateFormat.html">DateFormat</a>,
<a href="http://docs.oracle.com/javase/6/docs/api/java/text/NumberFormat.html">NumberFormat</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/If.html">If</a>,
<a href="http://tapestry.apache.org/component-parameters.html#ComponentParameters-Rendervariables">Render Variables</a>,
<a href="http://getbootstrap.com/css/#type-lists">.dl-horizontal</a>,
<a href="http://getbootstrap.com/components/#wells">.well</a>.<br/><br/>
<t:pagelink page="Index">Home</t:pagelink><br/><br/>
<t:tabgroup>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/localization/LocalizationByTemplate.tml"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/localization/LocalizationByTemplate_fr.tml"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/localization/LocalizationByTemplate.java"/>
<t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/plain.css"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/services/AppModule.java"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/commons/LocalesUtil.java"/>
</t:tabgroup>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like
We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">
<h3>Localization By Template (French)</h3>
This page demonstrates localization by "localized component template".
There are two templates for this page:
<ul>
<li><strong>LocalizationByMessageCatalog_fr.tml</strong> provides a French page (actually, only parts of it are in French).</li>
<li><strong>LocalizationByMessageCatalog.tml</strong> provides a default page, which is English.</li>
</ul>
<div class="eg">
The current template is the first one. Using the current locale, we get:
<dl class="dl-horizontal well">
<dt>Locale code</dt>
<dd>${currentLocale.toString()}</dd>
<dt>Nom de locale</dt>
<dd>${currentLocale.getDisplayName()}</dd>
<dt>Une date</dt>
<dd><t:output value="date" format="prop:dateFormat"/></dd>
<dt>Un nombre</dt>
<dd><t:output value="number" format="prop:numberFormat"/></dd>
</dl>
The supported locales are defined in AppModule. Our AppModule has ${supportedLocales.size()}.<br/><br/>
Choose one:
<t:loop source="supportedLocales" value="supportedLocale" index="var:index">
<t:if test="var:index">, </t:if>
<t:eventlink event="switchLocale" context="supportedLocale.toString()">${supportedLocale.getDisplayName()}</t:eventlink>
</t:loop>.
</div>
<strong>UTF-8 File Encoding</strong><br/>
For a seamless experience when working with non-ASCII characters in properties files, it's probably best to set the file to UTF-8 encoded.<br/>
In Eclipse, right-click on the file, choose Properties > Resource > Text file encoding > Other > UTF-8.<br/><br/>
References:
<a href="http://tapestry.apache.org/localization.html">Localization</a>,
<a href="http://en.wikipedia.org/wiki/UTF-8">UTF-8</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/LocalizationSetter.html">LocalizationSetter</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/ioc/services/ThreadLocale.html">ThreadLocale</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/PersistentLocale.html">PersistentLocale</a>,
<a href="http://docs.oracle.com/javase/6/docs/api/java/util/Locale.html">Locale</a>,
<a href="http://docs.oracle.com/javase/6/docs/api/java/text/DateFormat.html">DateFormat</a>,
<a href="http://docs.oracle.com/javase/6/docs/api/java/text/NumberFormat.html">NumberFormat</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/If.html">If</a>,
<a href="http://tapestry.apache.org/component-parameters.html#ComponentParameters-Rendervariables">Render Variables</a>,
<a href="http://getbootstrap.com/css/#type-lists">.dl-horizontal</a>,
<a href="http://getbootstrap.com/components/#wells">.well</a>.<br/><br/>
<t:pagelink page="Index">Home</t:pagelink><br/><br/>
<t:tabgroup>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/localization/LocalizationByTemplate.tml"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/localization/LocalizationByTemplate_fr.tml"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/localization/LocalizationByTemplate.java"/>
<t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/plain.css"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/services/AppModule.java"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/commons/LocalesUtil.java"/>
</t:tabgroup>
</body>
</html>
package jumpstart.web.pages.examples.localization;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.LocalizationSetter;
@Import(stylesheet = "css/examples/plain.css")
public class LocalizationByTemplate {
// Screen fields
@Inject
@Property
private Locale currentLocale;
@Property
private Date date;
@Property
private double number = -1234.56;
@Property
private DateFormat dateFormat;
@Property
private NumberFormat numberFormat;
@Property
private List<Locale> supportedLocales;
@Property
private Locale supportedLocale;
// Generally useful bits and pieces
@Inject
private LocalizationSetter localizationSetter;
// The code
void setupRender() {
supportedLocales = localizationSetter.getSupportedLocales();
date = new Date();
dateFormat = DateFormat.getDateInstance(DateFormat.LONG, currentLocale);
numberFormat = NumberFormat.getInstance(currentLocale);
}
void onSwitchLocale(String localeCode) {
localizationSetter.setLocaleFromLocaleName(localeCode);
}
}
.eg {
margin: 20px 0;
padding: 14px;
border: 1px solid #ddd;
border-radius: 6px;
-webkit-border-radius: 6px;
-mox-border-radius: 6px;
}
package jumpstart.web.services;
import java.util.Map;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.Translator;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.beanvalidator.ClientConstraintDescriptor;
import org.apache.tapestry5.internal.beanvalidator.BaseCCD;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Primary;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
import org.apache.tapestry5.ioc.services.Coercion;
import org.apache.tapestry5.ioc.services.CoercionTuple;
import org.apache.tapestry5.ioc.services.ThreadLocale;
import org.apache.tapestry5.services.BeanBlockContribution;
import org.apache.tapestry5.services.ComponentRequestFilter;
import org.apache.tapestry5.services.DisplayBlockContribution;
import org.apache.tapestry5.services.EditBlockContribution;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.DataConstants;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.apache.tapestry5.services.security.WhitelistAnalyzer;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.upload.services.UploadSymbols;
import org.joda.time.DateMidnight;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.slf4j.Logger;
import jumpstart.business.validation.constraints.Letters;
import jumpstart.util.JodaTimeUtil;
import jumpstart.web.translators.MoneyTranslator;
import jumpstart.web.translators.YesNoTranslator;
/**
* This module is automatically included as part of the Tapestry IoC Registry, it's a good place to configure and extend
* Tapestry, or to place your own service definitions. See http://tapestry.apache.org/5.3.4/tapestry-ioc/module.html
*/
public class AppModule {
private static final String UPLOADS_PATH = "jumpstart.upload-path";
@Inject
@Symbol(SymbolConstants.PRODUCTION_MODE)
@Property(write = false)
private static boolean productionMode;
// Add 2 services to those provided by Tapestry.
// - CountryNames, and SelectIdModelFactory are used by pages which ask Tapestry to @Inject them.
public static void bind(ServiceBinder binder) {
binder.bind(CountryNames.class);
binder.bind(SelectIdModelFactory.class, SelectIdModelFactoryImpl.class);
}
// Tell Tapestry about our custom translators and their message file.
// We do this by contributing configuration to Tapestry's TranslatorAlternatesSource service, FieldValidatorSource
// service, and ComponentMessagesSource service.
@SuppressWarnings("rawtypes")
public static void contributeTranslatorAlternatesSource(MappedConfiguration<String, Translator> configuration,
ThreadLocale threadLocale) {
configuration.add("yesno", new YesNoTranslator("yesno"));
configuration.add("money2", new MoneyTranslator("money2", 2, threadLocale));
}
public void contributeComponentMessagesSource(OrderedConfiguration<String> configuration) {
configuration.add("myTranslationMessages", "jumpstart/web/translators/TranslationMessages");
}
// Tell Tapestry about the client-side (javascript) validators that corresponds to each server-side Bean Validator.
public static void contributeClientConstraintDescriptorSource(final JavaScriptSupport javaScriptSupport,
final Configuration<ClientConstraintDescriptor> configuration) {
configuration.add(new BaseCCD(Letters.class) {
public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes) {
javaScriptSupport.require("beanvalidation/letters");
writer.attributes(DataConstants.VALIDATION_ATTRIBUTE, true, "data-validate-letters", true,
"data-letters-message", message);
}
});
}
// Tell Tapestry about our custom ValueEncoders.
// We do this by contributing configuration to Tapestry's ValueEncoderSource service.
// @SuppressWarnings("rawtypes")
// public static void contributeValueEncoderSource(MappedConfiguration<Class, Object> configuration) {
// configuration.addInstance(Person.class, PersonEncoder.class);
// }
// Tell Tapestry which locales we support, and tell Tapestry to use its jQuery implementation for its JavaScript.
// We do this by contributing configuration to Tapestry's ApplicationDefaults service.
public static void contributeApplicationDefaults(MappedConfiguration<String, String> configuration) {
configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en_US,en_GB,fr");
configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "jquery");
}
// Tell Tapestry how to detect and protect pages that require security.
// We do this by contributing a custom ComponentRequestFilter to Tapestry's ComponentRequestHandler service.
// - ComponentRequestHandler is shown in
// http://tapestry.apache.org/request-processing.html#RequestProcessing-Overview
// - Based on http://tapestryjava.blogspot.com/2009/12/securing-tapestry-pages-with.html
public void contributeComponentRequestHandler(OrderedConfiguration<ComponentRequestFilter> configuration) {
configuration.addInstance("PageProtectionFilter", PageProtectionFilter.class);
}
// Tell Tapestry how to handle WildFly 11's classpath URLs - WildFly uses a "virtual file system".
// We do this by overriding Tapestry's ClasspathURLConverter service.
// See "Running Tapestry on JBoss" (sic) in http://wiki.apache.org/tapestry/Tapestry5HowTos .
@SuppressWarnings("rawtypes")
public static void contributeServiceOverride(MappedConfiguration<Class, Object> configuration) {
// configuration.add(ClasspathURLConverter.class, new ClasspathURLConverterJBoss7());
configuration.add(ClasspathURLConverter.class, new ClasspathURLConverterWildFly11());
}
// Tell Tapestry how to handle @EJB in page and component classes.
// We do this by contributing configuration to Tapestry's ComponentClassTransformWorker service.
// - Based on http://wiki.apache.org/tapestry/JEE-Annotation.
@Primary
public static void contributeComponentClassTransformWorker(
OrderedConfiguration<ComponentClassTransformWorker2> configuration) {
configuration.addInstance("EJB", EJBAnnotationWorker.class, "before:Property");
}
// Tell Tapestry how to handle pages annotated with @WhitelistAccessOnly, eg. Tapestry's ServiceStatus and
// PageCatalog.
// The default WhitelistAnalyzer allows localhost only and only in non-production mode.
// Our aim is to make the servicestatus page available to ALL clients when not in production mode.
// We do this by contributing our own WhitelistAnalyzer to Tapestry's ClientWhitelist service.
public static void contributeClientWhitelist(OrderedConfiguration<WhitelistAnalyzer> configuration) {
if (!productionMode) {
configuration.add("NonProductionWhitelistAnalyzer", new WhitelistAnalyzer() {
@Override
public boolean isRequestOnWhitelist(Request request) {
if (request.getPath().startsWith("/core/servicestatus")) {
return true;
}
else {
// This is copied from org.apache.tapestry5.internal.services.security.LocalhostOnly
String remoteHost = request.getRemoteHost();
return remoteHost.equals("localhost") || remoteHost.equals("127.0.0.1")
|| remoteHost.equals("0:0:0:0:0:0:0:1%0") || remoteHost.equals("0:0:0:0:0:0:0:1");
}
}
}, "before:*");
}
}
// Tell Tapestry how to build our Filer service (used in the FileUpload example).
// Annotate it with EagerLoad to force resolution of symbols at startup rather than when it is first used.
@EagerLoad
public static IFiler buildFiler(Logger logger, @Inject @Symbol(UPLOADS_PATH) final String uploadsPath,
@Inject @Symbol(UploadSymbols.FILESIZE_MAX) final long fileSizeMax) {
return new Filer(logger, UPLOADS_PATH, uploadsPath, UploadSymbols.FILESIZE_MAX, fileSizeMax);
}
// Tell Tapestry how to coerce Joda Time types to and from Java Date types for the TypeCoercers example.
// We do this by contributing configuration to Tapestry's TypeCoercer service.
// - Based on http://tapestry.apache.org/typecoercer-service.html
@SuppressWarnings("rawtypes")
public static void contributeTypeCoercer(Configuration<CoercionTuple> configuration) {
// From java.util.Date to DateMidnight
Coercion<java.util.Date, DateMidnight> toDateMidnight = new Coercion<java.util.Date, DateMidnight>() {
public DateMidnight coerce(java.util.Date input) {
// TODO - confirm this conversion always works, esp. across timezones
return JodaTimeUtil.toDateMidnight(input);
}
};
configuration.add(new CoercionTuple<>(java.util.Date.class, DateMidnight.class, toDateMidnight));
// From DateMidnight to java.util.Date
Coercion<DateMidnight, java.util.Date> fromDateMidnight = new Coercion<DateMidnight, java.util.Date>() {
public java.util.Date coerce(DateMidnight input) {
// TODO - confirm this conversion always works, esp. across timezones
return JodaTimeUtil.toJavaDate(input);
}
};
configuration.add(new CoercionTuple<>(DateMidnight.class, java.util.Date.class, fromDateMidnight));
// From java.util.Date to LocalDate
Coercion<java.util.Date, LocalDate> toLocalDate = new Coercion<java.util.Date, LocalDate>() {
public LocalDate coerce(java.util.Date input) {
// TODO - confirm this conversion always works, esp. across timezones
return JodaTimeUtil.toLocalDate(input);
}
};
configuration.add(new CoercionTuple<>(java.util.Date.class, LocalDate.class, toLocalDate));
// From LocalDate to java.util.Date
Coercion<LocalDate, java.util.Date> fromLocalDate = new Coercion<LocalDate, java.util.Date>() {
public java.util.Date coerce(LocalDate input) {
// TODO - confirm this conversion always works, esp. across timezones
return JodaTimeUtil.toJavaDate(input);
}
};
configuration.add(new CoercionTuple<>(LocalDate.class, java.util.Date.class, fromLocalDate));
}
// Tell Tapestry how its BeanDisplay and BeanEditor can handle the JodaTime types.
// We do this by contributing configuration to Tapestry's DefaultDataTypeAnalyzer and BeanBlockSource services.
// - Based on http://tapestry.apache.org/beaneditform-guide.html .
public static void contributeDefaultDataTypeAnalyzer(
@SuppressWarnings("rawtypes") MappedConfiguration<Class, String> configuration) {
configuration.add(DateTime.class, "dateTime");
configuration.add(DateMidnight.class, "dateMidnight");
configuration.add(LocalDateTime.class, "localDateTime");
configuration.add(LocalDate.class, "localDate");
configuration.add(LocalTime.class, "localTime");
}
public static void contributeBeanBlockSource(Configuration<BeanBlockContribution> configuration) {
configuration.add(new DisplayBlockContribution("dateTime", "infra/AppPropertyDisplayBlocks", "dateTime"));
configuration
.add(new DisplayBlockContribution("dateMidnight", "infra/AppPropertyDisplayBlocks", "dateMidnight"));
configuration
.add(new DisplayBlockContribution("localDateTime", "infra/AppPropertyDisplayBlocks", "localDateTime"));
configuration.add(new DisplayBlockContribution("localDate", "infra/AppPropertyDisplayBlocks", "localDate"));
configuration.add(new DisplayBlockContribution("localTime", "infra/AppPropertyDisplayBlocks", "localTime"));
configuration.add(new EditBlockContribution("dateMidnight", "infra/AppPropertyEditBlocks", "dateMidnight"));
configuration.add(new EditBlockContribution("localDate", "infra/AppPropertyEditBlocks", "localDate"));
}
}
package jumpstart.web.commons;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class LocalesUtil {
public static List<Locale> convertToLocales(String supportedLocaleCodesString) {
List<Locale> locales = new ArrayList<Locale>();
String[] localeCodes = supportedLocaleCodesString.split(",");
for (String localeCode : localeCodes) {
locales.add(convertToLocale(localeCode));
}
return locales;
}
public static Locale convertToLocale(String localeCode) {
Locale locale = null;
String[] elements = localeCode.split("_");
switch (elements.length) {
case 1:
locale = new Locale(elements[0]);
break;
case 2:
locale = new Locale(elements[0], elements[1]);
break;
case 3:
locale = new Locale(elements[0], elements[1], elements[2]);
break;
case 4:
locale = new Locale(elements[0], elements[1], elements[2] + "_" + elements[3]);
break;
default:
throw new RuntimeException("Can't handle localeCode = \"" + localeCode + "\". Elements.length = " + elements.length);
}
return locale;
}
}