Up: Part III

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 [G]  [G] Primarily java.util.Locale and java.util.ResourceBundle 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 [H]  [H] http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes. 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:
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 [I]  [I] Technically, they can have other formats, but Lift generally only deals with PropertyResourceBundles 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).
Default Door Bundle
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↓.
Spanish Door Bundle
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 [J]  [J] The properties files are retrieved with ClassLoader.getResourceAsStream; 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 [K]  [K] http://download.oracle.com/javase/6/docs/api/java/util/Properties.html, 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:
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:
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.
Formatted Bundles
// 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 the loc tag
<!-- 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 [L]  [L] Thanks to Tim Perret for the original examples at http://blog.getintheloop.eu/2009/7/26/how-to-extensive-localization-with-the-liftweb-framework. 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 
}
Up: Part III

(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir