Using Apache2 Content Negotiation To Serve Different Languages
Author: Falko Timme
Follow me on Twitter
Content negotiation is the ability of a web server to deliver the document that best matches the browser's preferences/capabilities. For example, if a resource exists in multiple languages, the web server can choose which variant it serves based on the Accept-Language header delivered by the browser. This tutorial describes how to configure content negotiation in Apache2 to serve different languages based on browser preferences.
I do not issue any guarantee that this will work for you!
1 Preliminary Note
In Apache2, content negotiation is made available by the mod_negotiation module which is built-in by default.
I will use a Debian Squeeze system here with Apache2 already installed, but the Apache configuration is independent of the distribution you use.
On Debian Squeeze, there's a global content negotiation configuration in /etc/apache2/mods-available/negotiation.conf; because I want to demonstrate how to configure it on a per-vhost basis, I comment out everything in that file (of course, you can do your content negostiation configuration in this file instead of in your vhosts if you want the configuration to be valid for all your vhosts):
<IfModule mod_negotiation.c> # # LanguagePriority allows you to give precedence to some languages # in case of a tie during content negotiation. # # Just list the languages in decreasing order of preference. We have # more or less alphabetized them here. You probably want to change this. # #LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv tr zh-CN zh-TW # # ForceLanguagePriority allows you to serve a result page rather than # MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback) # [in case no accepted languages matched the available variants] # #ForceLanguagePriority Prefer Fallback </IfModule>
Restart Apache afterwards:
I will modify Debian's default Apache vhost with the document root /var/www and the vhost configuration file /etc/apache2/sites-available/default in this tutorial.
Let's delete any index file in /var/www...
rm -f /var/www/index.*
... and create three new index files, one in German (index.html.de), one in English (index.html.en), and one in French (index.html.fr):
<html> <head> <title>Deutsch</title> </head> <body text="#000000" bgcolor="#FFFFFF" link="#FF0000" alink="#FF0000" vlink="#FF0000"> <h1>Willkommen zu unserer deutschen Seite!</h1> </body> </html>
<html> <head> <title>English</title> </head> <body text="#000000" bgcolor="#FFFFFF" link="#FF0000" alink="#FF0000" vlink="#FF0000"> <h1>Welcome To Our English Site!</h1> </body> </html>
<html> <head> <title>Français</title> </head> <body text="#000000" bgcolor="#FFFFFF" link="#FF0000" alink="#FF0000" vlink="#FF0000"> <h1>Bienvenue sur notre site français!</h1> </body> </html>
Our /var/www directory looks as follows:
ls -la /var/www/
[email protected]:~# ls -la /var/www/
drwxr-xr-x 2 root root 4096 Jul 20 00:13 .
drwxr-xr-x 14 root root 4096 Feb 14 18:43 ..
-rw-r--r-- 1 root root 196 Jul 20 00:06 index.html.de
-rw-r--r-- 1 root root 186 Jul 20 00:03 index.html.en
-rw-r--r-- 1 root root 207 Jul 20 00:09 index.html.fr
I want Apache to deliver the right document (variant) based on the browser's language preferences. Browsers send an Accept-Language header which lists their preferred language(s). This can be achieved in two ways:
- By using the MultiViews option where Apache does a file name pattern match and chooses the appropriate variant from the results.
- By using a type map where you explicitly list all variants.
MultiViews works as follows: if you request a resource named foo, and foo does not exist in the directory, Apache will search for all files named foo.*, like foo.html, foo.html.de, foo.html.en, foo.de.html, foo.en.html, foo.en.html.gz, foo.gz.en.html, foo.html.gz.en, foo.html.en.gz, and so on.
MultiViews is a per-directory setting, i.e., it has to be set with an Options directive in a <Directory>, <Location>, or <Files> section in the Apache configuration. It has to be set explicitly (i.e. Options MultiViews); Options All does not set it.
[...] <Directory /var/www/> Options Indexes FollowSymLinks MultiViews DirectoryIndex index.html AllowOverride None Order allow,deny allow from all </Directory> [...]
That's basically all we need for content negotiation.
Now let's look at our browser's language preferences (I use Firefox 5 here). Go to Tools > Options:
Under Content, there is a Languages area where you can click on the Choose... button:
In this example, I have en-US and en enabled (the order is important!):
Now let's go to our default vhost (my server's IP address is 192.168.0.100, so I go to http://192.168.0.100). I have the LiveHTTPHeaders plugin for Firefox installed so that I can check the headers sent by Firefox. As you see, my browser sends the header Accept-Language: en-us,en;q=0.5 which means its first preference is en-US (if no q value (q = Quality/Priority) is present, this means q=1.0; q must be a value between 0 (lowest priority) and 1 (highest priority)).
Because we have specified no file name in our request (just http://192.168.0.100 instead of http://192.168.0.100/index.html or http://192.168.0.100/foo.html), Apache will use the DirectoryIndex directive to search for an index file, and because we use DirectoryIndex index.html, it will search for index.html. Because index.html does not exist, MultiViews will now try to find the best match for the request; we don't have an index.html.en-US file, but we have an index.html.en file (en is the second-best preference of our browser), so index.html.en gets served:
(Apache will also try to match language subsets if no other match can be found. For example, if our browser accepted only en-US, Apache would serve the en variant - index.html.en - instead of serving a 406 "Not Acceptable" error (en gets a low q value, but because the browser does not accept any other language, the en variant will be served). But if the browser sends an Accept-Language: en-us,fr;q=0.8 header, for example, the French variant - index.html.fr - will be served because of the low q value for en.)