Localizing PHP Applications “The Right Way”, Part 3

Share this article

In Part 2 you gained more insight into using the gettext library by learning the most important functions of the extension. In this part you’ll learn how to best use a fallback locale, switch between locales, and override the currently selected message domain. So without further ado, let’s dive in!

Directory Structure and Fallback Locales

If you’ve been following along in the previous articles, you should have the following directory structure for testing the gettext library. While it is typical, it is not designed to achieve the best performance.

directory recap

Generally speaking, you should define your application’s default language/locale before getting too far into the localization process because this decision will affect how you create your core domain files. For example, if you decide to use American English as the default locale – as most people do – then you probably won’t create the en_US directory at all! It is preferable to create each target domain with msgids that are actual strings that would have been found in the default locale. So instead of using the identifier “HELLO_WORLD”, you should be using the actual English string.
#Test token 1
msgid "Hello World!"
msgstr "Bonjour tout le monde!"
Remember, gettext will display the msgid if it can’t find a translation domain. So it’s not about the en_US directory itself, it’s about avoiding unnecessary translations when the user requests the default locale. Using real text strings like this saves execution time and memory by eliminating the need to translate English to English, “HELLO_WORLD” to “Hello World!”. While some developers prefer to keep the English-to-English translation so there is a clear distinction between application strings (“HELLO_WORLD”) and interface text (“Hello World!”), I much prefer this fallback approach. Of course you are free to choose whichever best aligns with your needs and personal style. After deleting the en_US directory, I’ll add two more locales the application can target: Spanish (es_ES) and Egyptian Arabic (ar_EG). This is how the Locale directory should look now:

ar_EG and es_ES directories

Don’t forget to create the necessary translation domains for each using the same procedures outlined in Part 1. The French domain contains:
#Test token 1
msgid "Hello World!"
msgstr "Bonjour tout le monde!"

#Test token 2
msgid "Testing Translation..."
msgstr "Test de traduction..."
The Spanish domain contains:
#Test token 1
msgid "Hello World!"
msgstr "¡Hola mundo!"

#Test token 2
msgid "Testing Translation..."
msgstr "Prueba de traducción..."
And the Arabic domian contains:
#Test token 1
msgid "Hello World!"
msgstr "!أهلا بالعالم"

#Test token 2
msgid "Testing Translation..."
msgstr "...اختبار الترجمة"
See how the msgids in all the target domains are actually strings from the en_US locale that was removed since you are now using it as the default. Now you have a real-world directory structure with translation domains!

Switching Locales

Switching between various locales is as easy as telling gettext to use another domain. Typically you will do this at the top of your application files or by putting it in a common file to be included in each script that sends output to the browser. Create a new file named locale.php with the following contents:
<?php
// use sessions
session_start();

// get language preference
if (isset($_GET["lang"])) {
    $language = $_GET["lang"];
}
else if (isset($_SESSION["lang"])) {
    $language  = $_SESSION["lang"];
}
else {
    $language = "en_US";
}

// save language preference for future page requests
$_SESSION["Language"]  = $language;

$folder = "Locale";
$domain = "messages";
$encoding = "UTF-8";

putenv("LANG=" . $language); 
setlocale(LC_ALL, $language);

bindtextdomain($domain, $folder); 
bind_textdomain_codeset($domain, $encoding);

textdomain($domain);
Then open the test-locale.php script; remove the domain setup code and include the new locale.php file instead. Your code should now look like this:
<?php
// Include I18N support
require_once "locale.php";

echo _("Hello World!"), "<br>";
echo _("Testing Translation...");
Now you’re ready for the fun! Go to your browser and you’ll get the following output:
Hello World!
Testing Translation...
Change the URL to pass in one of the locales created earlier, for example: test-locale.php?lang=fr_FR. gettext will display the output in French or Arabic, depending on what you provided for the parameter. Switching languages is as simple as that!
Bonjour tout le monde!
Test de traduction...
The locale.php file makes use of sessions so you only have to pass the lang parameter once and it will be used for subsequent requests by the user. If you are performing URL-rewriting, another possibility is to make the language part of the URL and extract it from there, as in www.example.com/en_US/test-locale.php.

Overriding the Current Domain

Let’s suppose for a moment you want to switch from one text domain to another. This might be needed if you’re using separate domains to store the core translation and system error messages, for example messages would be main application domain and errors would be the domain for error strings. You’ll find the function dgettext() very useful for this type of setup. dgettext() is essentially the same as gettext() and _(), except it accepts the name of a domain as the first argument in which to look up the translation. This doesn’t affect the default domain that was set by textdomain(); subsequent calls to gettext() will still use the default. Create a new translation domain by the name of errors for the French locale (TestI18N/Locale/fr_FR/LC_MESSAGES/errors.po). Make sure you create the file using Poedit, then open it using a text editor and add the following translations:
Test Error 1
msgid "Error getting content"
msgstr "Erreur de l'obtention du contenu"

#Test Error 2
msgid "Error saving data"
msgstr "Erreur de sauvegarde des données"
Save errors.po, re-open it in Poedit and compile it to errors.mo. Then add the following lines at the end of test-locale.php:
<?php
echo "<br>";
echo _("Error getting content"), "<br>";
echo _("Error saving data");
When you run the test-locale.php script you’ll see the English strings, even if you pass lang=fr_FR
as the parameter. This is because you added these messages to a different domain (_() is using the messages domain to look up translations since this is what was set with textdomain()). To inform gettext where to find the new translation strings, update the code to use dgettext() instead:
<?php
echo dgettext("errors", "Error getting content"), "<br>";
echo dgettext("errors", "Error saving data");
Now when you run the script… you still see the English messages! Hrm… why aren’t they being replaced? Actually we’ve just made a very common mistake that people make when using gettext. If you recall in Part 1 I talked about two very important methods, bindtextdomain() and bind_textdomain_codeset() and mentioned that you can call them several times to bind as many domains as you want. Whenever you need to use a domain you must to explicitly bind it using bindtextdomain() first. gettext only allows loading a single “master” domain, which is the one when specify using textdomain() and is the one used by gettext() and _(). You can lookup messages in other domains with dgettext() only if they’re bound first. Update the locale.php include file to bind the errors domain as well:
<?php
...
bindtextdomain($domain, $folder); 
bind_textdomain_codeset($domain, $encoding);

bindtextdomain("errors", "Locale"); 
bind_textdomain_codeset("errors", "UTF-8");

textdomain($domain);
Now when you run your script you’ll see the error messages correctly translated to French.

Summary

In this part you learned how using the default locale’s strings as msgids in target domains can improve performance and organization, and how switching between locales based on the user’s preference can be accomplished. You also learned that while gettext allows only one default lookup domain, you can use multiple domains with dgettext() provided you’ve bound them first. In the next part I’ll show you how powerful gettext is when it comes to handling one of language’s most demanding aspects – plural forms. Image via sgame / Shutterstock

Frequently Asked Questions on PHP Localization

What is the importance of PHP localization in web development?

PHP localization is a crucial aspect of web development, especially for businesses aiming to reach a global audience. It involves adapting a website or an application to different languages, regional differences, and technical requirements of a target market. This process not only includes translating the content into various languages but also adapting the user interface, adapting graphics, and modifying content to suit the tastes and consumption habits of other markets. PHP localization enhances user experience, increases the accessibility of the application, and can significantly boost the market reach of the business.

How does PHP localization work?

PHP localization works by using specific functions and classes that allow developers to define different language settings for different users. These functions include gettext, setlocale, and Locale class. The gettext function is used for translating strings, setlocale for setting locale information, and Locale class for locale-sensitive operations. The developer can use these functions to create a multilingual website or application that automatically adapts to the language settings of the user’s device.

What are the best practices for implementing PHP localization?

Implementing PHP localization effectively requires careful planning and execution. Some of the best practices include: using a consistent coding style for easier maintenance and updates, using gettext for string translation as it is widely supported and efficient, avoiding hard-coding strings in the code, using locale-sensitive functions for date, time, and number formatting, and testing the localized version thoroughly to ensure all elements are displayed correctly in different languages.

How can I use the Locale class in PHP localization?

The Locale class in PHP provides several methods for locale-sensitive operations. For instance, you can use the Locale::getDefault() method to get the default locale, Locale::setDefault() to set the default locale, and Locale::getDisplayName() to get the display name of the locale in the specified language. These methods can be very useful in creating a localized user interface.

What are the challenges in PHP localization and how can I overcome them?

Some of the challenges in PHP localization include handling plurals, managing right-to-left languages, dealing with cultural differences, and maintaining the localized versions. These challenges can be overcome by using the right tools and libraries, following best practices, and thorough testing. For instance, gettext provides support for plural forms, and there are libraries available for handling right-to-left languages.

How can I localize date and time in PHP?

PHP provides several functions for localizing date and time, such as strftime() and date(). These functions can format the date and time according to the locale settings. For instance, strftime(“%A %e %B %Y”, time()) will display the date in the format “weekday day month year”, according to the current locale.

How can I manage translations in PHP localization?

Managing translations in PHP localization can be done using gettext, which is a powerful and widely supported tool for string translation. You can create a .po file for each language, containing the translations of all strings. These files can then be compiled into .mo files, which are used by gettext to translate the strings at runtime.

How can I test the localized version of my PHP application?

Testing the localized version of your PHP application involves checking all elements of the application in all supported languages. This includes not only the translated content but also the user interface, graphics, date and time formats, number formats, and other locale-sensitive elements. Automated testing tools can be very helpful in this process.

How can I handle right-to-left languages in PHP localization?

Handling right-to-left languages in PHP localization can be done using specific libraries and CSS techniques. For instance, you can use the dir attribute in HTML to specify the text direction, and use CSS to adjust the layout for right-to-left languages. There are also libraries available that provide support for right-to-left languages.

How can I keep the localized versions of my PHP application up to date?

Keeping the localized versions of your PHP application up to date requires regular updates and maintenance. Whenever you update the original version, you need to update the translations as well. Using a version control system can be very helpful in managing and tracking these updates. Automated testing can also help in ensuring that all elements of the localized versions are functioning correctly.

Abdullah AbouzekryAbdullah Abouzekry
View Author

Abdullah Abouzekry is an experienced web-developer with over 7 years developing PHP/MySQL applications ranging from simple web sites to extensive web-based business applications. Although his main experience is with PHP/MySQL and related web technologies, he has developed and localized many desktop applications in C#, Python/Qt, Java, and C++. When not writing code, Abdullah likes to read, listen to oriental music, and have fun with his little family.

Intermediate
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week