The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


Luxor localization

Posted by dcengija on March 23, 2004 at 6:32 AM PST

Luxor is a useful tool: it really increases your productivity. Usage is simple: define your user interface in an XML file, add some Java meat around it and you have it running. Everything's nice and clean as long as you have only one human language to support.

Part of an XML user interface definition
<vbox >
	<spacer style="height:10px;" />
	<button command="test_users"  label="Test Users" 
            style="height:35px;"/>
	<spacer style="height:5px;" />
	<button command="test_groups" label="Test Groups"  
            style="height:35px;"/>
	<spacer style="height:5px;" />
	<button command="test_modules" id="button7" 
            label="Test ACL" enabled="false"  
            style="height:35px;"/>
	<button command="test_parentgroups" id="button8" 
            label="Test Application PARENT GROUPS" enabled="false"  
            style="height:35px;"/>			
	<button command="test_calendar" id="button9" 
            label="Test Calendar" enabled="false"  style="height:35px;"/>			
	<choice list="lista" />
</vbox>	

As soon as you introduce another language, you have problems. As shown in the XML snippet above, Luxor uses label, value and other attributes to assing the text to be displayed in the GUI. Unfortunatelly, those text values are hard coded and your only option is to provide different set of XML definitions for each human language you want to support. Please note that noone said 'different XML files', but 'different XML definitions', and that's the place where XSLT jumps in.

The approach we took was to translate XML definitions on the fly, using a simple XSLT stylesheet. To achieve that, we needed to do four things: 1. Edit XML definitions and add a custom attribute to those elements you want to translate, 2. Create an XSLT stylesheet which will transform XML definitions, 3. Create your translation file in the language of your choice and 4. Edit Luxor's code and glue it all together.

Changing XML definitions

We decided to add another attribute, tranid to each element we want to translate. So, for example, first <button> element from the XML snippet shown above will look like <button command="test_users" tranid="btn_testusers" label="Test Users" style="height:35px;"/> etc. Of course, those tranids need to unique accross your application.

XSLT stylesheet

The XSLT stylesheet is pretty simple: it copies all elements which don't have tranid attribute, and for some elements which might have it (button, label and some other, not shown here) it copies all attributes except value or label or tooltip, depending on the element. Finally, XML translation file is read and the appropriate values in another language are added.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:param name="filename"/>

  <xsl:template match="button|label">
    <xsl:variable name="tranid" select="@tranid"/>
    <xsl:choose>
      <xsl:when test="not($tranid)">
        <xsl:copy>
          <xsl:copy-of select="@*"/>
          <xsl:apply-templates/>
        </xsl:copy>
      </xsl:when>
      <xsl:otherwise>
        <xsl:copy>
          <xsl:choose>
            <xsl:when test="name() = 'label'">
              <xsl:copy-of select="@*[not(name()='value')]"/>
              <xsl:attribute name="value">
                <xsl:value-of 
                  select="document($filename)/translations/translation[@key = $tranid]/value"/>
              </xsl:attribute>
            </xsl:when>
            <xsl:when test="name() = 'button'">
              <xsl:copy-of select="@*[not(name()='label' or name()='tooltip')]"/>
              <xsl:attribute name="value">
                <xsl:value-of 
                  select="document($filename)/translations/translation[@key = $tranid]/value"/>
              </xsl:attribute>
              <xsl:attribute name="tooltip">
                <xsl:value-of 
                  select="document($filename)/translations/translation[@key = $tranid]/tooltip"/>
              </xsl:attribute>
            </xsl:when>
          </xsl:choose>
          <xsl:apply-templates/>
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>
XML translation file

The translation file is very simple: keys, values and that's it. This sample translates "Test Users" to Croatian language.

<?xml version="1.0"?>
<translations>
	<translation key="btn_testusers">
		<value>Testni korisnici</value>
	</translation>
          <-- etc -->
</translations>
Edit Luxor's code

Find out where Luxor reads its resources and just after an XML definition is read and just before it is sent further to the Luxor's system, transform it. The example is shown below. Necessary steps are:

  • Instantiate TransformerFactory and load the stylesheet
  • Assign the appropriate translation file
  • Do the actual transformation
  • Procede with transformated InputStream
Of course, this code could be written better: possible FileNotFound and other exceptions need to be handled, translation file name will almost certainly be read from some configuration file, stylesheet and translation file could be read from a jar file, TransformerFactory could be static field etc, but for this showcase this approach better shows what should be done.

class luxor.core.XulLoader
    public void load(String name) {
        try {

            // XSLT
            TransformerFactory tFactory = TransformerFactory.newInstance();
            Source xslSource =
                new StreamSource(new File("c:\\temp\\startup\\trans.xsl"));
            Transformer transformer = tFactory.newTransformer(xslSource);

            String transFilename = "translations_hr.xml"; // hrvatski
            transformer.setParameter("filename", transFilename);

            T.debug("loading xul file " + name);
            InputStream in = _loader.getResourceAsStream(name);

            Source xmlSource = new StreamSource(in);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            transformer.transform(xmlSource, new StreamResult(baos));

            InputStream inTrans = new ByteArrayInputStream(baos.toByteArray());

            SAXBuilder builder = new SAXBuilder();
            Document doc = builder.build(inTrans);

            Element xul = doc.getRootElement();
            createElements(null, xul);
            T.debug("xul file " + name + " successfully loaded");
        }
        catch (JDOMException jex) {
            Xul.Error.XUL_PARSING_EXCEPTION(name, jex);
        }
        catch (TransformerException tce) {
            T.error(tce.getMessage());
        }
    }

And that's it. Build your custom luxor.jar and use it.

Another approach is to translate XML definitions off-line and make different builds for each language, but that would only confuse our customers so we decided not to do it.

On the other hand, Swixml does the same thing and has localization and internationalization already incorporated.

Related Topics >> Java Desktop      
Comments
Comments are listed in date ascending order (oldest first)