The Source for Java Technology Collaboration
User: Password:



Davor Cengija

Davor Cengija's Blog

Luxor localization

Posted by dcengija on March 23, 2004 at 06:32 AM | Comments (2)

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.


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • User interface tools
    You might also want to take a look at the UICompiler for Java project. http://uic.sf.net/

    "The UICompiler is designed to be used as both a rapid prototyping tool for your GUIs as well as a superior approach to GUI building that well known players as NetBeans and JBuilder simply fail to address."

    Posted by: ktukker on March 23, 2004 at 02:03 PM

  • Luxor team is working on localization
    Please visit this URL: Luxor mailing list and read what Gerald Bauer says about lozalization efforts in Luxor. Quote:
    More than a year ago I
    started work on adding internationalization (i18n)
    support using locale-sensitive string tables (property
    like name-value pairs)so you no longer need to
    hard-code your labels or values but instead you can
    use Velocity-style keys/variables (e.g.
    $mybutton.label and so on).

    See
    http://cvs.sourceforge.net/viewcvs.py/luxor-xul/ramses-intl
    for details.

    Posted by: dcengija on March 24, 2004 at 02:16 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds