A Polyglot Web Site in PHP

AID: 8910
  • Status: Published

4615 points

  • ByRay_Paseur
  • TypeGeneral
  • Posted on2011-12-18 at 09:38:05
Introduction
This article describes the general design elements of a multi-lingual web site. The site will be very simple, but all the important parts of the design will be present. Here are two images that show what the site looks like.  By clicking on the flags at the bottom, our clients can change the languages.
screenshots.png
  • 29 KB
  • Two examples showing English and German
Two examples showing English and German


The Design of the Directory Structure
We want to be able to support many languages, but with a single, simple design.  A directory structure that makes sense for this is in place at www.php.net.  The PHP folks have organized the online manual this way, where XX indicates the language.

php.net/manual/XX/function.var-dump.php

The standard terminology for languages is prescribed by ISO639, an evolving standard that employs short abbreviation codes.  See http://en.wikipedia.org/wiki/ISO_639-1 for more information.  For our example we will develop our web site in three languages: English, French and German.  The abbreviations we will use for English, French and German are en, fr, and de (from the endonym Deutsch), respectively.  

Our directory structure will look like this.

www_root
|
-- image/
-- index.php
-- language.php
-- template.php
|
-- en/index.php
|
-- fr/index.php
|
-- de/index.php

This structure will be implemented by using relative links for the web scripts in the language directories and explicit URLs for the image directory.  If we wanted to add a fourth language, for example Spanish (/es/), we would be able to copy any of the language directories and translate the text into Spanish.

What Language Should We Use?
We might want to consider using an IP-to-Location service to choose the language, but in practice this is problematical.  Consider the French who travel in Germany.  Even if they were accessing the web site from a German city, it would not be reasonable to assume that the French now wanted to use German language.  So instead of using geolocation to make decisions, we will give the client a highly recognizable visual clue, the national flags.  We will make these flag icons into links that activate a common script.  The common script will recognize the language, set a cookie and redirect to the appropriate language directory.  We can find images of the national flags here: http://en.wikipedia.org/wiki/National_flags and we can use a drawing program like Photoshop to create our icons.  We will use English as the assumed language; our French and German clients will be able to get their languages with a single click on the flag icon.

The Home Page in the WWW Root
Our home page will try to recognize the client's preferred language by looking for a long-lived cookie. If the cookie is not set or is invalid, we will choose English.  We will redirect the browser to the appropriate directory.
<?php // /index.php
error_reporting(E_ALL);

// THE DEFAULT LANGUAGE
$lang = 'en';

// IF WE REMEMBER A LANGUAGE PREFERENCE
if (isset($_COOKIE["lang"]))
{
    switch($_COOKIE["lang"])
    {
        case 'en' : $lang = 'en';
        break;

        case 'fr' : $lang = 'fr';
        break;

        case 'de' : $lang = 'de';
        break;
    }
}

// REDIRECT TO THE LANGUAGE VERSION OF THE SITE
$home = $lang . DIRECTORY_SEPARATOR;
header("Location: $home");
exit;
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:

Select allOpen in new window



The Language Page
In order to give our client the ability to change languages, we need a language selection page.  Instead of using the value in $_COOKIE, it will use the URL argument in $_GET.  It is no accident that this script looks very much like the home page of the web root.
<?php // /language.php
error_reporting(E_ALL);

// THE DEFAULT LANGUAGE
$lang = 'en';

// IF WE ARE GIVEN A LANGUAGE PREFERENCE
if (isset($_GET["lang"]))
{
    switch(strtolower($_GET["lang"]))
    {
        case 'en' : $lang = 'en';
        break;

        case 'fr' : $lang = 'fr';
        break;

        case 'de' : $lang = 'de';
        break;
    }
}

// SET THE LANGUAGE FOR ABOUT A YEAR
setcookie('lang', $lang, time()+365*24*60*60, DIRECTORY_SEPARATOR);

// REDIRECT TO THE LANGUAGE VERSION OF THE SITE
$home = $lang . DIRECTORY_SEPARATOR;
header("Location: $home");
exit;
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:

Select allOpen in new window



Handling Common Elements
Our image files are common to all language versions, therefore we will not use relative URLs to access our images.  Instead, we will use a leading-slash notation to make reference to files that are relative to the web root, something like this:
<img src="/image/piglet.png" />

Similarly, our template script will be the same for all languages, so we will set up the template as a common script that can be included in each version of the web page.  Near the bottom of this script you will see where we visualize the cookie.  When we are able to see data like this, it makes debugging much easier.
<?php // /template.php
error_reporting(E_ALL);

// OUR LANGUAGES
$languages = array
( 'en' => 'English'
, 'fr' => 'French'
, 'de' => 'Deutsch'
)
;

// CREATE THE PAGE TEMPLATE USING HEREDOC NOTATION
$html_1 = <<<TEMPLATE
<!DOCTYPE html>
<html dir="ltr" lang="$lang">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1" />
</head>
<body>
<h1>$head <img src="/RAY_EE_mlws/image/flag_$lang.png" /></h1>
<p>
<img src="/RAY_EE_mlws/image/piglet.png" />
</p>
TEMPLATE;


// CREATE OUR FLAG LINKS
$html_2 = $text . '<br/>';
foreach ($languages as $abbr => $language)
{
    // CREATE OUR LINKS USING HEREDOC NOTATION
    $choices = <<<CHOICES
<a title="$language" href="/RAY_EE_mlws/language.php?lang=$abbr"><img src="/RAY_EE_mlws/image/flag_$abbr.png" /></a>
CHOICES;

    // APPEND EACH FLAG LINK TO THE HTML STRING
    $html_2 .= $choices . PHP_EOL;
}

// WRITE THE HTML STRINGS TO THE BROWSER
echo $html_1;
echo $html_2;

// SHOW THE COOKIE
echo "<pre>COOKIE: ";
var_dump($_COOKIE["lang"]);
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:

Select allOpen in new window



Different Content for Different Languages
The use of "heredoc" notation makes PHP templating into a very simple task.  We simply assign values to the variables that are embedded in the heredoc blocks.  Learn more about heredoc and nowdoc on the PHP web site at http://php.net/manual/en/language.types.string.php.  You may find, as I do, that the heredoc notation is much less confusing than trying to deal with escaped quotation marks, apostrophes, and concatenation operators.

Here are our three different languages.
<?php // /de/index.php DEUTSCH
error_reporting(E_ALL);

// SET LANGUAGE AND TEXT
$lang = 'de';
$head = 'Schweinchen';
$text = 'Sprache wählen';

// LOAD THE TEMPLATING SCRIPT
require_once('../template.php');
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

Select allOpen in new window


<?php // /en/index.php ENGLISH
error_reporting(E_ALL);

// SET LANGUAGE AND TEXT
$lang = 'en';
$head = 'Piglet';
$text = 'Choose Language';

// LOAD THE TEMPLATING SCRIPT
require_once('../template.php');
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

Select allOpen in new window


<?php // /fr/index.php FRENCH
error_reporting(E_ALL);

// SET LANGUAGE AND TEXT
$lang = 'fr';
$head = 'Petit Cochon';
$text = 'Choisissez une langue';

// LOAD THE TEMPLATING SCRIPT
require_once('../template.php');
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

Select allOpen in new window



From This Design...
We could certainly do many more interesting things.  For example, the variables that make up our pages, $lang, $head, $text could be much more extensive (In a real-world example, they would probably come from a data base.)  If we wanted to add another language, it would be fairly easy.  And most importantly, the new translation could be worked on without disrupting any of the existing translations.  Once our new translation is ready for publication, we would install a new flag icon, and we would make a simple change to the template script to add the new element to the $languages array.

The combination of a common template and separate language directories makes this a flexible design with an intuitive client interface.

See it in Action
http://www.laprbass.com/RAY_EE_mlws/
    Asked On
    2011-12-18 at 09:38:05ID8910
    Tags

    PHP

    ,

    Translation

    ,

    Languages

    ,

    Natural Languages

    ,

    Multilingual

    Topic

    PHP Scripting Language

    Views
    1968

    Comments

    Add your Comment

    Please Sign up or Log in to comment on this article.

    Join Experts Exchange Today

    Gain Access to all our Tech Resources

    Get personalized answers

    Ask unlimited questions

    Access Proven Solutions

    Search 3.2 million solutions

    Read In-Depth How-To Guides

    1000+ articles, demos, & tips

    Watch Step by Step Tutorials

    Learn direct from top tech pros

    And Much More!

    Your complete tech resource

    See Plans and Pricing

    30-day free trial. Register in 60 seconds.

    Loading Advertisement...

    Top PHP Experts

    1. Ray_Paseur

      1,354,218

      Genius

      3,734 points yesterday

      Profile
      Rank: Savant
    2. DaveBaldwin

      284,951

      Guru

      4,300 points yesterday

      Profile
      Rank: Genius
    3. StingRaY

      144,754

      Master

      4,000 points yesterday

      Profile
      Rank: Wizard
    4. jason1178

      143,172

      Master

      1,000 points yesterday

      Profile
      Rank: Genius
    5. bportlock

      123,643

      Master

      1,600 points yesterday

      Profile
      Rank: Genius
    6. ChrisStanyon

      111,336

      Master

      0 points yesterday

      Profile
      Rank: Sage
    7. Roads_Roads

      106,350

      Master

      0 points yesterday

      Profile
      Rank: Genius
    8. maeltar

      95,247

      Master

      0 points yesterday

      Profile
      Rank: Guru
    9. gr8gonzo

      95,168

      Master

      0 points yesterday

      Profile
      Rank: Sage
    10. smadeira

      82,088

      Master

      0 points yesterday

      Profile
      Rank: Wizard
    11. Slick812

      77,062

      Master

      0 points yesterday

      Profile
      Rank: Sage
    12. johanntagle

      74,700

      Master

      2,000 points yesterday

      Profile
      Rank: Sage
    13. logudotcom

      67,088

      Master

      0 points yesterday

      Profile
      Rank: Genius
    14. COBOLdinosaur

      65,841

      Master

      0 points yesterday

      Profile
      Rank: Genius
    15. leakim971

      63,819

      Master

      0 points yesterday

      Profile
      Rank: Genius
    16. un1x86

      56,406

      Master

      0 points yesterday

      Profile
      Rank: Master
    17. Derokorian

      46,763

      10 points yesterday

      Profile
      Rank: Guru
    18. marqusG

      44,475

      10 points yesterday

      Profile
      Rank: Sage
    19. DrDamnit

      42,892

      0 points yesterday

      Profile
      Rank: Genius
    20. ahoffmann

      40,068

      0 points yesterday

      Profile
      Rank: Genius
    21. designatedinitializer

      37,800

      0 points yesterday

      Profile
      Rank: Master
    22. maestropsm

      37,678

      0 points yesterday

      Profile
      Rank: Guru
    23. TerryAtOpus

      34,800

      0 points yesterday

      Profile
      Rank: Genius
    24. xterm

      32,850

      0 points yesterday

      Profile
      Rank: Sage
    25. kaufmed

      31,808

      0 points yesterday

      Profile
      Rank: Genius

    Hall Of Fame