Support for localization, formatting and parsing of locale dependent values, and multi-language messages
is an integral part of Civilian.
defines localization (l10n) and internationalization (i18n) as "means of adapting computer software to different languages,
Localization in the context of web applications deals especially with theses tasks:
- How to associate a locale with a request and a response.
- How to parse number, date and time values from request parameters which are formatted according to a certain locale.
- How to format number, date and time values in response content according to a certain locale.
- How to construct text messages in response content, translated into a certain language.
If you build an application which only consumes and produces JSON messages, or only constructs responses in a single language,
and never serializes a number or date, then localization is obviously not an issue.
Else you might want to learn about the localization support of Civilian.
The basic techniques
Of course the experienced Java programmer knows how to deal with the localization tasks mentioned above:
java.util.Locale is the standard way to represent a language or more specific a locale.
- HTTP specifies how to associate a locale with a request (via the Accept-Language header) and a response
(via the Content-Language header). The Servlet API allows to access and override that information in ServletResponse and
ServletRequest in form of
- Locale dependent parsing and formatting of numbers, date and time values can be achieved
- Multi-language applications can be realized by converting message ids into language texts of the target language.
java.util.ResourceBundle provides a popular way how to implement such a translation function.
Nevertheless dealing with this tasks is burdensome in detail:
- Parsing and formatting string based request parameters to and from objects is left to the application.
java.text has its own issues (thread-safety, complicated to use, etc.)
- The various attempts of the JDK to model Date and Time classes were not convincing.
(The LocalDate class of Java 8 seems to be the first acceptable solution).
Civilians localization support
Civilians support for locale-dependent and multi-language applications consists
of these integrated classes and functionality:
- MsgBundle allows to translate text id into language texts.
- TypeSerializer allows for locale-dependent formatting and parsing of values.
- LocaleService bundles a MsgBundle and a TypeSerializer for a certain Locale.
- Request and
Response can be associated with a LocaleService instance.
- The applications LocaleServiceList provides
LocaleService instances for all supported locales.
- The LangMixin allows easy formatting and translation in (CSP) templates.
- The @LocaleValue can be set on controller method parameters
to parse injected values into a locale-dependent way.
LocaleService, supported locales
Every Civilian application has a list of supported locales. (If you don't configure
explicitly this list will default to the default system locale).
For each supported locale, a LocaleService
object is available via the applications
. The LocaleService itself offers
- a MsgBundle which can be used to translate text keys into texts of a certain language
- a TypeSerializer which can be used to format or parse objects of simple types to and from strings.
Associating a LocaleService with request and response
provide a LocaleService instance.
If you don't set the LocaleService explicitly, it will be initialized as follows:
- The request uses the preferred client locale as specified with the
header and asks the LocaleServiceList for the corresponding LocaleService instance. If the locale is not supported, it will fall back
to the default locale of the LocaleService.
- The response initializes its LocaleService instance from the request.
A frequent use case is to ignore the
header and set the request LocaleService explicitly – for instance
using the locale preference stored in a user session. Just be sure to set the LocaleService before you start to read
locale-dependent parameters from the request.
The MsgBundle class
Each LocaleService has
object to translate text ids into texts of the language of the LocaleService.
LocaleService ls = app.getLocaleServices().getLocaleService(Locale.FRENCH);
MsgBundle itself is abstract, in order to support different implementations for this translation service.
Internally, the LocaleServiceList
uses a MsgBundleFactory
to create MsgBundle instances for a locale.
In not explicitly configured the MsgBundle(s) of an application are empty: They will just return the text id + "?" as translated text.
Civilian provides classes ResMsgBundle and ResMsgBundleFactory
which are a MsgBundle and factory implementations based on
Please see the config section on how to configure and use these.
The resource bundle compiler
Using string text ids which are translated into languages texts has an obvious disadvantage: If you pass string literals,
you can't easily find where a specific text id is used except using full text search on your code-base.
A better idea is to define Java constants for your text ids, and use the constants on a MsgBundle:
MsgBundle msgs = ...
msgs.get("FREEDOM")); // string literal
msgs.get(Msg.FREEDOM); // constant, checked by the compiler
Civilian provides a small command-line tool ResBundleCompiler
which is based on that idea.
It enables the following workflow:
- You collect and edit your text ids and translations in a Excel file. It contains a column for the text id and columns
for all translated locales.
- Whenever you change (add, edit, delete) texts in that translation file you invoke the ResBundleCompiler on it.
- The compiler generates a resource-bundle file for each locale, and a Java class defining constants for every text id
Msg class in the above example).
The ResBundleCompiler uses Apache POI
to read the Excel file.
In order to run the ResBundleCompiler, you must add the POI (3.1+) libraries to your classpath.
When you run the ResBundleCompiler without arguments, it will print a help message how to use it.
sample demonstrates this technique.
The Type framework
Civilians Type framework
builds the basis of locale-dependent formatting and parsing of values.
But not restricted to locale-dependent string representations it allows to implement arbitrary serialization schemes:
Generally speaking, a parsing and formatting function depends on both the value type and the serialization scheme:
format: (T value, TypeSerializer ts) → String
parse: (String s, TypeSerializer ts) → T
The type framework implements such functions using a double-dispatch-pattern: Types like strings, numbers, boolean, dates are
represented by a Type
object, the serialization scheme is represented by a TypeSerializer
Given a Type object, a value of that type and a TypeSerializer we can produce a string for the value.
In reverse given the Type, TypeSerializer and that string we can reconstruct the value:
TypeSerializer ts = ...;
Type<T> type = ...;
T value = ...;
String s = type.format(ts, value);
T parsed = type.parse(ts, s);
Civilian implements two serialization schemes, a locale-dependent LocaleSerializer
which is based on
to serialize values:
||formatted by LocaleSerializer
|Date(y=2014, m=12, d=31)
Date, Time and DateTime values
Civilian provides out of the box support for
Localization of controller method parameters
You can inject request values into parameters
of a controller action method.
For example given
public class SearchController extends Controller
@Post public void search(@Parameter("term") String term, @Parameter("from") Date from)
the query parameters
are injected into the respective parameters when this method is invoked.
The String parameter is passed unchanged, but the Date parameter will be converted from a string into a date value.
If this conversion is done
by a Type
object, then by default the
is used, and of course will raise a runtime error if the supplied query parameter
does not conform to its format.
But what if the Date value was entered in a HTML form in a locale-dependent manner? Add the
annotation to instruct the Type to use the locale-dependent LocaleSerializer
of the request to parse the value:
@Post public void search(..., @QueryParam("from") @LocaleValue Date from)
Localization and forms
If a HTML form presents a textfield to allow the user to input an integer, a decimal number, a date, a time value, etc.
then the input is done in some locale-dependent format and needs to be parsed from a string request parameter when the submitted
form is evaluated on the server.
The form controls in Civlians form library
don't force you to interact with string values,
but already convert request values into typed representations. In addition any form control which displays a HTML textfield
to the user, will parse the entered value in a locale dependent manner.
You can configure the list of supported locales and the MsgBundleFactory
to create MsgBundle
for those locale in civilian.ini
app.myapp.locales = en-UK,de-AT,de-CH,fr
app.myapp.messages = resbundle:org/example/myapp/text/message
entry lists the supported locales, the default locale coming first.
entry defines a MsgBundleFactory
. The entry is either
- The class name of a MsgBundleFactory implementation
- a string starting with
resbundle: followed by a base name which can be passed to
java.util.ResourceBundle.getBundle(String baseName, Locale locale).
In the example above the application
could provide these resource bundle files
(in that case locale
would share the same resouce bundle).
Alternatively you can also configure the localization settings programmatically during
using AppConfig.setSupportedLocales() and
You may also add own Type implementations for
custom types to the type library of the