This is my Blog.

It's full of opinions on a wide range of stuff.

How To: Extensive Localization with the Liftweb Framework

One of the best things about Lift is its amazingly flexible template and resource localization system. This article discusses the mechanisms that you can use to localize your application.

Overview

Out of the box, Lift gives you the following options - items 1 and 2 require zero boiler plate, whilst the 3rd option gives you the flexibility to extend the localization however you need.

  • Text localization from property bundles
  • Full template localization
  • Custom resource bundle provider hook

We will now take a look at each localization option in turn, in relative detail stopping to investigate how they work.

Assumed environment

For the length of this article we'll assume that we have the need to localize into English (our default language), French, German and Hebrew. These languages have the following locale codes:

        Language 
        Code 

English

en_GB

French

fr_FR

German

de_DE

Hebrew

he_IL

These are standard ISO codes used by the Java localization system, irrespective of Lift et al. As a bit of background for those of you not familiar with locale codes and there purpose, you can see that the first part of the locale code denotes the spoken language - for example, en is English - and the second part after the underscore is the country code. This is amazingly helpful for languages like English and Arabic which are spoken in many countries as it gives you a specific language and then culture on which you can account for language nuances etc. Salutations are a classic one - UK English might have a salutation of "Good afternoon" whilst Australian English might have "Good ay'".

In order to tell Lift that this application will be localized into several languages, we have to set a customized localeCalculator. In our application it will look like this:

  def localeCalculator(request : Box[HttpServletRequest]): Locale = 
  request.flatMap(r => {
  def workOutLocale: Box[java.util.Locale] = 
  S.findCookie(localeCookieName) match {
    case Full(cookie) => cookie.getValue()
    case _ => Full(LiftRules.defaultLocaleCalculator(request))
  }
  tryo(r.getParameter("locale")) match {
    case Full(null) => workOutLocale
    case Empty => workOutLocale
    case Failure(_,_,_) => workOutLocale
    case Full(selectedLocale) => {
      setLocale(selectedLocale)
      selectedLocale
    }
  }
}).openOr(java.util.Locale.getDefault())

Add this function someplace in your application - here we'll assume its just in the Boot class, then set it in LiftRules like so:

LiftRules.localeCalculator = localeCalculator _

Text localization from property bundles

So, given our working languages we have several issues at hand and several strategies we could choose. Lets start with the most basic form of localization that will be familiar with most users of the java platform... properties files loaded as a ResourceBundle.

So lets assume that we have our translations already completed, we just need to put them into key-value pairs in properties files located in:

`${project.basedir}/src/main/resources/mybundlename_*locale*`

So for us, that looks like:

mybundlename_en_GB.properties
mybundlename_fr_FR.properties
mybundlename_de_DE.properties
mybundlename_he_IL.properties

This is pretty standard stuff that is well documented in the javadocs from sun. So, you need to know how to specify value keys within lift. There are two use cases, one is in your code, and the other is in your HTML templates.

In code:

S.?("mykey")

In template:

<lift:loc locid="mykey">Default Text</lift:loc>

This is all well and good, but there are use cases that simple key/value replacement doesnt take care of - with our use case languages we have a great example here:

  • Hebrew is a language that is written right-to-left (RTL) so generally the content / css / markup will be quite different
  • Within the languages that are left-to-right (LTR), French and English have a comparable number of character tokens, but german is typically more verbose and content takes up more room.

So, to deal with such cases Lift brings you comprehensive template localization which we'll now discuss.

Template file localization

Given this conundrum about language direction and content length lift can assert different template names. For example, lets assume that in your webapp directory you have a file called index.html - based on the LiftRules.localeCalculator Locale that is returned, it can choose the right template. With the problems we face here, we might have:

index_en_GB.html
index_en_AU.html
index_de.html
index_he.html

This would then yield different content templates for German and Hebrew, and gives us two distinctly different english templates for UK English and Australian English... for arguments sake the imagery in the two templates could be different because Australian culture is somewhat more casual than compared to England.

This exact same scheme also applies for CSS resources if you do not need the possibly side effect of code duplication in this system.

Custom resource bundle provider hook

One of the driving mantras of Lift is that everything, and we mean everything has sensible defaults, but you can hook right into the core lift lifecycle and add your own stuff. Localization is no different and it is of course a common idiom to need to load localization content from a database backed cache. I wont delve into exactly how you handle the caching or similar (that my friends is up to you) but this is how you pass the special resource bundles into Lift's cycle:

LiftRules.resourceBundleFactories.prepend { 
  case (basename, locale) if localeAvalible_?(locale) => 
      CacheResourceBundle(locale)
  case _ => CacheResourceBundle(new Locale("en","GB"))
}

In the above example, localeAvalible_? checks if this locale is available (from a value in my application) and of course, CacheResourceBundle is a subclass of ResourceBundle and has the following signature:

case class CacheResourceBundle(loc: Locale) extends ResourceBundle

Conclusion

Thats pretty much it folks - by way of a mix of these schemes its possible to build up a very rich localization methodology which works for pretty much all locale needs. In this example we have discussed language lengths, RTL languages and given you all the code you need to get going with localization in Lift - go forth and localize!

blog comments powered by Disqus