Multilingual websites with Lift and OSGi

Lift already provides some mech­an­isms to pub­lish a web­site in dif­fer­ent lan­guages. If Lift is used within an OSGi en­vir­on­ment, a little work­around is needed in or­der to make it find the Re­source Bundles con­tain­ing lan­guage-de­pend­ent strings (usu­ally provided as prop­erty files).

Internationalization with Lift

First of all, let me sum­mar­ize the pos­sib­il­it­ies of in­ter­na­tion­al­iz­a­tion with Lift: String re­sources have to be placed in so-called “prop­erty bundles”. These are nor­mal Java prop­erty files like the fol­low­ing:

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

The name of a prop­erty bundle al­ways starts with lift, but dif­fer­ent suf­fixes in the ISO format will be ap­pen­ded de­scrib­ing the file’s lan­guage and coun­try. For ex­ample, the file lift_en.properties con­tains Eng­lish string re­sources, lift_de.properties con­tains Ger­man ones and lift_en_US.properties US Amer­ican Eng­lish. The pre­fix lift can be spe­cified in the web ap­plic­a­tion’s boot loader:

LiftRules.resourceNames = "anotherPrefix" :: Nil

The vari­able resourceNames is a list of strings. There­fore, re­source bundles can be di­vided into mul­tiple files with dif­fer­ent pre­fixes. This is use­ful for lar­ger pro­jects with a lot of string re­sources.

After the re­source bundles have been cre­ated, strings in HTML tem­plates can be trans­lated us­ing the <lift:loc locid="..." /> tag. For ex­ample, fol­low­ing tem­plate:

<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 trans­lated to the fol­low­ing HTML code (us­ing the prop­er­ties 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 state­ful ob­ject net.liftweb.http.S can be used in­stead. It provides the method ? which takes a string and looks for the cor­res­pond­ing trans­la­tion in the re­source bundle:

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

Tip: If you want to trans­late a tag’s at­trib­ute in a HTML tem­plate (e.g. the at­trib­ute value of the inputtag), you should copy the re­spect­ive code into a snip­pet. There­fore you can use the S ob­ject to trans­late the at­trib­ute:

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

Resource Bundles in the OSGi environment

Lift tries to find re­source bundles in the classpath us­ing the method getClass.getClassLoader.getResourceAsStream(). In an OSGi en­vir­on­ment this is not go­ing to work, be­cause the class­loader of the net.liftweb.lift-webkit bundle is used.

As men­tioned above, this can be solved with a little work­around. A re­source bundle fact­ory has to be ad­ded in the ap­plic­a­tion’s boot loader:

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

There­fore, the re­source bundles will be loaded by the class­loader of the bundle that con­tains the boot loader.

Conclusion

The means for in­ter­na­tion­al­iz­a­tion provided by Lift are com­pre­hens­ive enough for most web­sites. If something is not trans­lat­able us­ing the <lift:loc> tag (e.g. at­trib­utes) or if more con­trol over the pro­cess is needed, snip­pets can be used in­stead. In OSGi en­vir­on­ments a re­source bundle fact­ory has to be cre­ated, so prop­erty files will be loaded by the cor­rect class­loader.


Posted by Michel Krämer
on April, 5th 2010.