0

I'm trying to solve a problem, where I have to translate strings using xslt.

I saw this: XSLT key() lookup and this: XSLT Conditional Lookup Table

but I'm not able to get it to work. I've tried to come up with the minimal example below which shows the problems that I'm facing. The "real" xsl is assembled from code snippets using a build process. This involves some constraints.

The inner structure of the translation lookup tables always is the same, since they are downloaded from a translation tool in flat xml format http://docs.translatehouse.org/projects/translate-toolkit/en/latest/formats/flatxml.html. I can only wrap them into distinct parent nodes which is what i tried using the "lu" namespace. The translation tables for all languages have to be stored inside the xsl, because different generations of xsl with different translations may exist next to each other. So no "sidecar" files.

Until now I can't get the key to work. The output of xsltproc is the following:

Setup Key - Start
German
xsltApplyOneTemplate: key was not compiled
Setup Key - End
de # skipped #
de # failed #

Expected output:

Setup Key - Start
German
Setup Key - End
de # skipped # Übersprungen
de # failed # Fehlgeschlagen

The XML file just needs to contain a root element.

So obviously the way I try to define the key depending on the target language is wrong, but my xsl knowledge has reached its limit now. The language stays the same during the transformation, so the key for all translation lookups has to be set up only once at the beginning.

The xsl Transformation:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns:lu="http://www.my.domain.de/lookup"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:variable name="vLanguageCode">
    <!-- <xsl:value-of select="/root/@language"/> -->
    <xsl:value-of select="'de'"/>
  </xsl:variable>

  <xsl:template match="/">
    <xsl:call-template name="setupKey"/>

    <xsl:call-template name="getLabel">
      <xsl:with-param name="pKey" select="'skipped'"/>
    </xsl:call-template>

    <xsl:call-template name="getLabel">
      <xsl:with-param name="pKey" select="'failed'"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="setupKey">
    <xsl:message>Setup Key - Start</xsl:message>
    <xsl:choose>
      <xsl:when test="$vLanguageCode='DE' or $vLanguageCode='de'">
        <xsl:message>German</xsl:message>
        <xsl:key name="kLanguageDict" match="/lu:de/root/str" use="@key"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message>English (default)</xsl:message>
        <xsl:key name="kLanguageDict" match="/lu:en/root/str" use="@key"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:message>Setup Key - End</xsl:message>
  </xsl:template>

  <xsl:template name="getLabel">
    <xsl:param name="pKey"/>
    <xsl:variable name="vResult">
      <xsl:value-of select="key('kLanguageDict', $pKey)/@str"/>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="$vResult!=''">
        <xsl:value-of select="$vResult"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$pKey"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:message>
      <xsl:value-of select="concat($vLanguageCode, ' # ', $pKey, ' # ', $vResult)"/>
    </xsl:message>
  </xsl:template>


  <lu:de>
    <root>
      <str key="skipped">Übersprungen</str>
      <str key="failed">Fehlgeschlagen</str>
    </root>
  </lu:de>

  <lu:en>
    <root>
      <str key="skipped">Skipped</str>
      <str key="failed">Failed</str>
    </root>
  </lu:en>

</xsl:stylesheet>

Additions in response to the answer from @michael.hor257k:

  1. Thank you. I didn't know that. So this means that I can't selectively define a key depending on language?
  2. The translation system originally has one key at the top level and a translation table with interleaved entries for each language. It uses a double index (language+id) to look up the values.

I am trying to find a solution where I can embed the xml files returned by the translation management system (weblate) directly into the xsl without having to modify them. Unfortunately it looks like I'm limited in what I can get back (only default nodes and attributes).

This is the core of the original working translation lookup code:


  <xsl:variable name="vLanguageDict" select="document('')/*/lu:strings"/>
  <xsl:key name="kLanguageDict" match="lu:string" use="concat(@lang,@id)"/>

  <xsl:template name="getLabel">
    <xsl:param name="pKey"/>
    <xsl:variable name="vResult">
      <xsl:for-each select="$vLanguageDict">
        <xsl:value-of select="key('kLanguageDict', concat($vLanguageCode,$pKey))/@value" />
      </xsl:for-each>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="$vResult!=''">
        <xsl:value-of select="$vResult"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$pKey"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

<lu:strings>
  <lu:string lang="DE" id="skipped" value="Übersprungen"/>
  <lu:string lang="EN" id="skipped" value="skipped"/>
  <lu:string lang="DE" id="failed" value="Fehlgeschlagen"/>
  <lu:string lang="EN" id="failed" value="failed"/>
</lu:strings>

Lübbe Onken
  • 526
  • 2
  • 12
  • Please post a [mcve] including an input and the expected output. – michael.hor257k Jul 29 '21 at 14:56
  • `xsl:key` needs to be a top-level declaration. – Martin Honnen Jul 29 '21 at 14:58
  • This is my minimal example. The xml file just needs to contain any root element. Added expected output. – Lübbe Onken Jul 30 '21 at 07:41
  • I am confused regarding what you want. Do you want to have a single list of all entries (as shown in your newly added example) and use a single key to select the value based on the input word AND the output language? Or do you want to have a separate dictionary for each language (or more precisely, for each pair of languages)? – michael.hor257k Jul 30 '21 at 08:03
  • We are coming from the second example, which works. In order to integrate the xsl into our translation workflow, I have to somehow integrate the files returned from the translation system. So I have to work with n separate tables, one for each language. My next step would have been to define one key for each language and switch the key, like in your example below. I'll try to get ahead with that. Thanks! – Lübbe Onken Jul 30 '21 at 08:53

1 Answers1

1

There are two mistakes in your XSLT stylesheet that immediately jump out:

  1. The xsl:key element is allowed only at the top level, as a child of the xsl:stylesheet element.
  2. In XSLT 1.0, keys operate only on the current document. If you want to lookup from the stylesheet itself, you must change the context to the stylesheet document before calling the key() function. Here are two examples: https://stackoverflow.com/a/32440143/3016153
    https://stackoverflow.com/a/30188334/3016153

I am afraid that's about all that can be said without a reproducible example.

--- added ---

So this means that I can't selectively define a key depending on language?

You cannot define a key conditionally - but you can define more than one key and select the one to use based on the specified language. Here's a simplified example:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dict="http://example.com/dict">
<xsl:output method="text" encoding="UTF-8"/>

<xsl:key name="de" match="dict:de/entry" use="@key" />
<xsl:key name="en" match="dict:en/entry" use="@key" />

<xsl:param name="input">skipped</xsl:param>
<xsl:param name="lang">de</xsl:param>

<xsl:template match="/">
    <xsl:value-of select="$lang"/>
    <xsl:text> # </xsl:text>
    <xsl:value-of select="$input"/>
    <xsl:text> = </xsl:text>
    <!-- switch context to stylesheet in order to use key -->
    <xsl:for-each select="document('')">
        <xsl:value-of select="key($lang, $input)"/>
    </xsl:for-each>
</xsl:template> 
    
<dict:de>
    <entry key="skipped">Übersprungen</entry>
    <entry key="failed">Fehlgeschlagen</entry>
</dict:de>

<dict:en>
    <entry key="skipped">Skipped</entry>
    <entry key="failed">Failed</entry>
</dict:en>

</xsl:stylesheet>

Applied to any XML input, this will return:

Result

de : skipped = Übersprungen
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Thanks a million. It is so easy, when it's done right. Now I understand what "" stands for ... If you work with xslt only once every five years, like I do, you lack a lot of basic knowledge – Lübbe Onken Jul 30 '21 at 11:15