How To: Extensive Localization with the Liftweb Framework
July 26th, 2009
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!
5 Responses to “How To: Extensive Localization with the Liftweb Framework”
Sorry, comments are closed for this article.
July 28th, 2009 at 08:03 PM
Why is a customized locale calculator needed? What is special about it that it couldn’t be default with Lift?
July 29th, 2009 at 02:55 PM
So does this also work with the time / date formats as well?
July 30th, 2009 at 01:12 PM
@Daniel – The locale calculator I detailed in this post essentially saves the users locale in a cookie… different people like doing things differently so by default lift just uses the JVM locale; some people will power there localization / cultures differently so its just more flexible to have this function. Its minimal work as I have this function in a reusable library that I just add to my lift apps if i want specific localization within my known scheme.
@Jamie – Actually no, we currently dont have date / time localization; we talked about adding jodatime to Lift and using that… perhaps its something i’ll work on. We’ll see – if this is something people need then do let us know!
August 21st, 2009 at 06:21 PM
This looks really simple to set up and use common sense design (like anything I have seen in Lift so far).
That said, IMHO, no localization package is complete if it does not handle numbers, date/time, currency (incl. showing the symbol on the left or right), etc. Having to deal with these issues manually would be a major set back …
September 11th, 2009 at 01:26 PM
While a time/date converter would make it more complete, it’s not that essential from my point of view.
Text and template localisation is the most important imho.