Internationalization in MODx Revolution

As our company is based in Hong Kong, our clients often require a multi-lingual site. Luckily for us, the MODx team have given us the perfect tools for internationalization, or i18n, in MODx Revolution. In this tutorial I'll show you how we handle i18n, making use of contexts and lexicons. There is two parts to achieving this. First, we create a context for each language, and set up a plugin to handle the request and load the appropriate context. Then, we'll use lexicons to handle any text in our templates, chunks and snippets.

Contexts

Lets start by creating contexts for each language. This will separate your content by language in the resource tree, keeping it nicely organised and allowing us to select content from the appropriate language simply by switching the context. To create a context, select Contexts from the System menu in the MODx manager and click create new. Enter a context key, and optionally a description and save.

The Contexts Menu

Creating a Context

The Plugin

The next step is to give each context a setting containing the Language key (or cultureKey). To add a context settings, just click on the context in the resource tree, and select the Context Settings tab. Now you can enter your content for each language in its respective context.

Context Settings

Next, we need to create a plugin to load the correct context for the selected language. To create a plugin, select the elements tab on the site tree and click the new plugin button.

Creating a Plugin

Now we give our plugin a name (we usually call it gateway, you can use whatever makes sense to you) an optional description and category, and finally the code. For those of you less experienced in writing PHP, don't be daunted, it's a simple switch statement. In our implementation, each language will require its own subdomain (eg en.example.com) except for the default language, as this will be loaded for the primary domain (eg www.example.com). We pull the domain from the http_host option and switch the context accordingly. In this example we're only using two contexts/languages, with the default being Chinese. To add an extra language you just need to add a case (and a subdomain and context to go with it).`

		//make sure our plugin does not run on the mgr context (the manager)
			if($modx->context->get('key') != "mgr"){
				//grab the current domain from the http_host option
				switch ($modx->getOption('http_host')) {
					case 'en.example.com':
						//switch the context
						$modx->switchContext('en');
						//set the cultureKey
						$modx->setOption('cultureKey', 'en');
						break;
					default:
						// Set the default language/context here
						$modx->switchContext('chinese');
						$modx->setOption('cultureKey', 'zh');
						break;
				}
			}
	

Finally, we need to set the plugin to run on the onHandleRequest event by selecting the System Events tab and checking the box for OnHandleRequest, don't forget to save.

Selecting the onHandleRequest event

Lexicons

Now lets move on to Lexicons, the core of i18n in MODx. A lexicon is simply a collection of language entries. A lexicon entry is any piece of text that will appear in your templates, chunks or snippets. In MODx the entries are separated into files by namespace, language and topic. We like to create a namespace for our lexicon entires to separate anything that we create from the core lexicon. To create a namespace, select Namespaces from the System menu and hit the Create New button. Enter a name for your namespace (we generally use the site name here) and a path to the namespace. For the path we like to use the core/components directory which is also where all the namespaces for 3PCs live but you can place it anywhere you see fit. Remember, this is a full path to the directory, so it makes sense to use the {base_path} or {core_path} options as appropriate (eg. {core_path}components/directory_of_namespace). Namespaces Menu Creating a Namespace

Before we create the entries we need to set up the directory structure. So in your filesystem, navigate to the directory of your namespace (create it if you haven't already) and create a directory called lexicon. Inside the lexicon directory we create another directory for each language. In our case one named 'en' and one named 'zh'. Inside that folder we create the lexicon files. The convention for naming these is topic.inc.php so if your topic is cart the file will be cart.inc.php. If you would like to group these further, you could create a directory (eg. store) and have your php file in there. To specify this in your lexicon call (which we'll cover a little further down) you would use directory.filename as the topic (eg. store.cart). Directory Structure

Storing an Entry

Now to create an entry. Inside your topic.inc.php file, we need to add to the $_lang array. The convention for this is $_lang[key] = value. The value can contain HTML, snippet calls or placeholders (passed in the lexicon call).

A lexicon entry where the key is store.your_cart and the value is 'Your cart':

		$_lang['store.your_cart'] = "Your cart";
	

A lexicon entry where the key is store.more_info and the value contains some text with link to more info:

		$_lang['store.more_info'] = "Click here for more info.";
	

A lexicon entry where the key is store.cart_count and the value uses a snippet call to fetch the number of items, followed by the text 'items':

		$_lang['store.cart_count'] = "[[!ShoppingCart? &action=`getCount`]] items.";
	

A lexicon entry where the key is store.hello and the value uses a placeholder containing the name:

	$_lang['store.hello'] = "Hello [[+name]]!";
	
Retrieving an Entry

Lexicon entries can be called in with a MODx tag, or with PHP code.

Here's' how to call a lexicon in template/chunk. This will call the entry with the key store.name from the file namespace_name/cart.inc.php It will use the current cultureKey by default, but if you need to specify a language you could do so by passing the 'language' parameter (eg. &language=`en`).

		[[%store.hello? &topic=`cart` &namespace=`namespace_name`]]
	

In this lexicon call we're setting a placeholder called name which will be available in the entry:

		[[%store.hello? &topic=`cart` &namespace=`namespace_name` &name='Jude']]
	

There's a little more to calling lexicon entries with PHP. First we have to make sure the lexicon service is loaded:

		$modx->getService('lexicon','modLexicon');
	

Now we select a topic. The syntax is namespace:topic so the below would load the topic 'default' from the store namespace:

		$modx->lexicon->load('store:default');
	

Finally, we can use the lexicon method to call an entry. Again this will default to the current cultureKey, but can be specified in the load method by prepending the language (eg. $modx->lexicon->load('en:store:default')). This would get an entry with the key 'store.hello':

		$modx->lexicon('store.hello');
	

This will get an entry with the key 'store.hello' and pass the placeholder 'name' with the value 'Jude':

		$modx->lexicon('store.hello', array('name' => 'Jude'));
	
Tags: MODx, MODx Revolution, MultiLingual
Comments (17)
  1. James :
    Aug 25, 2010 at 10:57 AM

    First post on the new blog!

  2. Florian:
    Aug 26, 2010 at 08:53 AM

    Would you please add RSS to your blog? Thank you.

  3. James:
    Aug 26, 2010 at 09:00 AM

    Hey Florian, Will do - give us a few days and we'll catch up. Thanks for reading!

  4. Jude:
    Aug 26, 2010 at 10:30 AM

    Hey Florian,

    I've just put up the RSS feed now... Enjoy!

  5. daerhiel:
    Sep 18, 2010 at 12:55 PM

    Hello there!
    I've looked through your both sites specified in references and you didn't use en.*.com domain to switch contexts in the 'The Wine Guide' site. Could you please share the basic mechanism there? Cookies? Session variables? This would be very helpful ;)
    Thanks in advance.

  6. James:
    Sep 19, 2010 at 07:14 AM

    Hey daerhiel, you're right that on the wineguide we didn't use the above approach exactly. But we did use it for Shi Wai Yuan, as you'll see here with the two subdomains for each language:

    http://en.shiwaiyuan.com (english), http://www.shiwaiyuan.com (chinese)

    For the wine guide, we used sessions to load lexicons like this:

    [[key? &namespace=`namespace` &topic=`topic` &language=`[[+session_lang]]`]]

    whereby session_lang is a placeholder that has been set by a plugin in the OnHandleRequest event, and corresponds to a cultureKey.

    Having built a few multi-lingual sites now, the method described by Jude above (and by the MODx team) is definitely the recommended way forward. Here's a thread discussing some of the reasons for and against each method:

    http://modxcms.com/forums/index.php/topic,52869.msg306924.html

    Hope that helps. If you have any specific questions you can fire one of us an email on churn@butter.com.hk

    Cheers

  7. Enrique Erne:
    Nov 16, 2010 at 08:06 AM

    This is very exiting! I Found this blog through Bubel plugin http://modxcms.com/extras/package/757 and going to try this soon. Thanks in advance.

  8. Daniel Miguel:
    Nov 16, 2010 at 11:51 AM

    Perfect! Great tutorial :) Now it's easier to create multi-language snippets for MODx and share with the community!

  9. James:
    Nov 16, 2010 at 04:11 PM

    @Enrique Erne: Thanks for posting that link, I'm super excited about that proof of concept. Could solve one of the few remaining issues with multi-lingual content in MODx from a content editors point of view.

  10. Jakob Class:
    Dec 20, 2010 at 11:11 AM

    This tutorial helped me a lot building my first multilingual websites. Thank you!

    For better handling your multilingual sites which where configured like described in this tutorial I implemented an Extra for MODx called Babel.

    You may have a look at it on GitHub: https://github.com/mikrobi/babel

    Cheers, Jakob

  11. Roadie:
    Jan 07, 2011 at 01:42 PM

    Hi guys!
    I've been trying to implement Babel, but no success till now. Jakob, would you please share with us how you did it?
    Thanks! Roadie

  12. James:
    Jan 07, 2011 at 01:47 PM

    @Roadie - not sure if you can wait this long but we're planning to release another update to this post specifically with Babel in around a week. Drop me an email if you can't wait and I can send you some basic basic info sooner than that. (james at butter.com.hk)

  13. Jakob Class:
    Jan 08, 2011 at 11:21 PM

    Due to several request I wrote a very simple article which may help you setting up your ML sites with Babel: http://www.class-zec.com/en/blog/2011/multilingual-websites-with-modx-and-babel.html

    I hope this helps and I'm looking forward to the updated ML post in this blog!

    Cheers, Jakob
    I hope this helps and I'm looking forward to the updated ML post in this blog!

    Cheers, Jakob

  14. Jakob Class:
    Jan 19, 2011 at 05:52 PM

    If you're interested: I wrote an article about setting up a multilingual website with MODx and Babel by using subfolders which is more SEO friendly: http://www.class-zec.com/en/blog/2011/seo-friendly-multilingual-websites-with-modx-and-babel.html

    Cheers,
    Jakob

  15. Jon:
    Aug 17, 2011 at 08:28 AM

    I'm creating a site in both Eng and Chinese using ModX and am having troubles with viewing chinese characters in the modx manager. I just get a whole bunch of ?????????
    Any idea how to allow chinese characters to be viewed?

  16. Dawid:
    Jan 13, 2012 at 04:25 PM

    @JON: You have to change database tables collations from latin1 to utf8 (or utf16)
    You can find help here for example
    http://paulkortman.com/2009/07/24/mysql-latin1-to-utf8-conversion/

  17. CottageStuff:
    May 08, 2012 at 06:56 AM

    Just to let you know where you lost this i18n newbie: "give each context a setting containing the Language key (or cultureKey". The newbie who tries to add that setting is faced with a series of completely blank fields and has no idea what to enter. To make things idiot-proof, there is a stage here that needs to be described in a little more detail.
    Off to see if Jakob can help, coz I am still in the dark, even though I now have a context that appears in the resource tree.





Allowed tags: <b><i><br>Add a new comment:


Share |