Publishing on the WWW in only one language? Don't be intimidated by internationalization! I will show you how easy it is to convert your custom PHP site or application into a worldly site.
You might have an existing project you want to internationalize, or you might be wondering how to apply this technique to a brand new project. Let's create a basic "Hello World" example app that we will use for demonstration purposes. After you see how this application works, you will be able to apply these techniques to your own project.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>index</title>
</head>
<body id="index">
Hello World
</body>
</html>
"Hello World"" will be the text we will translate in two language, the original (English) and the other, for this example, French."
Let's apply some basic styling, this step is completley superficial (in both senses of te word) for this tutorial, but it's better to stare at something pretty than not, so just copy and paste this style and the updates to the HTML and on we go.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>index</title>
<style type="text/css" media="screen">
body {
font-family: Helvetica, Arial, sans-serif;
color: #666;
}
#language_switch_wrap {
width: 600px;
margin-left: auto;
margin-right: auto;
text-align: right;
padding: 5px;
}
#language_switch_wrap a {
padding: 5px;
color: #ccc;
text-decoration: none;
}
#language_switch_wrap a:hover,
#language_switch_wrap a:hover i {
background: #C5EAFF;
color: white;
}
#language_switch_wrap i {
color: #666;
font-style: normal;
}
#content {
width: 600px;
margin-left: auto;
margin-right: auto;
font-family: Palatino Linotype, Book Antiqua, Palatino, serif;
}
#copy {
font-size: 500%;
}
hr {
height: 1px;
border: none;
background: #C5EAFF;
}
</style>
</head>
<body id="index">
<hr/>
<div id="content">
<span id="copy">Hello World</span>
</div>
</body>
</html>
If you view the page in your browser now, it should look like this:
Let's create the project directory structure to help organize the code we will have to write.
While it's beyond the scope of this tutorial to talk about project organization best practices, it sufices to say a clear structure is irreplaceable, as your site grows.
For our purposes, since this demo application is small, we will focus on creating folders for code that generates some HTML for your view or template code, we place this code in a "helpers" folder.
Code that is pure PHP, and is generally used as an aid in a variety of places on your site goes in the "lib" or "library" folder.
Particular to this application, and other sites that make use of multi-language files, is a place where these translation files reside, I like to put them in the "translations" or "internationalizaion" folder. We'll use the first one for this example.
Let's start writing some code! For the first aspect of the translation process let's write the helper that will be used to embedd the language switcher HTML. I will assume a simple switcher between two languages, English and French, you may replace these to your liking.
Make a file named "lang_switcher" in the helpers folder.
<div id="language_switch_wrap">
<small>
<span>
<a href="?lang=en"><?= @$_SESSION['lang'] == 'en' ? "<i>" : "" ?>English<?= @$_SESSION['lang'] == 'en' ? "</i>" : "" ?></a>
</span>
|
<span>
<a href="?lang=fr"><?= @$_SESSION['lang'] == 'fr' ? "<i>" : "" ?>Français<?= @$_SESSION['lang'] == 'fr' ? "</i>" : "" ?></a>
</span>
</small>
</div>
The interesting parts of this code are happening between the two a tags
<a href="?lang=en"><?= @$_SESSION['lang'] == 'en' ? "<i>" : "" ?>English<?= @$_SESSION['lang'] == 'en' ? "</i>" : "" ?></a>
Here we create a link with a GET parameter that defines each of the languages ?lange=en. These will give our application the necessary information to apply the language switch (we will get to this soon).
The other important thing we want to do is wrap each link with a tag the <i> in this case, so that we can distinguish between the selected language and the other option. We do this by checking the PHP $_SESSION global variable and using a simple "ternary operator" convention:
<?= @$_SESSION['lang'] == 'en' ? "<i>" : "" ?>English<?= @$_SESSION['lang'] == 'en' ? "</i>" : "" ?>
The @ in front of each $_SESSION variable stops PHP from reporting a Notice in case they are undefined, which they will be; before the user click on one of them.
Begin by creating a file called "lang_session.php" in the lib folder. And add the following code do it:
<?php
if(isset($_GET['lang'])) {
if ($_GET['lang'] == 'en') {
$_SESSION['lang'] = 'en';
} elseif ($_GET['lang'] == 'fr') {
$_SESSION['lang'] = 'fr';
}
}
if(isset($_SESSION['lang'])) {
$lang = $_SESSION['lang'];
} else {
$lang = null;
}
?>
This file will set the PHP global variable $_SESSION based on the GET parameters we set through the helper links: ?lang=en.
After the "lang" session key is set with the appropriate language value (en or fr), in the second if setatement, we check if such a session exists and set a "$lang" variable to that value, this is simply to make our code more readable as we will have to make use of this $lang variable in the future.
If you wanted to set other variables based on the session setting, here is where you could do that. For example you might want to prefix image folder names or CSS file names with an appropriate language prefix if you wanted to switch where your app gets its resources from based on language.
For this part we will look at building the 'machinery' or function for grabbing the correct translation bit and doing things with it. Let's look at the code and then break it down.
<?php
function t($key, $lang='en') {
if( $lang == null) {
$lang = 'en';
}
@include("translations/$lang.php");
if (isset($translated_text)) {
if (isset($translated_text[$key])) {
return $translated_text[$key];
} else {
return $key;
}
} else {
return $key;
}
}
?>
First, let's do some housekeeping: in case the lang parameter to the function is set to null then we will set it to the default language en.
Next, we are including the proper file (which will be containing our translations), the file will be located in the translations folder. If it does not yet exist, we block the error from being shown by using the PHP @ prefix again.
If you want to be stricter, you can remove the @ from before this include and you will be warned if the translation file you are trying to fetch does not exist.
Next, we check for certain assumption: that the translation file we are including has the $translated_text variable set. And if this is true, then we want to fetch the appropriate text 'string' according to the $key we pass into the function.
They $key is the 'magic' number.
When we wrap our text-to-be-translated with this function, the $key we pass in will be the existing or default language copy. If you look at the next piece of code: we check to see if we find a $translated_text[$key] and if so, we return it, in other words: we return the translated text from the translation file.
But, if we don't find this $key, then we don't want to fuss, we will simply return the $key value! So that if we pass in the existing text string as the $key, it will be returned by default if no translation of it is found. This will allow us to translate step by step, and is what lies at the heart of this simple translation functionality.
For the final step, we need to create the translation files wich will contain our translated text.
Because the script expects that each file be the value in the $lang variable -- which, if you remember, we define through our helper script as either en or fr -- we create two files in the translations folder using these names: "en.php" and "fr.php".
Each of these files will define the $translated_text variable as an array of key/value pairs. The key will be the original copy: sentence or paragraph or whatever piece of text we expect to translate. The value will be the translation of this key.
The english file en.php is very simple, since we don't need anything translated:
<?php $translated_text = array( ); ?> The french file fr.php will contain the key/value pairs which will put the translator into action. <?php $translated_text = array( 'Hello' => 'Bonjour' ); ?>
As you can see in the above code, we are translating one word for illustration purposes, however, you could include both words at once or entire sentences or paragraphs. But don't worry this will become clear when we get to put things into action in the next step.
Using the function
Now we turn back to our index.php page, where we put this function into action. But first, let's include and require the appropriate PHP files we need.
<?php
session_start();
require('lib/lang_session.php');
require('lib/translator.php');
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>index</title>
<style type="text/css" media="screen">
body {
font-family: Helvetica, Arial, sans-serif;
color: #666;
}
#language_switch_wrap {
width: 600px;
margin-left: auto;
margin-right: auto;
text-align: right;
padding: 5px;
}
#language_switch_wrap a {
padding: 5px;
color: #ccc;
text-decoration: none;
}
#language_switch_wrap a:hover,
#language_switch_wrap a:hover i {
background: #C5EAFF;
color: white;
}
#language_switch_wrap i {
color: #666;
font-style: normal;
}
#content {
width: 600px;
margin-left: auto;
margin-right: auto;
font-family: Palatino Linotype, Book Antiqua, Palatino, serif;
}
#copy {
font-size: 500%;
}
hr {
height: 1px;
border: none;
background: #C5EAFF;
}
</style>
</head>
<body id="index">
<?php include('helpers/lang_switcher.php'); ?>
<hr/>
<div id="content">
<span id="copy">Hello World</span>
</div>
</body>
</html>
Notice all the changed we made to our original HTML file by adding the necessary PHP components.
If you remember, we defined the t() function earlier in the translator.php file; so let's use it on our content. Let's just translate one of the words, "Hello" to see how this works: since we already defined the word "Hello" in our en.php file, let's wrap that word with the t() function:
<div id="content">
<span id="copy"><?= t('Hello', $lang) ?> World</span>
</div>
Notice a few things, we pass in the whole "Hello" word to the function, this is the $key I was talking about earlier, and we pass in the $lang which tells the script which language file to look in for translations.
I am making use of the short hand PHP tags <?= ?> instead of using the echo() function. This will keep code concise and easy to type.
If you view the french version of the page in your browse now, you should see:
If you want to continue adding translations, we can do the same thing for the next word, "World", like so:
<span id="copy"><?= t('Hello', $lang) ?> <?= t('World', $lang) ?></span>
Don't forget to add the appropriate translation key/value pairs to the fr.php file!
$translated_text = array( 'Hello' => 'Bonjour', 'World' => 'Monde' );
Click on the English | French links to see the translator do its magic!
The entire index.php file for reference:
<?php
session_start();
require('lib/lang_session.php');
require('lib/translator.php');
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>index</title>
<style type="text/css" media="screen">
body {
font-family: Helvetica, Arial, sans-serif;
color: #666;
}
#language_switch_wrap {
width: 600px;
margin-left: auto;
margin-right: auto;
text-align: right;
padding: 5px;
}
#language_switch_wrap a {
padding: 5px;
color: #ccc;
text-decoration: none;
}
#language_switch_wrap a:hover,
#language_switch_wrap a:hover i {
background: #C5EAFF;
color: white;
}
#language_switch_wrap i {
color: #666;
font-style: normal;
}
#content {
width: 600px;
margin-left: auto;
margin-right: auto;
font-family: Palatino Linotype, Book Antiqua, Palatino, serif;
}
#copy {
font-size: 500%;
}
hr {
height: 1px;
border: none;
background: #C5EAFF;
}
</style>
</head>
<body id="index">
<?php include('helpers/lang_switcher.php'); ?>
<hr/>
<div id="content">
<span id="copy"><?= t('Hello', $lang) ?> <?= t('World', $lang) ?></span>
</div>
</body>
</html>
In this manner you can continue adding translated text to the rest of your project files that need them. By using this method, you don't have to re-engineer your application with other complex translation libraries. For example: the simplicity of this method was inspired by the Symphony PHP framework's internationalization functionality. But instead of using complex XML language files I opted for a simple PHP array, which keeps things much simpler and light weight.
Caveat: this method is great for small to medium sized projects but I would not recommend creating translation files the size of entire books. The translated text will be included in you server's memory and you don't want this memory to run out on each page request! Otherwise, it's a quick and simple way to get your app to trully belong on the WWW.
I hope you enjoyed this tutorial and find it a useful process to take your project to the international stage!