7

I am trying to do a page in different languages with xml/xsl. I want to have only one xml and one xsl. On my page Url I have a parameter pLanguage that I think I can use to look if I have selected English or Dutch.

I tried with this code but I don’t know how I put it together:

First I make variables of all the words who has to been translated like this:

<xsl:variable name="lang.pageTitle" select="'This is the title in English'"/>

To get the pageTitle in the template I now can use

<xsl:value-of select="$lang.pageTitle"/>

I thought to replace the first line of code above by using a if-else statement to test if my choosen language is EN or NL like this:

<xsl:choose>
      <xsl:when test="$choosenLanguage &#61; ‘NL’">
        <xsl:variable name="lang.pageTitle" select="Titel in het nederlands'"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:variable name="lang.pageTitle" select="'This is the title in English'"/>
      </xsl:otherwise>
    </xsl:choose>

But I get the error: java.lang.IllegalArgumentException: can't parse argument number $lang.opdracht

Bigjo
  • 613
  • 2
  • 10
  • 32

3 Answers3

4

Here is a complete example how this can be done in a generic way:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my" exclude-result-prefixes="my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pLang" select="'nl'"/>

 <my:texts>
  <pageTitle lang="en">This is the title in English</pageTitle>
  <pageTitle lang="nl">Titel in het nederlands</pageTitle>
 </my:texts>

 <xsl:variable name="vTexts" select="document('')/*/my:texts"/>

 <xsl:template match="/">
     <html>
      <title>
        <xsl:value-of select="$vTexts/pageTitle[@lang = $pLang]"/>
      </title>
     </html>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on any XML document (not used), the wanted, correct result (the title is generated in accordance with the global/external parameter $pLang) is produced:

<html>
   <title>Titel in het nederlands</title>
</html>

Do note:

It is recommended that all strings be maintained in an XML document that is separate from the XSLT stylesheet file(s). This allows the strings to be modified/added/deleted without changing the XSLT code.

To access the strings from another XML document the code remains almost the same, the only difference is that the argument to the document() function now is the URI to the strings XML document.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • Why not use the `xml:lang` attribute and the `lang($pLang)` function? – DanMan Dec 19 '13 at 21:27
  • @DanMan, Yes, this is perfectly possible. I am not sure if this approach can still be used if we have different strings in the same language but with different focus-- e.g. text for the end-user and for the intermediate user. If this can't be done using just `xml:lang` and the `lang()` function, then the technique in this answer would still be applicable. – Dimitre Novatchev Dec 19 '13 at 22:43
1

If you perform the transformation in your own application you can use another approach. One that requires some coding but leaves your stylesheets less cluttered.

the stylesheet:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stylesheet SYSTEM "i18n/humanreadable.ent">
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"...>
    &text1; &text2;

(You can choose whatever name you fancy for the i18n directory, just keep in mind it plays a special role.)

humanreadable.ent:

<!ENTITY text1 "Hello">
<!ENTITY text2 "world!">

So far this is still a good old & valid XSLT. But while it keeps the stylesheet more readable it does not give you the much desired multi-language support. For that you need to do some coding.

Customize the document builder which you parse the stylesheet file with; assign an entity resolver to it:

Source getStylesheetSource(String stylesheetFilename, EntityResolver entityResolver) throws ... {
    DocumentBuilder docBuilder = getDomFactory().newDocumentBuilder();
    docBuilder.setEntityResolver(entityResolver);
    Document stylesheet = docBuilder.parse(new FileInputStream(new File(stylesheetFilename)));
    return new DOMSource(stylesheet);
}

This entity resolver gets called every time a relative URL/path is encountered when your stylesheet is being parsed into a document.
When this happens check whether the path starts with your magic prefix (your special directory), and translate this prefix into a path pointing to the humanreadable.ent of your desired language.

final String targetLanguage = figureOutDesiredLanguage(...);
EntityResolver entityResolver = new EntityResolver() {
        @Override
        public InputSource resolveEntity(String publicId, String systemId) throws IOException {
            if (pointsToMySpecialFolder(systemId)) {
                String lang = targetLanguage;
                String i18n = insertLangIntoThePath(systemId, lang);
                return new InputSource(new FileInputStream(new File(i18n)));
            }
            return null;
        }
};

Source stylesheet = getStylesheetSource("stylesheet.xslt", entityResolver);
Result result = new SAXResult(...);
Transformer transformer = transformerFactory.newTransformer(stylesheet);
transformer.transform(new DOMSource(inputXml), result);

The drawbacks are obvious: you need to do some coding outside of XML/XSLT, and your XSLT stylesheet is multi-lingual only when used within your super-special application.

The benefit is no extra tag soup in my (already rather dense) XSLT stylesheets.

Jaroslav Záruba
  • 4,694
  • 5
  • 39
  • 58
  • I had consider using entities, but I’m not sure about ordering entities with dynamic strings like dates. Entities would solve the problem with gettext() not being able to translate the same string two or more different ways, but there might be a lot of switching back and forth between the entity file and the stylesheet. – John Carlson Aug 01 '21 at 23:49
  • What I’m think now is entity strings with entity parameters. – John Carlson Aug 01 '21 at 23:50
  • I do not know how to handle the case of reordering text nodes with other nodes in the stylesheet. Reordering only entities seems ok. – John Carlson Aug 02 '21 at 00:57
0

I used quite another solution similar to Dimitre Novatchev's answer, but without the use of document()-function. Instead exslt is used:

<xsl:stylesheet version="1.1"
                xmlns:my="http://example.com/my"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:exsl="http://exslt.org/common"
                extension-element-prefixes="exsl"
                exclude-result-prefixes="exsl">

  <xsl:variable name="i18n">
    <pageTitle lang="en">This is the title in English</pageTitle>
    <pageTitle lang="nl">Titel in het nederlands</pageTitle>
  </xsl:variable>

  <xsl:template match="/">
    <html>
      <title>
        <xsl:value-of select="exsl:node-set($i18n)/pageTitle[@lang = $pLang]"/>
      </title>
    </html>
  </xsl:template>
</xsl:stylesheet>

In case you need to compute specific font names:

<xsl:stylesheet version="1.1"
                xmlns:my="http://example.com/my"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:exsl="http://exslt.org/common"
                xmlns:func="http://exslt.org/functions"
                extension-element-prefixes="exsl func"
                exclude-result-prefixes="exsl func">

  <xsl:variable name="i18n">
    <pageTitle lang="en">This is the title in English</pageTitle>
    <pageTitle lang="nl">Titel in het nederlands</pageTitle>
  </xsl:variable>

  <func:function name="my:get-font">
    <xsl:param name="pLang" />
    <xsl:choose>
      <xsl:when test="$pLang = 'th'">
        <func:result select="'Noto Serif Thai'"/>
      </xsl:when>
      <xsl:when test="$pLang = 'zh'">
        <func:result select="'Noto Serif SC'"/>
      </xsl:when>
      <xsl:otherwise>
        <func:result select="'Noto Serif'"/>
      </xsl:otherwise>
    </xsl:choose>
  </func:function>
  
  <xsl:template match="/">
    <html>
      <title font-family="{my:get-font($vLang)}">
        <xsl:value-of select="exsl:node-set($i18n)/pageTitle[@lang = $pLang]"/>
      </title>
    </html>
  </xsl:template>
</xsl:stylesheet>
AlexS
  • 5,295
  • 3
  • 38
  • 54
  • 1
    *"fully compatible with XSLT 1.0*" Although practically every XSLT 1.0 processor supports it in some way or another, the `node-set()` function is NOT part of the XSLT 1.0 language. In case you missed the point, the `document()` function can be used here in order to avoid the reliance on an extension function. – michael.hor257k Oct 13 '22 at 12:13
  • @michael.hor257k Now I get it. Thank's for the clarification. I will try to adjust my answer accordingly. – AlexS Oct 13 '22 at 13:08