Table of contents:
Magnolia Content Internationalization And Localization Support
Magnolia 3.5 offers content internationalization (i18n) and localization (l10n) support in a pluggable manner which allows you to customize the applied i18n or l10n strategy (note that i18n and l10n use exactly the same concept, thus they're used interchangeably here).
Prerequisites
- using fresh Magnolia EE >= 3.5.2 on Tomcat 5.5.x
- demo uses i18n a single site tree (in contrast the other possible strategy of using multiple site trees) but duplicates nodes for each enabled locale
- demo shows i18n for sample text-image paragraphs only
Configuration
Enabling default i18n content support
Freshly install Magnolia, including the samples module.
Follow the instructions below to see the i18n feature in action:
- duplicate the node "text" of the dialog /modules/samples/dialogs/samples-textimage/tabText and name it "text_de" (or generally "fieldname_language_COUNTRY"). You probably also might want to change the value of the "label" node data to "Content (de)".

In general the name has to follow the pattern "fieldname_language_COUNTRY", since this pattern is used by the <cms:out> tag to print the internationalized value automatically. However, the suffix "_language_COUNTRY" has to match the node names you specify in config:/server/i18n/content/locales, so if you do not put them there with the "_COUNTRY" denominator than leave it out here as well.
- enable i18n content support by setting config:/server/i18n/content.enabled to true

Now, i18n content support is enabled using the DefaultI18nContentSupport implementation to map URLs to a specific locale. The default implementation looks for URL path snippets that identify a locale right in between of the web applications context path and the requested Magnolia page handle (e.g. the implementation will recognize the URL http://localhost:8080/magnoliaAuthor/de/help.html as to be served in the German language).
Note: only those locales that are configured under config:/server/i18n/content/locales (and those that are enabled) will be recognized by the default implementation. (By default there are two pre-configured locales in there: English and German.) You can configure the fallback locale for non-recognized locales with the config:/server/i18n/content.fallbackLocale property.
Next, edit the "help" page that comes with the samples module, create a new "Samples: Text Image" paragraph and put in some content in English content in the first text field and some text in German language into the text field we created before (both in the same tab).

Then point your browser to http://localhost:8080/magnoliaAuthor/de/help.html and http://localhost:8080/magnoliaAuthor/en/help.html and observe the change of the newly created paragraph that displays the content of the appropriate text field.
It should look like the following:

Using a custom i18n support implementation
If you want to use another implementation to determine the language requested by the user, you can simply set the implementation class via the config:/server/i18n/content.class property. E.g. if you want to pick up the locale from the HTTP request sent by the client's web browser instead of using different URL patterns you can use the implementation class "info.magnolia.module.i18n.RequestLocaleAwareI18nContentSupport" (see below). 
Ensure you have this implementation class in the classpath of your web application. It is not delivered by default with Magnolia 3.5.2, so you have to compile it yourself:
 | This implementation is not part of any Magnolia release yet. It is meant for testing/demonstration purposes only. |
/**
* This file Copyright (c) 2008 Magnolia International
* Ltd. (http: *
*
* This file is dual-licensed under both the Magnolia
* Network Agreement and the GNU General Public License.
* You may elect to use one or the other of these licenses.
*
* This file is distributed in the hope that it will be
* useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
* Redistribution, except as permitted by whichever of the GPL
* or MNA you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or
* modify this file under the terms of the GNU General
* Public License, Version 3, as published by the Free Software
* Foundation. You should have received a copy of the GNU
* General Public License, Version 3 along with this program;
* if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 2. For the Magnolia Network Agreement (MNA), this file
* and the accompanying materials are made available under the
* terms of the MNA which accompanies this distribution, and
* is available at http: *
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.module.i18n;
import info.magnolia.cms.i18n.DefaultI18nContentSupport;
import info.magnolia.context.MgnlContext;
import info.magnolia.context.WebContext;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
/**
* I18n content support that uses the browsers locale to determine the user's language. Does not transform the requested
* URI.
* @author vsteller
* @version $Id$
*/
public class RequestLocaleAwareI18nContentSupport extends DefaultI18nContentSupport {
/**
* Determines the locale from the browser and returns a locale that only contains the language code but not the
* country. Country specific locales are omitted.
*/
public Locale determineLocale() {
final HttpServletRequest request = ((WebContext) MgnlContext.getInstance()).getRequest();
return new Locale(request.getLocale().getLanguage());
}
/**
* Returns the same URI as passed to this method since this I18n content support does not require rewriting of URIs.
*/
public String toRawURI(String uri) {
return uri;
}
/**
* Returns the same URI as passed to this method since this I18n content support does not require rewriting of URIs.
*/
public String toI18NURI(String uri) {
return uri;
}
}
Having that implementation in place you can just switch the language of your browser to match "en" or "de" and see the same results as before when accessing the URL http://localhost:8080/magnoliaAuthor/help.html(note that with this implementation the URI is independent of the displayed locale now).
The following picture shows the page with German locale enabled: 
And this one with English locale enabled:

Note about l10n support and the default implementation
As stated before you can also use the exactly same concept for localization. For instance, you can create two different locales in the supported content locales configuration section: one called "us" and another one called "ca", for both you set the country to either "US" or "CA" and leaving language as "en". The default implementation now assumes the locales to be identified by "/en_US/". Also, the <cms:out> tag looks for nodes which name ends with "_en_US" to print the value for the specific country. This looks not very nice. Note that locale strings are case-sensitive. However, with the pluggable architecture of the i18n and l10n support, custom implementations can change this behavior.
Options
The solution outlined above has two main disadvantages
- All languages need to have exactly the same paragraphs
- Dialogs for complext paragraphs get rather big
There is a simple, unobtrusive modification that does not have these two disadvantages and that is having one content node collection for each supported language.
The modifications you have to do are rather small. In your template instead of hardcoding the content node collection name you make it dependent on the locale. The easiest way for this is an EL function.

<cms:contentNodeIterator contentNodeCollectionName="mainColumnParagraphs">
<cms:editBar adminOnly="true" />
</cms:contentNodeIterator>
<cms:newBar contentNodeCollectionName="mainColumnParagraphs" paragraph="${module.paragraphs.mainColumn}" />
<cms:contentNodeIterator contentNodeCollectionName="mainColumnParagraphs${mytld:localePostfix()}">
<cms:editBar adminOnly="true" />
</cms:contentNodeIterator>
<cms:newBar contentNodeCollectionName="mainColumnParagraphs${mytld:localePostfix()}" paragraph="${module.paragraphs.mainColumn}" />
public static String getLangaugePostfix() {
I18nContentSupport i18nSupport = I18nContentSupportFactory.getI18nSupport();
if(i18nSupport != null){
Locale locale = i18nSupport.getLocale();
String language = locale.getLanguage();
if (!language.isEmpty()) {
return "_" + language;
}
}
LOG.warn("no locale set");
return StringUtils.EMPTY;
}
<function>
<name>localePostfix</name>
<function-class>com.me.MyFunctions</function-class>
<function-signature>String getLangaugePostfix()</function-signature>
</function>