D Internationalization ↓↓
The ability to display pages to users of multiple languages is a common feature of many web frameworks. Lift builds on the underlying Java I18N foundations to provide a simple yet flexible means for using Locales and translated strings in your app. Locales are used to control not only what language the text is in that’s presented to the user, but also number and date formatting, among others. If you want more details on the underlying foundation of Java I18N we suggest you visit the Internationalization Homepage at
http://java.sun.com/javase/technologies/core/basic/intl/.
Another note is that languages are selected in Lift using language tags, as defined in
http://www.w3.org/International/articles/language-tags/. Language tags are base on the ISO 639 standard. In general, you should keep language tags as short as possible and avoid adding information (such as regional specifiers) that does not provide otherwise distinguishing information. For example, if your Spanish page will be used for both Mexican and Spanish clients without modification, simply use
es and not
es_MX or
es_ES.
D.1 Localized Templates ↓
As we described in Section
4.1 on page 1↑, Lift automatically chooses the template for a request based on the current locale by appending the locale’s variants. That means that a request for
/index with a calculated locale of
en_US will try these filenames, in order:
-
index_en_US.html
-
index_en.html
-
index.html
Note that Java upper-cases the country portion of the locale, so you need to make sure you name your templates accordingly. For instance, in the above example a file named index_en_us.html wouldn’t match.
D.2 Resource Bundles
Resource bundles are sets of property files that contain keyed strings for your application to use in messages. In addition to the key/value pair contents of the files, the filename itself is significant. When a
ResourceBundle is specified by name, the base name is used as the default, and additional files with names of the form “<base name>_<language tag>” can be used to specify translations of the default strings in a given language. As an example, consider listing
D.2↓, which specifies a default resource bundle for an application that reports the status of a door (open or closed).
openStatus=The door is open
closedStatus=The door is closed
Suppose this file is called “DoorMessages.properties”; we can provide an additional translation for Spanish by creating a file called “DoorMessages_es.properties”, shown in listing
D.2↓.
openStatus=La puerta está abierta
closedStatus=La puerta está cerrada
When you want to retrieve a message (covered in the next two sections) Lift will check the current Locale and see if there’s a specialized ResourceBundle available for it. If so, it uses the messages in that file; otherwise, it uses the default bundle.
Lift supports using multiple resource bundle files so that you can break your messages up into functional groups. You specify this by setting the LiftRules.resourceNames property to a list of the base names (without a language or “.properties” extension):
LiftRules.resourceNames = "DoorMessages" ::
"DoorknobMessages" :: Nil
The order that you define the resource bundle names is the order that they’ll be searched for keys. The message properties files should be located in your WEB-INF/classes folder so that they are accessible from Lift’s classloader; if you’re using Maven this will happen if you put your files in the src/main/resources directory.
Note: According to the
Properties documentation, keys
must escape significant whitespace, colons or equals signs in the key itself with backslashes. For example, to specify “this = that” as a key, you would have to write it as “this\ \=\ that” in the properties file.
↓
D.3 An Important Note on Resource Bundle Resolution↓
Per Java’s documentation on
ResourceBundle, resolution of property files is done in this order:
-
baseName + “_” + language1 + “_” + country1 + “_” + variant1
-
baseName + “_” + language1 + “_” + country1
-
baseName + “_” + language1
-
baseName + “_” + language2 + “_” + country2 + “_” + variant2
-
baseName + “_” + language2 + “_” + country2
-
baseName + “_” + language2
-
baseName
where “language1”, “country1”, and “variant1” are the requested locale parameters, and “language2”, “country2”, “variant2” are the default locale parameters.
For example, if the default locale for your computer is “en_GB”, someone requests a page for “ja”, and you have the following property files defined:
-
Messages_fr_FR.properties
-
Messages_en_GB.properties
-
Messages.properties
then the
Messages_en_GB.properties file, and not
Messages.properties will be used. If you want to change this behavior so that any undefined locales utilize the base properties file, set your default Locale to the ROOT locale in your
Boot.scala with the code shown in Listing
D.3↓:
Setting the ROOT Default Locale
import java.util.Locale
Locale.setDefault(Locale.ROOT)
D.4 Localized Strings in Scala Code
Retrieving localized strings in your Scala code is primarily performed using the
S.? method. When invoked with one argument the resource bundles are searched for a key matching the given argument. If a matching value is found it’s returned. If it can’t be found then Lift calls
LiftRules.localizationLookupFailureNotice on the (key, current Locale) pair and then simply returns the key. If you call
S.? with more than one argument, the first argument is still the key to look up, but any remaining arguments are used as format parameters for
String.format executed on the retrieved value. For example, listing
D.4↓ shows a sample bundle file and the associated Scala code for using message formatting.
// bundle
tempMsg=The current temperature is %0.1 degrees
// code
var currentTmp : Double = getTemp()
Text(S.?("tempMsg", currentTemp))
Lift also provides the S.?? method, which is similar to S.? but uses the ResourceBundle for internal Lift strings. Lift’s resource-bundles are located in the i18n folder with the name lift-core.properties The resource-bundle name is given by LiftRules.liftCoreResourceName variable. Generally you won’t use this method.
D.5 Formatting Localized Strings
While Lift provides facilities for retrieving strings from localized property bundles (Section
D.4↑), it does not provide direct support for localized formatting of those strings. There is an
S.? method which takes additional parameters, but it uses
String.format (and printf syntax) to format the strings and does not properly support date/time formatting. Instead, we recommend you use
java.text.MessageFormat for localized strings that will use parameters. Listing
D.5↓ shows a utility method that you can use in your code to localize strings with parameters. Note that if you have a lot of Lift’s implicit conversions in scope you may need to explicitly type some arguments as we have in this example.
A Utility Method for Localizing Strings
private def i10n(key : String, args : Object*) = {
import java.text.{FieldPosition,MessageFormat}
val formatter = new MessageFormat(S.?(key), S.locale)
formatter.format(args.toArray, new StringBuffer, new FieldPosition(0)).toString
}
// Usage:
<span>{i10n("welcome", new java.util.Date, 6 : Integer)}</span>
D.6 Localized Strings in Templates↓
You can add localized strings directly in your templates through the
<lift:loc /> tag. You can either provide a locid attribute on the tag which is used as the lookup key, or if you don’t provide one, the contents of the tag will be used as the key. In either case, if the key can’t be found in any resource bundles, the contents of the tag will be used. Listing
D.6↓ shows some examples of how you could use lift:loc. In both examples, assume that we’re using the resource bundle shown in listing
D.2↑. The fallthrough behavior lets us put a default text (English) directly in the template, although for consistency you should usually provide an explicit bundle for all languages.
<!-- using explicit key (preferred) -->
<lift:loc locid="openStatus">The door is open</lift:loc>
<!-- should be the same result, but a missing bundle
will result in the key being displayed -->
<lift:loc>openStatus</lift:loc>
D.7 Calculating Locale↓↓
The Locale for a given request is calculated by the function set in LiftRules.localeCalculator, a (Box[HttpServletRequest]) ⇒ Locale. The default behavior is to call getLocale on the HTTPRequest, which allows the server to set it if your clients send locale preferences. If that call returns null, then Locale.getDefault is used. You can provide your own function for calculating locales if you desire.
Listing
D.7↓ shows how you can use a cookie in addition to a URL query parameter to select the locale for your pages. This code would be placed in your
Boot.boot method. Lines 2-3 define some imports beyond the standard imports (see Section
3.3 on page 1↑). Lines 6-10 define a utility function to convert a language tag string into a
Locale. Line 13 defines the name of the cookie that will store your locale choice. Lines 15-34 define the partial function that will be used to compute the new
Locale. First, we only do computation if the
LiftRules.localeCalculator method is invoked with an
HTTPRequest (line 16). Our next step is to determine the current locale by checking whether there is an existing locale cookie set (lines 18-20), or by utilizing the default Lift locale calculator (line 21). Our next check is to determine if the user has explicitly requested a locale via a query parameter (line 24). If the parameter is set (and not
null), we construct a new
Locale from the value (line 26), set the cookie so that the request is remembered (line 27), and return the new locale. If there is no request parameter then we use the current locale as defined on line 18.
Calculating Locale Based on Cookies and Parameters
// Import the classes we’ll be using beyond the standard imports
import java.util.Locale
import provider.{HTTPCookie,HTTPRequest}
// Properly convert a language tag to a Locale
def computeLocale(tag : String) = tag.split(Array(’-’, ’_’)) match {
case Array(lang) => new Locale(lang)
case Array(lang, country) => new Locale(lang, country)
case Array(lang, country, variant) => new Locale(lang, country, variant)
}
// Define this to be whatever name you want
val LOCALE_COOKIE_NAME = "SelectedLocale"
LiftRules.localeCalculator = {
case fullReq @ Full(req) => {
// Check against a set cookie, or the locale sent in the request
def currentLocale : Locale =
S.findCookie(LOCALE_COOKIE_NAME).flatMap {
cookie => cookie.value.map(computeLocale)
} openOr LiftRules.defaultLocaleCalculator(fullReq)
// Check to see if the user explicitly requests a new locale
S.param("locale") match {
case Full(requestedLocale) if requestedLocale != null => {
val computedLocale = computeLocale(requestedLocale)
S.addCookie(HTTPCookie(LOCALE_COOKIE_NAME, requestedLocale))
computedLocale
}
case _ => currentLocale
}
}
case _ => Locale.getDefault
}
(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir