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. Sadhan says:

    Hello, thanks for your reply. Actually I don’t have any idea which files need to upload to get the site in new language. It would be better if you give me step by step guide.

    I am building a dating site and you know that the sites will have lots of PHP files, which will be available in different language.

    Thanks,

    Sadhan.

  2. Hmmm, what language file? Well, you could use INI file and then have INI file for each language.

  3. Sadhan says:

    Hello, you posted a very nice tutorial to build a site in multilingual. But I want to build the site in multilingual via language file instead of database use. Can you guide me how it would be possible to do it and what is the necessary step to do it via language file.

    Thanks,

    Sadhan.

  4. Murat says:

    very informative. Thank you a lot.

  5. Wyand says:

    ‘design patter’ should be ‘design pattern’. Nice article btw ;)

  6. macarena says:

    Do you mean content translation or interface translation?

  7. kuldeep jain says:

    Hi,
    I want to learn to php,so please help me….

  8. Hmm, not quite sure what you want? You could, but then your function addTag should store tags value and some other function should replace that tag in some string :) .

  9. Hi, thanks for your comment.

    Well, my idea was not to create full system. I just wanted to give basic idea how it could be accomplished :) .

    And I know it does not work with all languages, but what’s the point of giving complete solution. That for libraries are build. In my opinion, tutorials should just give some guide lines and basic approach so that other people can make better adjustments by themselves :) . That’s the way how they’ll learn and that’s the way I still learn.

  10. andrew says:

    I tried to set up with Japanese or Chinese languages…. it is not working!

    This is a good tutorial, but the way to storing data is not good enough, it is ok for small site.

    below image seem better for storing data, but it is working with Pearl :(
    http://devzone.zend.com/images/articles/4469/image13.jpg

    http://devzone.zend.com/article/4469

    Thanks for your hardworks! :)

  11. k00dez says:

    something like this is possible?

    addTag(“linkhome”,utf8_encode($translation->getTranslation(“home”)));

    and for use (I changed for [], sorry, don’t now how to post code):

    [li] [a href="#"] {linkhome} [/a] [/li]

  12. kees says:

    Thanks, very nice script.

  13. I suppose you’ve added it through phpMyAdmin. It will work as long you have same connection charset as phpMyAdmin (if you just copied my code, I haven’t set it up, so it does not work). But if you add to your script code that inserts into database, it works.

    So you can solve it either by inserting data from php script or changing connection charset (in my configuration php uses latin1 and phpMyAdmin utf-8 and by executing mysql_set_charset(“utf8″) it works).

  14. RD says:

    Thanks for replying though..
    But when i did the same for arabic/japanese it shows “????”..I even changed the charset to utf8.
    Hope you could help me in this.

  15. What’s difference in Arabic language?

    This is just basic idea. It can be extended and tweaked as needed.

  16. RD says:

    Its realy good but how would you use languages like arabic etc?

  17. G.E.G. :) says:

    Yes, it’s good. :D Just as you. :P <3

  18. classpc says:

    thanks my friend. it’s good.

Leave a Reply


 *


 *


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