Localizing PHP Applications “The Right Way”, Part 1

This entry is part 1 of 5 in the series Localizing PHP Applications "The Right Way"

Localizing PHP Applications "The Right Way"

New audiences around the world access the Internet every second, most of whom would be delighted to find your content in their mother tongue. You might think you only need a good translator to translate the user interface of your website, an easy task nowadays given how easy it is to find one, but the bigger challenge isn’t translating or writing multilingual content… it’s writing the code behind the scenes.

Localizing software applications in general used to be a cumbersome and error-prone task resulting in a lot of messy code. Some developers even use different versions of code for the same application but for different locales, which makes managing the codebase practically impossible. Enter gettext, the wonderful open-source tool that will make your life easier by allowing you to concentrate on your code. Localization becomes a matter of writing separate translation files for the target language, which can easily be done by a translator using Poedit. If you master gettext for PHP, you will find extending your application’s global reach really can be just a matter of translation!

In this series of articles you will learn how to gettext to localize your PHP application. Part 1 will provide just a quick foray into gettext. Each subsequent part will build on preceding parts, teaching you how to handle multiple translation domains, plural forms, and even how you can automate some of the localization process.

Preparing the Environment

I prefer to make sure my environment is set up properly before I start learning something new, since having everything up and running correctly makes it easier to test what I learn step-by-step. Here is a checklist of things you need to have installed before going any further:

  • PHP 5.x on Apache (or whichever web server you prefer), with the gettext extension enabled. You can always get the latest PHP for your platform at www.php.net.
  • Poedit, a cross-platform editor for gettext catalogs. It’s a really nice tool that lets you keep your translations separate from your application code. You can get a copy from www.poedit.net

Regardless of your platform, to use gettext from PHP you will need to have the gettext library installed and the PHP extension that hooks into the library.

Ubuntu/Debian users can use apt-get and Fedora/CentOS/Redhat users can use yum to install the gettext library. If you’re using another unix-like system, go to www.gnu.org/s/gettext and get a copy of gettext that is compatible with your platform. Windows users can download the latest executable and install it from gnuwin32.sourceforge.net/packages/gettext.htm.

After you install the library, you need to enable the PHP extension for gettext by editing php.ini and adding a single line:

# for Windows users
extension=php_gettext.dll
# for *nix users
extension=gettext.so

Then, install Poedit which will be used later. You can download a copy from www.poedit.net.

Hello World with gettext

In your web root directory, create a sandbox directory for playing with gettext named TestI18N (I18N stands for Internationalization). Within the TestI18N directory, create the following hierarchy:

directory structure

In future projects, you can really name the parent directory and the Locale directory any name you want, but en_US and LC_MESSAGES are standard names which are used by gettext. en_US stands for the name of the locale and is made up of two parts. The first part is a two-letter lowercase abbreviation for the language according to the ISO 639-1 specification. The second part after the underscore is a two-letter uppercase country code according to the ISO 3166-1 alpha-2 specification. en_US thus means the language is English as spoken in the United States.

Next, make sure Poedit is working correctly on your platform. Launch the program and choose from the top menu bar File > New Catalog. In the settings window, fill in the information below skipping the plural forms field for now:

poedit settings window

Click OK and then save the file as messages.po inside the LC_MESSAGES directory you created earlier.

Now close Poedit, and use your favorite text editor to open messages.po… yes, it is an ordinary text file! you can edit it by hand, but to save ourselves some hassle we let Poedit create the main definitions for us. Leave an empty line after the lines that are already in the file and add the following:

#Test token 1
msgid "HELLO_WORLD"
msgstr "Hello World!"

#Test token 2
msgid "TEST_TRANSLATION"
msgstr "Testing translation..."

Save messages.po and close it, and then re-open it in Poedit.

In Poedit, choose File > Save or click the Save Catalog entry in the icon bar. The reason we are saving the PO file in Poedit is because they need to be compiled into a special format usable by gettext. After you save it in Poedit, you will see a new file has been created in the same directory with the same name but with the extension .mo. If you were to continue to modify and save the PO file with a regular text editor, you would need to compile it using the pocompile command. The extra compilation step isn’t necessary if you use Poedit because it automatically compiles the file anytime you save it.

Back in the TestI18N directory, create a file named test-locale.php with the following code:

<?php
// I18N support information here
$language = "en_US";
putenv("LANG=" . $language); 
setlocale(LC_ALL, $language);

// Set the text domain as "messages"
$domain = "messages";
bindtextdomain($domain, "Locale"); 
bind_textdomain_codeset($domain, 'UTF-8');

textdomain($domain);

echo _("HELLO_WORLD");

Open TestI18N/test-locale.php in your browser. If everything is installed correctly and working fine, you will see Hello World displayed on the page.

Summary

When it comes to localizing your PHP application, you may have a lot of options at your disposal. We chose to use the GNU gettext library and its PHP extension, a powerful and easy approach that localizes the application “The right way!” In this installment you saw what’s needed to install gettext and the PHP extension, briefly used Poedit, and whetted your appetite with a simple Hello World script. Part 2 will use this as a basis for learning more about gettext; I’ll explain each function introduced in the Hello World script as well as how the gettext library works.

Image via sgame / Shutterstock

Localizing PHP Applications "The Right Way"

Localizing PHP Applications “The Right Way”, Part 3 >>Localizing PHP Applications “The Right Way”, Part 4 >>

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://fawadafr.com Fawad Rashidi

    Excellent topic Abdullah. I look forward to the next parts.

  • Lars Shellerup

    Great topic. Can’t walt till next part.

  • Les

    I have found this approach to be too time consuming and why must I use “en_US” etc for my folder structure? I hate such things being forced upon me so I would suggest a different approach for multiple languages.

    Innovation beats down standardisation anytime IMHO :p

  • mario

    When the builtin gettext extension is not available, then drop-in alternatives are php-gettext http://savannah.nongnu.org/projects/php-gettext/ or with more features gettext.php from upgradephp http://freshmeat.net/projects/upgradephp (Both are pure PHP userland implementations, don’t have the caching drawback of the mod_php native libgettext)

  • http://www.ruempler.eu Soenke Ruempler

    the native gettext extension with php is a PITA:

    * you cannot update your translations on the fly in fastcgi (which is the common web sapi nowadays) – only with workarounds like: http://segfaultlabs.com/devlogs/php-gettext-caching-workaround – otherwise your fcgi processes will sigsegv

    * it uses setlocale() which itself is PITA: for example setlocale(LC_ALL) will not only translate your strings, but also translate system messages coming from other libraries (LC_MESSAGES), reformat your float numbers when you echo/cast them. setlocale is harmful because it changes so much things globally.

    please advocate higher level libs like Zend_Translate from ZF which are also able to use mo-files as backend but do not change global state of the process.

    thx, soenke

  • uzo

    tried this way. poedit is so buggy so it stops you on the half way. from my point of view it is not usable on the project unless u have lots of time to play with it :(

    i had to switch to csv files

  • mohsmed ateaa

    what an article i think you covered every single detail in your topic . really i enjoyed with this one and i am waiting your next topic.
    :)

  • Altamash

    thanks for valuable tutorial. i am learning php so i cant understand how to change php.ini file for gettext at server. editing php.ini files on localhost is not a problem but on server how i can do it.

  • Stefan

    Please read the notes on http://php.net/setlocale about using setlocale() in a multithreaded server.

  • summ3r

    Hello,
    If you’re involved in software and/or website localization, I would warmly recommend a new translation tool that my team developed and will probably make your work easier: http://poeditor.com/
    POEditor is intuitive and collaborative. You can import from multiple localization file formats (like pot, po, xls, xlsx, strings, xml, resx) and, most importantly, we’re constantly trying to enhance it based on our users’ feedback.
    Feel free to try it out and recommend it to developers and everyone who might find it useful.

  • help please

    on my local machine i use localization and it work but when on a remote server, it does not. the php extension is enabled, and i do not get any error messages. the user have a drop down to chose a language and once he does the strings should be translated (again it works on my local machine).

    this is my code:
    if(!isset($_COOKIE["locale"]))
    {
    setcookie(“locale”, $configSite-> getValue(‘siteLang’, ‘language’),time()+60*60*24*30, “/”);

    $locale = $configSite-> getValue(‘siteLang’, ‘language’);
    }else{
    $locale = $_COOKIE["locale"];

    }

    putenv(“LC_ALL=$locale”);//needed on some systems
    putenv(“LANGUAGE=$locale”);//needed on some systems
    setlocale(LC_ALL, $locale);
    bindtextdomain(“messages”, “./locale”);
    bind_textdomain_codeset(“messages”, “UTF-8″);
    textdomain(“messages”);
    sorry i do not see here how to enter code.