Multilingual Setup For Expression Engine

Toby Stokes
4th July '14

When researching possible approaches and implementations to a multilingual ee site, I discovered that there are a few good articles online already. I found myself taking tips and snippets from several of these sources whilst I was doing some updating for the latest ee version. I also had a few gotcha moments of my own, so I felt compelled to combine what I learnt in those articles along with my own lightbulb moments here.

Firstly, the key blog posts I referred to at the start of this process, of which without we wouldn't be here:

Set Up

The basic premise, when creating a multilingual site in ee is to use a subfolder for each language,


… but we don't want that to necessarily work as a normal ee segment, as all the languages will share templates and content.

So, the directory structure looks like this:


The root index.php file is no longer the default ee index.php, it just deals with routing to the appropriate language version.

I used an approach where a cookie was set for returning visitors that remembered their language choice, or otherwise defaulted back to their browser locale. However, you'll still need to set a fallback:

// previous visits will hopefully have lang cookie set. 
// if no cookie, try browser lang

$lang = (isset($_COOKIE["lang"])) ? 
	$_COOKIE["lang"] : substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
	switch ($lang) {
		case 'en':
			header('Location:  /en/');
		case 'de':
			header('Location:  /de/');
		case 'fr':
			header('Location:  /fr/');
			header('Location:  /en/'); // nothing going. give up use english.
} ?>

Then, the index in the language folder sets a few variables:

$assign_to_config['global_vars']['language_name'] = 'English';	// Displayed to user in language switch nav
$assign_to_config['global_vars']['language_locale'] = 'en_GB';	// switches php context
$assign_to_config['global_vars']['language_code'] = 'en';		// our folder name and quick reference
$assign_to_config['site_url'] = getenv('HTTP_HOST') . "/en/";	// tell ee our folder name isn't a url segment
setcookie("lang", "en", time()+94608000, "/", $_SERVER['SERVER_NAME']);	// save user choice in cookie

require_once('../engine.php'); // then reinclude ee's usual index file

You will need to find something else to call ee's standard index file, as index.php is taken. But apart from that, it can be included without modification into all languages.

Disclaimer: Possibly, assuming you are setting all your paths in your config file using environment variables, not in the db, but that's pretty much essential for any type of portability anyway.

So that's it for the setup!

Global Variables

You can now use global variables like {language_name} and {language_code} in your templates to check the current language. You'll definitely want to put <html lang="{language_code}"> to identify the language to browsers.

You'll most likely be adding a language navigation a bit like:

<ul class="lang {language_code}">
	<li class="en"><a href="/en/{snippet_current_path_slashless}">English</a></li>
	<li class="de"><a href="/en/{snippet_current_path_slashless}">Deutsch</a></li>
	<li class="fr"><a href="/en/{snippet_current_path_slashless}">Francais</a></li>

That preserves the current page selection, and just switches the top language url segment and variables. My snippet is a shorthand for {if current_path != '/'}{current_path}{/if} which deals with the fact that {current_path} returns a pre-trailing slash only if no template is defined, i.e it's the homepage.

Multi-language content

You might find it suitable to maintain totally different entries for each language, and use channels, categories or url titles to filter. But, I'm assuming that you want to keep a like-for-like likeness across the language variations, so ideally keep it as close together as possible.

For editors, this means one channel entry that covers all language versions.

I found one extra field type covered nearly everything: ... and for all the generic site bits that aren't in entries:

Some other things

The next blog post we have planned will discuss design considerations for other languages, but assuming you do run into some minor tweaks for certain languages, we can target it via css because we have the lang attr on our html. I use the following mixin:

// select by language, 
// send it one or more comma separated quote-delimited locale codes
// assuming lang attribute is set on the html tag, like it should be!
// usage: @include if-lang("de") {}
// usage: @include if-lang("de", "en", "cn-ZH") {}
@mixin if-lang($locales...) {
	@for $i from 1 through length($locales) {
		html[lang=#{nth($locales, $i)}] & {@content;}

PHP locales

Just a final little gotcha: If you are using php to localise things like dates, even though ee is UTF-8 as standard, it seems like you need a specific locale code to make sure character encoding is correct, for instance, for italian, I had to use:

$assign_to_config['global_vars']['language_locale'] = 'it_IT.UTF-8';

If you've any additions to contribute to this post, please tweet @PrettyStudio or me @tobystokes directly and we'll add them in.