Thursday, March 30, 2006

Simple multi-language web site

There are many ways to direct users of a multi lingual web site to the pages
in their language. But the quick search I made didn't return solutions which
I liked, so I did my own. I'm sure the same method has been used many times,
but it's sometimes faster to re-invent a little wheel than to go through zillions
of Google search results.

I had to work on a small site with about 20 pages, in which every language
is in it's own subdirectory (www.example.com/fr/, www.example.com/en/, etc.)
and the file names remain the same.

This simple structure made it pretty easy, and the solution makes it trivial
to add another language.

(For a lot of background about languages and a different solution involving the standard "content language negotiation", see Dan's Web Tips: Languages).

The way I choose works like this:

  • One line in every page calls a PHP script to display the language menu.
  • A cookie holds the visitor's preferred language.
  • Javascript updates the cookie when the visitor switches languages.
  • The main index page redirects visitors to the correct language, using:
    1. either the cookie set in a previous visit,
    2. or the browsers preferred language,
    3. or a pre-configured default language.
  • Finally, a few CSS rules take care of the menu's look

The redirector script

I used a little Perl script on www.example.com/index.cgi. It first looks for
a cookie with the user's preferred language. If there is no cookie (usually
because this is a first-time visit), it uses the HTTP_ACCEPT_LANGUAGE
header sent by the browser to find the best guess. If that doesn't work either,
it uses a default language. Finally, it redirects the user to the selected language,
while also setting the cookie for the next visit.

I probably could have done the index page in PHP, but I didn't know PHP at
all when I started so it was easier for me in Perl. (If someone feels like sharing
a PHP translation, you are welcome to post it here as a comment).

#!/usr/bin/perl

# Redirect to correct language version of the site.
# Use cookie if present. Otherwise, parse the HTTP_ACCEPT_LANGUAGE header

# Author: Milivoj Ivkovic "mi\x40alma.ch"

use CGI qw(:cgi);
my $VERSION = 0.3;

my %available_languages = (
fr => 'fr/',
de => 'de/',
en => 'en/',
default => 'fr/',
);
my $wanted_language = 'default';

my $q = new CGI;

# try cookie
my $cookie_lang = $q->cookie("lang");
if ( exists $available_languages{$cookie_lang} ) {
$wanted_language = $cookie_lang;
}
else { # try HTTP_ACCEPT_LANGUAGE
# Get languages from HTTP_ACCEPT_LANGUAGE, (ignore local variants like en_US, en_GB, etc.).
# Don't bother removing duplicates
my @langs = map { substr($_, 0, 2) } split(/,/, $ENV{HTTP_ACCEPT_LANGUAGE});
foreach my $l (@langs) {
if (exists $available_languages{$l}) {
$wanted_language = $l;
last;
}
}
}

# if nothing worked, we still have the default in $wanted_language which was set at the beginning

# fix URL
my $url = $q->url(-path_info=>1);

# (some servers give us a url ending with 2 "/", so we need "/+" in the
# regex below instead of just "/".
# Otherwise we may end up with "http://example.com//en/" which doesn't look nice.)
$url =~ s|/+[^/]*$|/$available_languages{$wanted_language}|;

# redirect, and also set the cookie for next time
print $q->redirect ( -uri => $url,
-cookie => cookie( -name=>"lang",
-value=>$wanted_language,
-path=>"/",
-expires=>"+1y",
),
);

The javascript setLang function

Anytime the user switches the language, this small javascript function is called
to update a cookie, so the preference is saved between sessions.

function setLang(l) {
// Called from links which switch the language.
// Sets cookie with "lang=fr" or other language code, valid for 1 year, on all the site
var expire = new Date();
expire.setTime(expire.getTime() + 3600000*24*365);
document.cookie = 'lang='+l+";expires="+expire.toGMTString()+";path=/";
}

The language menu

Since all the pages happened to be in PHP, I did the language menu in PHP,
and added an include() to every page.

<?php include("../common/lang-selector.php") ?>

The PHP lang-selector.php script

This is the PHP script which displays the language menu.

<!-- php lang selector -->
<div id="lang-selector">
<?php
$langs = array(
"fr" => "Français",
"de" => "Deutsch",
"en" => "English",
);
$self=$_SERVER['PHP_SELF'];
$pattern = "{^/[^\/]+/}i";
$links = array();
foreach ($langs as $l => $lang) {
$is_current = ! ( strpos($self, "/$l/") === false );
$url = preg_replace($pattern, "/$l/", $self);
$link = "<a href=\"$url\" class=\"lang-"
. ($is_current ? "current\">" : "other\" onclick=\"setLang('$l')\">")
. " $lang </a> ";
array_push($links, $link);
}
echo " " . implode(" | ", $links) . "\n";
?>
</div>
<!-- end php lang selector -->

The finishing touch: CSS

The look of the language menu is done with CSS. This is what I used for that
particular site:

#lang-selector {
font-size: x-small;
text-align: left;
white-space: nowrap;
padding-bottom: 1em;
color: #CCCCCC;
}

a.lang-current,
a.lang-current:visited,
a.lang-current:hover
{
color: #CCCCCC;
text-decoration: underline;
}

a.lang-other,
a.lang-other:visited
{
color: #333333;
text-decoration: none;
}

a.lang-other:hover
{
text-decoration: underline;
background: #F7EBDB;
}

@media print {
#lang-selector {
display: none;
}
}

By the way, the site for which this was first set up is http://lesouffledudesert.com/.

When we added English to the site, the steps were:

  • Copy the /fr directory to a new /en directory
  • Add "en" => "English", to lang-selector.php
  • Add en => 'en/', to index.cgi
  • And of course, translate all pages, which was the real work and where
    this little multi-language system doesn't help...

Labels: , , , , ,