Complete guide to multilingual MODX

$locale = $modx->getOption('locale');
setlocale(LC_ALL, $locale);

Once I used some guide on MODX internationalization. When I started configuring multilingual setup on my own website, surprisingly was unable to find the doc. Some of the official URLs led to the room #404, some forum advises were misleading or just outdated in . So I decided to compile a complete doc on MODX internationalization from advises that worked for me.


Don't forget to save a backup before going to the next level! So if you lose the battle against Multilingual Monster you don't have to begin from very start :)

Install Babel and XRouting

Find Extras → Installer item of the Modx's admin panel Main Menu. Click Download Extras and search for XRouting. It resides in the Internationalization section. It's job is switching contexts depending on the URL demanded by user. Last time it was updated in 2015. There are also few choices from 2016 and 2017. So they are all are, let's say... mature. May be they are so good that do not need further updates... Anyway, there is a choice.

Package Management Screenshot

Then click Download, then click Back to Package Manager, find the newly downloaded package in the manager and click Install. That's it.

Make the same steps with Babel. Here you may provide some basic configuration:

Babel initial setup

The first field contains a list of contexts Babel will serve. You may do it later at the respective babel.contextKeys system setting. Content of a third field goes to a babel.syncTvs system setting. These template variables will be synced accross your contexts.

Create contexts and system settings

MODX provides you with two contexts out of the box: mgr and web. For the back and frontend respectively.

MODX default contexts

So the next step is to create contexts for each language. Hit Create New and fill required fields:

New context creation

Now you have to see your newly created context root in a side tree view of resources:

New context in resources tree

It's only a start. Now you have to set up system settings that will be used within the context. That's why we needed different contexts for each language in the first place. To do that click with right mouse button on the context and select Update Context item of menu:

Update context

The complete list of settings:

System settings for context

My values are just for demo, use your own ones.

Don't miss anything. I thought that since http_host is the same for all contexts, it will be taken from common space and skipped it. Then I wasted a lot of time searching for a fix for 404-error returned on URLs provided by [[BabelTranslation]] before figuring out that this setting is mandatory for each of the contexts.

Translate resources

Now you are ready to make translations for resources. Click respective button Save menu item:

Create translations

Edit templates

Now look at your newly translation resource by clicking View button. If you page looks as it should to, then skip to the next section. If you have broken CSS, malfunctioning JS or media not being loaded or something of that sort, then you have to correct paths to resources. Since there is uncountable ways of creating page markup, I will draw your attention to the settings we created for our contexts.

But first you might wish to set up server locale with PHP, if you are presenting dates or other localized data for your readers. For this a snippet in the very start of the template with something relevant for you:

$locale = $modx->getOption('locale');
setlocale(LC_ALL, $locale);

We took this $locale variable from the System Setting you set for your context earlier. Remember?

Now check why resources are not being loaded. Most obvious reason is that you used relative paths, but base URL changed for the given context:

<base href="[[++site_url]]">

So you have somehow give proper paths for your page's resources. Here is one of possible ways:

<script defer src="[[++server_protocol]]://[[++http_host]]/[[++tpl]]/js/site.min.js"></script>

lexicon for you to help

Lexicon is well documented. Please read this article. I'll wait here.

Welcome back :)

I created my own namespace. I'm afraid it could be overwritten on updates... Though not sure about that.

Name spaces

Now you may add lexicon records like that:

Lexicon management

Other considerations

Thoroughly look through the page's code to look for possible improvements. Some of the candidates are: lang attribute of html tag, twitter and opengraph description of a page, breadcrumbs etc.

Add language switch to frontend

Just like that: Babel.BabelLinks.

Help to discover yourself

Alternative links

Google suggests using hreflang to help them properly index your website. For this you may use different conditional use of [[BabelTranslation]] snippet. I failed. Since it was months ago I don't remember what exactly went wrong. So I updated the snippet for my needs and saved under custom name:

$babel = $modx->getService('babel', 'Babel', $modx->getOption('babel.core_path', null, $modx->getOption('core_path').'components/babel/').'model/babel/', $scriptProperties);

/* be sure babel and babel TV is loaded */
if (!($babel instanceof Babel) || !$babel->babelTv)

$result = '';
$id = $modx->resource->get('id');
$linkTemplate = '<link rel="alternate" hreflang="%1$s" href="%2$s">';

$linkedResources = $babel->getLinkedResources($id);

if (isset($linkedResources) && is_array($linkedResources) && count($linkedResources) > 1) {
    foreach($linkedResources as $context => $id) {
        $resourceObject = $modx->getObject('modResource');
        $url = $modx->makeUrl($id, "", "", "full");
        $result .= sprintf(
            $context == 'web' ? 'en' : $context,
        ) . "\n";
    $result .= sprintf(
        $modx->makeUrl($linkedResources['web'], "", "", "full")
    ) . "\n";

return $result;


I use [[GoogleSiteMap]] to generate sitemaps. For some reason translations were missing on the map provided by the snippet. The responsible part of [[GoogleSiteMap]] snippet starts with following lines:

// Set context(s)
$context = array_filter(array_map('trim', explode(',', $modx->getOption('context', $scriptProperties, $modx->context->get('key'), true))));

This system setting is empty somehow. Not sure is it set manually or dynamically by some code, so I made a custom replacement:

New system setting for GoogleSiteMap

If something of this did not work for you, do not hesitate to contact me.