Multilingual websites with Lift and OSGi

Lift already provides some mechanisms to publish a website in different languages. If Lift is used within an OSGi environment, a little workaround is needed in order to make it find the Resource Bundles containing language-dependent strings (usually provided as property files).

Internationalization with Lift

First of all, let me summarize the possibilities of internationalization with Lift: String resources have to be placed in so-called “property bundles”. These are normal Java property files like the following:

title=My Website
yes=Yes
no=No
deletequestion=Do you want to delete everything?

The name of a property bundle always starts with lift, but different suffixes in the ISO format will be appended describing the file’s language and country. For example, the file lift_en.properties contains English string resources, lift_de.properties contains German ones and lift_en_US.properties US American English. The prefix lift can be specified in the web application’s boot loader:

LiftRules.resourceNames = "anotherPrefix" :: Nil

The variable resourceNames is a list of strings. Therefore, resource bundles can be divided into multiple files with different prefixes. This is useful for larger projects with a lot of string resources.

After the resource bundles have been created, strings in HTML templates can be translated using the <lift:loc locid="..." /> tag. For example, following template:

<html>
<head>
<title><lift:loc locid="title" /></title>
</head>
<body>
<lift:loc locid="deletequestion" />
<button><lift:loc locid="yes" /></button>
<button><lift:loc locid="no" /></button>
</body>
</html>

will be translated to the following HTML code (using the properties file from above):

<html>
<head>
<title>My Website</title>
</head>
<body>
Do you want to delete everything?
<button>Yes</button>
<button>No</button>
</body>
</html>

In the Scala source code the stateful object net.liftweb.http.S can be used instead. It provides the method ? which takes a string and looks for the corresponding translation in the resource bundle:

def snippet(xhtml: NodeSeq): NodeSeq =
  <p>{ S ? "deletequestion" }</p>

Tip: If you want to translate a tag’s attribute in a HTML template (e.g. the attribute value of the inputtag), you should copy the respective code into a snippet. Therefore you can use the S object to translate the attribute:

def inputButton(xhtml: NodeSeq): NodeSeq =
  <input type="button" value={ S ? "yes" } />

Resource Bundles in the OSGi environment

Lift tries to find resource bundles in the classpath using the method getClass.getClassLoader.getResourceAsStream(). In an OSGi environment this is not going to work, because the classloader of the net.liftweb.lift-webkit bundle is used.

As mentioned above, this can be solved with a little workaround. A resource bundle factory has to be added in the application’s boot loader:

LiftRules.resourceBundleFactories prepend {
  case (basename, locale) => ResourceBundle.getBundle(basename, locale)
}

Therefore, the resource bundles will be loaded by the classloader of the bundle that contains the boot loader.

Conclusion

The means for internationalization provided by Lift are comprehensive enough for most websites. If something is not translatable using the <lift:loc> tag (e.g. attributes) or if more control over the process is needed, snippets can be used instead. In OSGi environments a resource bundle factory has to be created, so property files will be loaded by the correct classloader.