Written by torleik for umbraco 3.0 and archived at
web.archive.comHow to create multilingual sites with only a single navigation-tree, > using 'tabbed translation' and the dictionary.
The has been updated with later information I've come across.
Chapter 0 - Summary of the Multilingual 1:1 site system
The idea of the system, is to have each document in the Umbraco website contain all localized versions of the document text-fields, and have the code determine which fields to show on the page, depending on the selected language. Thus, there will exist only one copy of each document, each translatable by its own. The site will therefore also have exactly the same information architecture across all languages, hence the label: ”1:1 site system”.
The method described here is equally well-suited for creating new multilingual sites and for adding multi-lingual support to sites that are already created in a single language.
The implementation below assumes that there is an “initial language” – a default as you may. This is typically the language that is chosen when a visitor first enters the site, and also signifies the “master-language” for the web-manager.
The 7 steps for creating the multilingual 1:1 site system are these:
- The needed languages are first created. Dictionary items can now be added.
- Each document type will be added a new tab for each language, and new fields for each localizable field needed, one for each language. The localized fields alias-names are added a postfix consisting of an underscore and the language code.
- An XSLT library file is created and included in each XSLT file. This file handles language selection, session-storing and retrieval.
- In every XSLT file, when referring to localizable fields, you will add a concatenation method with the language as parameter.
- You will now add an XSLT extension that can do lookup in the dictionary using a key and a language id.
- Replace all that “hardcoded” text, that are not contained in document fields, with dictionary entries and a call to the XSLT extension just added.
- Finally, you should probably add a language selector, so that visitors may choose their preferred language.
The 7 steps are explained in more detail with examples below.
If you don’t need the dictionary functions, you can skip item 5-7, and in that case, you don’t need to write any code in Visual Studio – only XSLT and HTML code.
Chapter 1 - Create languages
Define the languages you want to use. (You can easily add more later) This is done in the Umbraco client "Settings", under Languages.
You can use the primary languages or languages with variety subtag as you please (e.g. you can use English with code en or English (USA) with code en-US. ) Whichever you choose, just do it everywhere a language-code string is used.
Example: I have chosen 3 languages without the variety subtag. Decide which language is the initial language (the “default” as you may.) I choose English in this case. You may also choose not to have an initial language.
Chapter 2 - Add language tabs to document types
On all document types that needs translation (probably all of them, right?), add a new content tab for each language. If there already is a tab for content, because you are adding localisation to an existing document type, let this tab contain the translatable fields for the initial – “master” language. Each tab should be labelled after the language it is to contain. You can write the full language name here, if you please; no need to use the language-code (except perhaps for saving screen space).
For each field that needs translation, add the same field to each language tab – but call the alias for each language that is not the initial language, the field name plus “_” plus the language-code. Thus, if a text field of type “Textbox Multiple” is called “abstract” on the initial language tab, make another text field of type “Textbox Multiple” on each language tab, alias-naming each “abstract_XX” where XX is the language-code.
Take care, that the fields names are exactly the same and placed in the same order. This will make translations easier for the editors.
Fields that does not need translation (images, links, metadata and so on), you can either place on the content tab for the initial language, or perhaps even better, move them to a “common” tab.
Example: An article contains 4 fields, of which only the “author” should not be translated. The rest are repeated for each tab, adding the language-code postfix to the Danish and Swedish tabs field aliases. When the editor creates a new document of the localization-prepared document-type, he will have a tab for each language, and fields arranged under each tab, with the same names and order across the tabs.
Example: The article from the example above, as seen in the document editor. The initial language also contains the “Author” field. Otherwise, the two tabs are identical in structure and labels. Chapter 3 - XSLT library function
Add an XLST file to the “Developer” section of your Umbraco project. You can call it lang_lib.xslt or another name of your choice. Below is a complete sample of the code that needs to be included:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl=http://www.w3.org/1999/XSL/Transform
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library"
xmlns:kv_trans ="urn:kv_trans"
exclude-result-prefixes="msxml umbraco.library kv_trans">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="initlang">en</xsl:param>
<xsl:variable name="dlang">
<xsl:choose>
<xsl:when test="umbraco.library:RequestQueryString('lang') != '' ">
<xsl:value-of select="umbraco.library:RequestQueryString('lang')" />
</xsl:when>
<xsl:when test="umbraco.library:Session('lang') != ''">
<xsl:value-of select="umbraco.library:Session('lang')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$initlang" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="flang">
<xsl:if test="$dlang != '' and $dlang != $initlang ">
<xsl:value-of select="concat('_',$dlang)" />
</xsl:if>
</xsl:variable>
<xsl:template name="setlang">
<xsl:value-of select="umbraco.library:setSession('lang',$dlang) " />
</xsl:template>
</xsl:stylesheet>
Reference to this XSLT file is to be included on all pages that needs translation. More on that later.
What does it do?
A variable, $dlang, is assigned the resolved language code. This variable can be used for dictionary lookups, hence the “d”. Resolving the language code is done by checking the querystring and the session for a defined language. Notice that the querystring has priority, so that changing the language is as simple as passing a new query parameter.
If the language is not found, it uses the initial language “initlang” defined at the top of the stylesheet. You can here change “en” to a default language-code that fits your needs.
Another variable - $flang – is also set, but only if its not the initial language. The $flang is created by concatenating an underscore and the $dlang, and is used for localized field-value fetches – hence the “f”. (more on this later)
Finally, a named template method “setlang” is defined. This method should be called at the top of all pages that contains the option of language-selection – in practise all pages. To make sure its called for all pages, and only once, place the call in an XSLT used in one of the top-most master templates. The call is simply done with a “call-template” element like this:
<xsl:template match="/">
<xsl:call-template name="setlang" />
...
</xsl:template>
Obviously, the library file must also be included in the XSLT file for the call to work. (See below.)
Chapter 4 - Alter all field references
Now we need to alter the XSLT files to look for the correct field from the displayed document.
First make sure to include the lang_lib.xslt (from step 3) in the beginning of all XSLT files that need translations. You should add it between the Stylesheet element and the first template element.
<xsl:include href="../xslt/lang_lib.xslt" />
You need to substitute each reference to a field with the X-path concat function, adding the languagecode defined in lang_lib.xslt. Example: Say, the alias name of a field is: “ALIASNAME”, we might have this code:
<xsl:value-of select="$currentPage/data[@alias = 'ALIASNAME']" />
- we need to change it to this:
<xsl:value-of select="$currentPage/data[@alias = concat('ALIASNAME',$flang)]" />
The concat function combines the fieldname with the language postfix, exactly as we have created the localized field-alias names in the document type in step 2. Thus the
<xsl:value-of ...
selects the correct translation and uses that. If we use an initial language, the value of $flang is the empty string and the concat method will have no effect, returning the field name without the language postfix, and thus referring to the field value from the “default” content tab - the "initial language".
Chapter 5 - Use the dictionary with an XSLT extension
When you create entries in the dictionary, you do as normally in Umbraco – create the entry using the key and adding a translation for each language.
In this example, the website title is a dictionary item, using the key “sitetitle”. To refer to this key, we want to simply use a library function to look up by key and language. Unfortunately, the current umbraco:library does not support that, so lets just make our own extension and add this support.
Create a Visual Studio project - Class library - and include a reference to the umbraco.dll file.
Create a class to hold the translation code. You could call it “Translations”. Create a public method that will be the method you invoke from the XSLT files. Call it “translate”. My implementation looks like this:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Web;
using System.Xml.XPath;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.language;
// Adds dictionary lookup as an extension for XSLT files. /TORK 2008.04.30
namespace kraftvaerk.umbraco
{
public class Translations
{
/// <summary>
/// Look up a translated string in the Umbraco dictionary using the provided key and language code
/// Returns the translated string if found and not empty. Else, it returns the key and the language
/// surrounded with "*" for easy detection and correction.
/// </summary>
/// <param name="key">The key by which the dictionary entry was made</param>
/// <param name="language">The desired language code- as it appears on the dictionary tabs</param>
/// <returns>The translated string, if found and not empty. returns>
public static string translate(string key, String language)
{
int langid = 1;
if (language.Length != 5) language = "en-US"; // hardcoded default language.
// Reimplement as desired.
Language lang = Language.GetByCultureCode(language);
langid = lang.id;
string trans = GetDictItemByLang(key, langid);
if (trans.Length > 0) return trans;
return ("*" + key + "(" + language + ")" + "*");
}
/// <summary>
/// Lookup the key in the Dictionary, then get the value for the correct language-id
/// </summary>
/// <param name="key">The key by which the dictionary entry was made</param>
/// <param name="languageid">The id of the choosen language</param>
/// <returns>The translated string or the empty string. returns>
private static string GetDictItemByLang(string key, int languageid)
{
try { return new Dictionary.DictionaryItem(key).Value(languageid); }
catch { return string.Empty; }
}
}
}
Please change the namespace :-)
Add the extension declaration to the \config\xsltExtensions.config file.
If you have no other extensions registered, it should look something like this, when you’re done:
<?xml version="1.0" encoding="utf-8" ?>
<XsltExtensions>
<ext assembly="/bin/kv_umb_lib" type="kraftvaerk.umbraco.Translations" alias="kv_trans" />
</XsltExtensions>
Change the assembly name, the namespace and the alias as appropriate to your needs.
Chapter 6 - Replace “hardcoded” text with dictionary calls.
Now, we can add this extension in all XSLT files where we need dictionary translations.
First we need to add the namespace declaration in the stylesheet element:
<xsl:stylesheet
version="1.0"
xmlns:xsl=http://www.w3.org/1999/XSL/Transform
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library"
xmlns:kv_trans ="urn:kv_trans"
exclude-result-prefixes="msxml umbraco.library kv_trans">
...
(The added code is line 6 and
kv_trans
on last line)
Then we can use it this way anywhere in our XSLT file:
<xsl:value-of select="kv_trans:translate('KEY',string($dlang))" />
Where the ‘KEY’ is the dictionary key for the entry, as explained in step 5.
Again, change the namespace and aliasname to what you used above, in step 5.
Chapter 7 - Adding language-selector - You're done!
All we need now is a a language selector. To implement it, we just need a link to the same page. To the Href attribute of this anchor-tag (that is the language selector), we add the querystring: “?lang=XX” where XX is the language code to switch to.
In my example, I use this HTML code in the page-header XSLT-file:
<div class="languageselector">
<a href="?lang=en">ENGLISH</a> |
<a href="?lang=sv">SVENSKA</a> |
<a href="?lang=da">DANSK</a>
</div>
Example: Here we see the result. The mouse is hovering over the “DANSK” link – that switches the site into Danish. Notice the link URL at the bottom. ?lang=da is all it takes to switch.
That’s it!
Translating the site one-to-one and using dictionary items.
To see a real commercial website using the exact technique described above, go visit this website: http://www.arn-swords.com
Best regards! /Torkaj