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.
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.
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
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.
did not work fine for me,
could you send files on my mail please !
[...] – How to Add Multi-language Support to a PHP Website http://www.php4every1.com/tutorials/multi-language-site – Multi-Language Site, with database [...]
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….
No problem
.
I managed to solve my challenge. Thanks.
Br Mika
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".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
Well, this implementation uses database, not language files. You need to create your own implementation that uses language files.