MODx multiple language site

Although it’s fairly easy to create a multi-lingual site with chunks and template variables alone it’s hardly ideal, to say the least. For most pages there will be a lot of unused template variables, the manager area will start to look like a mess.

Better then if we can create pages in several languages whose titles and bodies can be reused with the same HTML. This can be achieved through a snippet and below I will show how, but first some prerequisites/instructions.

1.) Install MODx, preferably really hardcore without TinyMCE (I did, WYSIWYG definitely has no place in the world we’re trying to create here).

2.) Turn on pretty URLs and allow duplicate aliases.


3.) Create a hierarchy looking like this:
  Site Page
   en
    Home
     Page 1
     Page 2
   se
    Home
     Page 1
     Page 2

I’m using se here since it’s my primary language.

Now the point is to be able to use the same HTML in both languages, hence we will use a chunk for the Home page. In this chunk we will use repeat calls to a snippet to do the translations. The chunk will be called home. In the body of both Home pages there will only be {{home}}, nothing else, this effectively turns these pages into a kind of advanced container.

The chunk could look something like this:

[[translate?]]
<div class="left">
 <div class="title">
  <h1>[[translate? &article=`page1` &field=`pagetitle`]]</h1>
 </div>
 <div class="body">
  [[translate? &article=`page1` &field=`content`]]
 </div>
</div>
<div class="left">
 <div class="title">
  <h1>[[translate? &article=`page2` &field=`pagetitle`]]</h1>
 </div>
 <div class="body">
  [[translate? &article=`page2` &field=`content`]]
 </div>
</div>

When we access the page through en/Home.html we will see:

<div class="left">
 <div class="title">
  <h1>The title of page 1</h1>
 </div>
 <div class="body">
  The body of page 1
 </div>
</div>
<div class="left">
 <div class="title">
  <h1>The title of page 2</h1>
 </div>
 <div class="body">
  The body of page 2
 </div>
</div>

And when using se/Home.html we will see:

<div class="left">
 <div class="title">
  <h1>Sida 1s titel</h1>
 </div>
 <div class="body">
  Sida 1s brödtext
 </div>
</div>
<div class="left">
 <div class="title">
  <h1>Sida 2s titel</h1>
 </div>
 <div class="body"> 
  Sida 2s brödtext
 </div>
</div>

Which of course is Swedish. So now the question is, what would the translate snippet look like to be able to accomplish these things?

<?php
if(empty($article) && empty($GLOBALS['cur_articles'])){
	$GLOBALS['translation_depth'] = empty($depth) ? 3 : $depth;
	$query = "SELECT * FROM " . $modx->getFullTableName('site_content') . "WHERE isfolder = 1";
	$ar = $modx->db->makeArray( $modx->db->query($query) );
	$cur_lang = array();
	foreach(explode('/', $_SERVER['REQUEST_URI']) as $uri_part){
		foreach($ar as $lang){
			if($lang['pagetitle'] == $uri_part){
				$cur_lang = $lang;
				break;
			}
		}
	}
	$cur_level = $GLOBALS['cur_articles'] = $modx->getDocumentChildren($cur_lang['id'], 1, '0', 'id, pagetitle, description, alias, parent, content');
	for($i = 0; $i <= $GLOBALS['translation_depth']; $i++){
		foreach($cur_level as $a){
			$cur_children = $modx->getDocumentChildren($a['id'], 1, '0', 'id, pagetitle, description, alias, parent, content');
			$tmp = array();
			foreach($cur_children as $cur_child){
				$GLOBALS['cur_articles'][] = $cur_child;
				$tmp[] = $cur_child;
			}
		}
		$cur_level = $tmp;
	}
}else{
	foreach($GLOBALS['cur_articles'] as $a){
		if($a['alias'] == $article){
			return $a[$field];
		}
	}
}

And there you have it, just copy paste into a new snippet you name translate. Let’s walk it:

1.) First we test if we have an article parameter (remember the empty call [[translate?]] at the top of {{home}}?).

2.) If we don’t have an article parameter we begin with setting the default depth to 3 (we use this cap in order for the code to execute faster so we don’t have to do expensive recursion). Anyway the goal here is to fetch basically all articles under any given language in order to keep them in memory so we don’t have to do a million roundtrips to the database.

3.) We get all folders (don’t go and name some folder or page en or se lower down in the hierarchy in another language!).

4.) We loop through the uri parts (one of them must contain en or se). When one of them match one of the folders we lock on to it and stop. The reason we can do this is due to the fact that the language will come first, before any folders that might exist lower down in the tree, that’s the reason we will always get the the correct language and not some other folder first.

5.) Once we have the correct language in the $cur_lang variable we get all its children on the level below.

6.) Finally we loop through all three levels (or more or less if set explicitly through the depth parameter) and get all children for use in the translation logic later on (through the $GLOBALS[’cur_articles’] array). Note that we are not using recursion which would be my primary algorithm choice if I wasn’t doing this in PHP :(

7.) When we want to translate something we will call the code in the last else clause at the bottom. If we have the correct alias we return the value in the $field slot (above we use content or pagetitle exclusively). It should now be clear why we need to allow duplicate aliases, it will still be ok as long as they belong to different languages.

Through the above scheme, translating a site to an arbitrary amount of languages becomes a breeze, no more screwing up the HTML in some clunky web editor, or having to redo various kinds of markup 6 times, once for each language.

I hope I’ve managed to convey the awesomeness of MODx’s basic architecture here, if you’re HTML and PHP savvy you can accomplish a lot of stuff through very little effort. The above snippet took me an hour or two and it enabled me to achieve exactly what I wanted: A site with the content and the markup completely separated for easy editing without any fear that a translator will screw up the layout.

If you hadn’t really grasped the meaning of CMS framework before, then this article should’ve made it perfectly clear :)

Related Posts

Tags: , , ,