Light i18n with Drupal

Running a multilingual site can be a real pain, depending on what you exactly want to obtain. Drupal 4.7 comes with an internationalization module that does a real good job on most cases. However, mine didn't fit within its features. Some of my content is written in English, other in French — exclusively. There is no use to translate every page to both languages.

What bothers me more in the current i18n module is that it always prefixes all your local URLs with the current language. That is, instead of having '/my/node', you'd have '/en/my/node' or '/fr/my/node'. This is not really serious by itself, but I started worrying when I realized that pages without any translation were both available under '/en/' and '/fr/', and that the only difference between the two versions was the language of Drupal's user interface. Having the same content referenced under different URLs is not a clean design to me, and it becomes objectively very bad when it comes to Search Engine Optimization.

So in the end I've written a little bit of code to support multi-lingual content, provided pages do not have translations to other languages. It allows to do what you are seeing on this site:

  • Add little flags before menu entries to indicate the language in which the item is written,
  • Translate Drupal's interface (comment form, etc.) into the language of the current node.

It comes in the form of a new vocabulary, a little bit of theming and a small module. First, enable the locale module and make sure you have installed the translations for all the languages you want to support. In "categories", add a new vocabulary named "Language" and add the terms corresponding to the locales you support on your site (in my case, 'en' and 'fr'). Leave "Free tagging" and "Multiple select" unchecked. This will allow you to assign a single language to all of your nodes, which is what you should do right after.

The next part is to make the little flags appear right before menu entries. I've been pointed to a method to assign a CSS ID for every menu entry and we are just going to use a little variant here. Instead of assigning a CSS ID to each entry, we'll just add a class according to the language of the node, if any. So in your theme, add the following code to template.php:

<?php
function phptemplate_menu_item($mid, $children = '', $leaf = TRUE) {
return _phptemplate_callback('menu_item', array(
'leaf' => $leaf,
'mid' => $mid,
'children' => $children
));
}
?>

Then create a menu_item.tpl.php containing the following:

<?php
$link = menu_item_link($mid);
$item = menu_get_item($mid);

// Is this item a node?
if (!strncmp($item['path'], 'node/', 5)) {
// Then get its nid
$nid = explode('/', $item['path']);
$nid = $nid[1];

// The 'Language' vocabulary ID
static $language_vid;
if (!$language_vid) $language_vid = db_fetch_object(db_query(
db_rewrite_sql("SELECT vid from {vocabulary} WHERE name like \"Language\"")))->vid;
// From the nid, get the taxonomy
$vocab = array_keys(taxonomy_node_get_terms_by_vocabulary($nid, $language_vid, 'name'));
// We suppose we can only have one language per node here
$vocab = $vocab[0];
}

// No vocab? Then generate a class according to the item name.
if (!$vocab) {
$link = menu_item_link($mid);
$vocab = strtolower(str_replace(' ', '_', strip_tags($link)));
}

// render the menu link with its flag class (or a class generated from its name)
$output = '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .
($vocab ? " " . $vocab : " nolang") . '">'. $link . $children ."</li>\n\
";
print $output;
?>

That will render all your menu items with an extra class which is the name of the language it is written in or, for entries that have no assigned language or are not nodes, the name of the entry itself, in a way similar to what is explained on Nick Lewis' entry.

To render the flags, you just need to do some CSS magic in style.css:

 ul.menu li.en a:before, ul.menu li.blog a:before {
padding-right: .5em;
content:url(/files/images/flags/gb.png);
}

ul.menu li.fr a:before {
padding-right: .5em;
content:url(/files/images/flags/fr.png);
}

Af for the icons files, you can use these really nice flag icons , which are available for free use.

Note that on the first entry, I have put two patterns to match. Indeed, the 'Blog' entry is not a node, and thus its class is not a language, but its name. But as it is an English content, I want to apply the same layout as English node. Note also that all menu items will transmit their language class to their children.

Now the last thing that remains to be done is to translate Drupal's interface according to the language of the current node. That is, just setting the $locale variable before the page is output. This is well done by a small module, named lighti10n.module in your modules directory:

<?php
/**
* Implementation of hook_help().
*/
function lighti10n_help($section) {
switch ($section) {
case 'admin/modules#description':
return t('Sets the locale to the language of the current node.');
}
}

function lighti10n_init() {
// First get the node id.
if (arg(0) == 'node' && is_numeric(arg(1))) $nid = arg(1);
// Comment submission pages should be translated too.
else if (arg(0) == 'comment' && arg(1) == 'reply' && is_numeric(arg(2))) $nid = arg(2);

// Displaying a node, we can look for its language
if (isset($nid))
{
// Fetch the right vocabulary ID
$vid = db_fetch_object(db_query(
db_rewrite_sql("SELECT vid from {vocabulary} WHERE name like \"Language\"")))->vid;

// Fetch the vocabulary terms for this node
$vocab = array_keys(taxonomy_node_get_terms_by_vocabulary($nid, $vid, 'name'));
$vocab = $vocab[0];

// If we couldn't find a vocabulary, see if a parent in the menu tree has one.
if (!$vocab) {
$item = menu_get_item(NULL, 'node/' . $nid);
$pid = $item['pid'];
while (!$vocab && $pid && ($item = menu_get_item($pid))) {
$nid = explode('/', $item['path']);
$nid = $nid[1];

$vocab = array_keys(taxonomy_node_get_terms_by_vocabulary($nid, $vid, 'name'));
$vocab = $vocab[0];

$pid = $item['pid'];
}
}
// Found a language? Then set the $locale global variable accordingly.
if ($vocab) {
global $locale;
$locale = $vocab;
}
}
}
?>

Finally, enable this module, and Drupal's locale will be set to the language of the current node, or of its nearest parent in the menu tree.

This solution is clearly not as powerful as the i18n module, but for sites with small needs like mine it performs just fine, avoids having duplicate content and does not mess with your URLs.

Comments

Which version?

This looks great. What version of Drupal does this work with?

Should have mentioned it

Sorry, should have mentioned it. This is for Drupal 4.7, so this post is probably a little bit outdated.