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.
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.
Hmmm, what language file? Well, you could use INI file and then have INI file for each language.
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.
very informative. Thank you a lot.
Thx, corrected
‘design patter’ should be ‘design pattern’. Nice article btw
Interface
Do you mean content translation or interface translation?
Hi,
I want to learn to php,so please help me….
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
.
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.
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!
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]
Thanks, very nice script.
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).
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.
What’s difference in Arabic language?
This is just basic idea. It can be extended and tweaked as needed.
Its realy good but how would you use languages like arabic etc?
Yes, it’s good.
Just as you.
<3
thanks my friend. it’s good.