logo

Multi-Language Site

Add comment

It’s been a while since I last posted something because I was very busy. So let’s do something useful now. Reader Tien Phuong requested a tutorial about multi-language sites and how to create them so now I’ll try to show you one way. We’ll use database to store our translations and languages. So let’s start.

Basic IdeaTop

First, let’s discuss our goal here. We want to add milti-language ability to our site. We can do it in many ways but I like database idea.

How it will work?Top

Well, we’ll have one table that will hold all languages we have. Second table will have translations that are connected to languages. Those translations will have keywords. Each language can have only one keyword but there can be multiple translations with same keyword that refer to different languages. I’ve also chosen database to store translations because it’s easy to manipulate with them later (we’ll not create it now). When user requests page, PHP will check language we are using and get translations we need from database.

How Will PHP Do That?Top

How will PHP do that? We’ll it very easy and simple. We’ll have few classes that will do all hard work for us. Classes will be Language, Translate and Translation. Language will be wrapper for language retrieved from database, Translation will be wrapper for one translation from database and Translate will be class that will connect other two classes and translate text.

Anything Else?Top

There is one thing. Since we’ll have dynamic page, we also need dynamic translations. We’ll do it like this. In our translation we’ll have some placeholders (they are optional) marked with %t%. When we need that translation, we’ll pass some arguments to our function and we’ll get translated text. For example, you have translation text “Welcome back %t%” that will be returned as “Welcome back Marijan” if argument value is “Marijan”.

Now that we have our idea explained, we can start working.

DatabaseTop

Database is nothing special. We’ll have two tables, translations and language where we’ll store our data.

Database Design

Database Design

As you can see it’s very simple. Table translations has field lang that’s FK to languages.id. We also index keyword in case we want to search our translations. SQL code is in ZIP file.

ClassesTop

Now we can start building our classes. First we’ll start with Translation and Language since they are just wrappers and are very simple.

LanguageTop

This is first class that we’ll build. She will only have a constructor and three getter methods.

ConstructorTop

Nothing special, there are three arguments that are stored to variables for later use. If they are invalid, InvalidArgumentException is thrown.

public function Language($id, $short, $name)
{
    if ((int) $id < 0)
        throw new InvalidArgumentException("ID is not valid.");

    if (!is_string($short))
        throw new InvalidArgumentException("Short name must be string.");

    if (!is_string($name))
        throw new InvalidArgumentException("Name must be string.");

    $this->short = $short;
    $this->name = $name;
    $this->id = (int) $id;
}

Getter MethodsTop

Also very simple. They just return what their name says.

public function getName()
{
    return $this->name;
}

public function getShort()
{
    return $this->short;
}

public function getId()
{
    return $this->id;
}

VariablesTop

There will be three variables for three parameters that are passed to constructor.

private $short;

private $name;

private $id;

TranslationTop

This class is almost the same as Language. We’ll have constructor and getter methods but we’ll also have two extra methods. One we’ll be magic method __toString and other we’ll be used to create dynamic translations.

ConstructorTop

Same as before. There are three arguments that are stored to variables. If they are invalid, InvalidArgumentException is thrown.

public function Translation($id, $keyWord, $translation)
{
    if ((int) $id < 0)
        throw new InvalidArgumentException("ID is not valid.");

    if (!is_string($keyWord))
        throw new InvalidArgumentException("Keyword must be string.");

    if (!is_string($translation))
        throw new InvalidArgumentException("Translation must be string.");

    $this->keyWord = $keyWord;
    $this->translation = $translation;
}

Getter MethodsTop

Also same as before. They just return what their name says.

public function getKeyWord()
{
    return $this->keyWord;
}

public function getTranslation()
{
    return $this->translation;
}

public function getId()
{
    return $this->id;
}

__toString Magic MethodTop

This method converts object to string. Why we need one? Because our translations are actually that can’t be echoed. This method is called by PHP when object needs to be converted to string that can be then printed. In our case it will just return translation.

public function __toString() {
    return $this->getTranslation();
}

replace MethodTop

This method will create our dynamic translations. How? Well we explained earlier. It will have N arguments where N is number of placeholders in that translation. Placeholders are %t% and are replaced with arguments. First placeholder is replaced by first placeholder and so on. If there are more placeholders than arguments, InvalidArgumentException is thrown. If there are more arguments than placeholders, nobody cares. We use simple while loop to go through our string and replace placeholders. At the end we just return generated string.

public function replace()
{
    $numArgs = func_num_args();

    $temp = $this->getTranslation();

    $pos = 0;
    $i = 0;

    while (($pos = strpos($temp, "%t%", $pos)) !== false) {
        if ($i >= $numArgs)
            throw new InvalidArgumentException("Not enough arguments passed.");

        $temp = substr($temp, 0, $pos) . func_get_arg($i) . substr($temp, $pos + 3);

        $pos += strlen(func_get_arg($i));

        $i++;
    }      

    return $temp; 

}

VariablesTop

There will also be three variables.

private $keyWord;

private $id;

private $translation;

Translate Top

This is class that gets translations and languages from database and then translates text. It’s also not to complicated. We’ll have one constructor, four getter methods, one setter method and two extra methods. We’ll implement singleton design pattern because we only need one translator per request.

ConstructorTop

Here constructor will not get any parameters. It will just load all languages and save them to an array. DataBase class used here is from my own library of simple classes that I use and it’s provided in ZIP file. I will not explain it here.

private function Translate()
{
    $languages = DataBase::getInstance()->query("SELECT * FROM `languages`");

    foreach ($languages as $language)
        $this->avLang[$language->short] = new Language($language->id, $language->short, $language->name);
}

Getter MethodsTop

Here we’ll have four getter methods.

  • getInstance – Returns instance of this class
  • getTranslation – Returns translation (instance of Translation class) of provided keyword. If translation does not exist, Exception is thrown
  • getLanguage – Returns shor name of language
  • getAllLanguages – Returns array with all languages in our database. Each entry is instance of Language class
public static function getInstance()
{
    if (self::$self === null)
        self::$self = new Translate();

    return self::$self;
}

public function getLanguage()
{
    return $this->lang;
}

public function getAllLanguages()
{
    return array_values($this->avLang);
}

public function getTranslation($keyWord)
{
    if (!isset($this->translations[$keyWord]))
        throw new Exception("Keyword does not exist.");

    return $this->translations[$keyWord];
}

Setter MethodsTop

We’ll have just one setter method and we’ll use it to set currently active language and we’ll pass short name to select language. If language does not exist or passed argument is not string we throw InvalidArgumentException. At the end we must reload all translations for currently active language.

public function setLanguage($lang)
{
    if (!is_string($lang))
        throw new InvalidArgumentException("Language short name must be string.");

    if (!$this->languageExist($lang))
        throw new InvalidArgumentException("Language does not exist.");

    if ($this->getLanguage() === $lang)
        return ;

    $this->lang = $lang;

    $this->preloadTranslations();
}

languageExist MethodTop

This method just checks if language exists. We just pass short name of language. If passed name is not string we throw InvalidArgumentException.

public function languageExist($lang)
{
    if (!is_string($lang))
        throw new InvalidArgumentException("Language short name must be string.");

    return isset($this->avLang[$lang]);
}

preloadTranslations MethodTop

This method will load all languages from database for selected language and store them to an array. If language is not set we throw Exception.

private function preloadTranslations()
{
    if ($this->lang === null)
        throw new Exception("Language is not set.");

    $result = Database::getInstance()->query(sprintf(
        "SELECT * FROM `translations` WHERE `lang` = %d",
        $this->avLang[$this->lang]->getId()
    ));

    foreach ($result as $translation)
        $this->translations[$translation->keyword] = new Translation($translation->id, $translation->keyword, $translation->value);
}

VariablesTop

We’ll have four variables

  • $lang – Short name of currently active language
  • $translations – All translations for active language. Each entry is instance of Translation class
  • $self – Instance of this class
  • $avLang – All available languages. Each entry is instance of Language class
private $lang;

private $translations;

private static $self;

private $avLang;

Those are our classes explained and we can move on.

How To Use This?Top

Now we’ll see how we can use this. First we need to place some languages and translations to our database.

Test DataTop

We have to add some languages to our database and we insert them in languages table. We do it like this.

INSERT INTO `languages` (`short`, `name`) VALUES ('en', 'English')

Value of short is ISO 3166-1 alpha-2 value of country (in our example “en”) and value of name is full name of our country (in our example “English”).

Now we need some translations. We’ll add them to translations table like this.

INSERT INTO `translations` (`keyword`, `lang`, `value`) VALUES ('hello', 1, 'Hello')

Value of keyword is some word that you’ll use to select specific translation (in our example “hello”) . Not that you’ll use same keyword for same translations in different languages. Value of lang is value of id column in our languages table of language we want to use (in our example “1″). So if you we want to add translation for english language, we’ll have to find out what’s id in languages table for english language. Value of value is our translation (in our example “Hello”).

Following those rules we can then add some test languages and translations.

INSERT INTO `languages` (`short`, `name`) VALUES
('en', 'English'),
('hr', 'Croatian'),
('de', 'German')

Assuming that id of English language is 1, Croatian language is 2 and German language is 3 we can then add translations. If  that is not true, you’ll have to change values to those you have.

INSERT INTO `translations` (`keyword`, `lang`, `value`) VALUES
('hello', 1, 'Hello'),
('whats_your_name', 1, 'What''s your name?'),
('is_your_name', 1, 'Is your name really %t%?'),
('hello', 2, 'Bok'),
('whats_your_name', 2, 'Kako se zoveš?'),
('is_your_name', 2, 'Da li je tvoje ime stvarno %t%?'),
('hello', 3, 'Hallo'),
('whats_your_name', 3, 'Wie heißt du?'),
('is_your_name', 3, 'Ist deine Name wirklich %t%?'),
('select_lang', 1, 'Select language'),
('select_lang', 2, 'Odaberite jezik'),
('select_lang', 3, 'Wählen Sie die Sprache')

Great, now we have languages and translations in our database.

PHP PartTop

Now we just have to create some PHP code to generate translations. How?

First we have to include required files.

require_once "DataBase.php";
require_once "Language.php";
require_once "Result.php";
require_once "Translation.php";
require_once "Translate.php";

Next we have to connect to database.

DataBase::getInstance()->connect("host", "username", "password", "database");

Now that we have it done, we need instance of Translate class.

$translation = Translate::getInstance();

Next we need to select our language (we select Croatian language).

$translation->setLanguage("hr");

Now we just need to print some translations like this (will produce “Bok”).

echo $translation->getTranslation("hello");

If you need dynamic translation like this (will produce “Da li je tvoje ime stvarno Marijan?”).

echo $translation->getTranslation("is_your_name")->replace("Marijan")

You can also get all languages and print them to users so they can select which one they want like this.

$allLanguages = $translation->getAllLanguages();

ConclusionTop

That would be all for this tutorial. I hope you understand how we did this. I’ve created demo that is available here so you can check how it works. You can download source code here. SQL code (schema.sql and demo.sql) and DataBase class (with Result class) are included so you can play around.

Thanks you for reading.

Related Posts
  • 27.07.2010 -- Upload Images With MySQL (6)
    [tinytoc level="1"]Intro[/tinytoc] Ok, let's continue with tutorial requests. This tutorial will ...
  • 07.04.2010 -- Edit XML (29)
    I had an idea about creating tutorial that will cover manipulating XML in PHP. The idea was to creat...
  • 16.10.2009 -- PHP DomDocument Tutorial (29)
    This will be a quick tutorial that will show you how to use PHP's DOMDocument to parse your XML so y...
  • 02.09.2009 -- PHP Walker Class (1)
    In this tutorial we will create walker class. This class will load results by executing given SQL qu...
  • 25.11.2009 -- Implementing Bitwise Permissions (1)
    Reader Freddy requested a tutorial about implementing bitwise permissions in real application. This ...
  • 25.09.2009 -- PHP Confirm Registration Link (11)
    This will be quick tutorial on how you can implement confirm registration link into your PHP applica...
  • 11.08.2009 -- Sql Queries Cache (0)
    This will be intermediate tutorial about caching SQL query results. We will use interface to make su...

logo

28 comments to “Multi-Language Site”

  1. Wassim says:

    did not work fine for me,
    could you send files on my mail please !

  2. [...] – How to Add Multi-language Support to a PHP Website http://www.php4every1.com/tutorials/multi-language-site – Multi-Language Site, with database [...]

  3. Martin says:

    Question about the ‘ Translations ‘ Table.

    I noticed that under Index you have it set for PRIMARY, but you also have ” Keyword and Lang ” under PRIMARY. I’m lost….

  4. Mika says:

    I managed to solve my challenge. Thanks.

    Br Mika

  5. First thing you should do is store it somewhere (session, pass it as GET param or something else) and then when generating list of languages, select that stored language using selected="selected".

  6. Mika says:

    Hi Marijan,

    I am building own small websites with multilingual support and I have one challenge that I haven’t managed to solve. My challenge is following: If user change start page language from English (default) to another one and then go to other page inside websites the language selection goes back to default (in this case English). I am the newbie in PHP, so can you give me a hint what I need to add/change?

    Thanks,
    Mika

  7. Well, this implementation uses database, not language files. You need to create your own implementation that uses language files.

Leave a Reply


 *


 *


logo
logo
Powered by Wordpress | Designed by Elegant Themes | CopyRight ©2012 php4every1.com