Localising your SitePublisher components the easy way

Posted: 22 September 2009 in Components, Content XML, Data Capture Template, Data Content Records, External, FormsPublisher, Java, Pages, SitePublisher, Sites, Templates, XSL

Localisation is always a bit tricky. Here’s an easy solution.

First, we’re going to create dictionaries for each of our supported languages. They are going to be content managed by the user in DCRs. In our workarea, we add a “common/dictionary” data type. For this, we create the following structure:


The content of datacapture.cfg would be like this:

<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE data-capture-requirements SYSTEM "datacapture6.0.dtd">
<data-capture-requirements name="dictionary">
 <ruleset name="dictionary">
<root-container name="dictionary" location="dictionary">
<container name="entry" location="entry" min="0" max="unbounded" default="1" combination="and">
 <item name="key" pathid="key">
 <description>words to translate</description>
 <text required="t" size="100" />
 <item name="translation" pathid="translation">
 <description>translation of the words</description>
 <text required="t" size="100" />

Update templating.cfg and create a new dictionary per language to support. I call mine acme.fr and I place the sentences I want to translate and their translations in it. Such as “Hello World!” -> “Bonjour tout le monde!”

Now that we have the data somewhat sorted, we can do the business logic. First, we compile a class that will read the DCR containing our dictionary and the translations so that the component can call it and add the XML it returns to its data model. That data model will then be read by the XSL to display our translation.

package com.acme.components;

import com.interwoven.livesite.dom4j.Dom4jUtils;
import com.interwoven.livesite.file.FileDALIfc;
import com.interwoven.livesite.runtime.RequestContext;
import java.io.InputStream;
import org.dom4j.Document;
import org.dom4j.Element;

public class i18n {

 public Document getDictionary(RequestContext context) {

 Document doc = Dom4jUtils.newDocument();
 Element rootElement = doc.addElement(this.getClass().getCanonicalName() + ".getDictionary");

 // open and parse the dictionary as an XML document
 FileDALIfc fileDal = context.getFileDAL();                                  // gives us access to the files in Teamsite
 Pattern workareaPattern = Pattern.compile("(.*/)sites/.*");                 // creates a regular expression
 Matcher workareaMatcher = workareaPattern.matcher(context.getSite().getPath()); // evaluates the regular expression against the site's path /default/main/com/WORKAREA/com/sites/europe
 String dictionaryRelativePath = context.getParameterString("dictionary");
 if (workareaMatcher.find() && dictionaryRelativePath != null && !dictionaryRelativePath.equals("")) {
 String workareaPath = workareaMatcher.group(1); // grab the content of the first set of brackets
 String dictionaryFilePath = workareaPath + dictionaryRelativePath;          // computes the absolute path to the dictionary DCR file
 try {
 InputStream dictionaryFileInputStream = fileDal.getStream(dictionaryFilePath);    // opens the dictionary as a stream for reading
 Document dictionaryXMLDoc = Dom4jUtils.newDocument(dictionaryFileInputStream);    // and creates an XML document from it
 } catch (Exception e) {
 // do nothing
 return (doc);

We now can add the external call to our component in the content.xml box.
 <Datum ID="dictionary" Name="dictionary" Type="DCR">
 <DCR Category="common" Type="dictionary">templatedata/common/dictionary/data/acme.fr</DCR>
 <Object Scope="local">com.acme.components.i18n</Object>

If you save and preview the component, you shall see the translations added to the content XML in the bottom left under the result node. All we have to do is use this in our XSL. The key to doing this is the key (pun intended!).

We add a translation template that will look for a translation for a given key, passed as a parameter:

<xsl:template name="i18n">
 <xsl:param name="key" />
 <xsl:variable name="translation"><xsl:value-of select="/Properties/Data/Result/com.acme.components.i18n.getDictionary/dictionary/entry/key[text()=$key]/../translation" /></xsl:variable>
 <xsl:when test="$translation != '' "><xsl:value-of select="$translation" /></xsl:when>
 <xsl:otherwise><xsl:value-of select="$key" /></xsl:otherwise>

If the translation is found, it returns it, otherwise it returns the original text. This is how the translation is called:

<xsl:call-template name="i18n">
 <xsl:with-param name="key">Hello World!</xsl:with-param>

The main benefit of this solution is that the translations are content managed and re-usable. We’re not compiling anything every time we want to add new stuff and we can also create the dictionary as a DCR from other sources. Overall, I have found this solution to be quite rapid to implement and very elegant.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s