Perhaps a more efficient use of the key. You define a key using a top-level element outside of your templates
<xsl:key name="langByCode" match="lang" use="@id" />
Then in a loop you can just say
<xsl:when test="@lang"> <xsl:value-of select="key('langByCode', @lang)" /> </xsl:when>
But generally speaking, XSLT's more natural approach to all of this will be to use pattern matching instead of for-each and if :
<xsl:template match="item[@lang]"> <xsl:value-of select="key('langByCode', @lang)" /> </xsl:template> <xsl:template match="item"> <xsl:text>No language set</xsl:text> </xsl:template>
Using these templates, you can make <xsl:apply-templates select="/root/items/item" /> and automatically select the appropriate template for each element. The rule is that it will use the most specific template, therefore item[@lang] for those elements that have the lang attribute, and the usual item for those who do not.
The third option is a little trick I learned on SO to put a full if / else check in one XPath expression
<xsl:value-of select=" substring( concat('No language set', key('langByCode', @lang)), 1 + (15 * boolean(@lang)) )" />
The trick here is that boolean(@lang) if it is treated as a number, 1 if the lang attribute exists and 0 if it is not. If there is lang="EN" , let's say we build the string "No language setEnglish" , and then take the substring starting with the 16th character, which is equal to "English" . If the lang attribute is missing, we build the string "No language set" (because the string value of the empty node set is an empty string) and take the substring starting with the first character (i.e., the whole string).
You can use the same trick with other attributes, for example. Suppose we have an optional color attribute and it wants to say "No color specified" , if it is missing, you can do this with
<xsl:value-of select="substring( concat('No color specified', @color), 1 + (18 * boolean(@color)) )" />