Managing Gettext Translations on Shared Hosting

Share this article

If you’re working for a big company, chances there are that sooner or later your employers will start to target the global market. With this ambition will come the need to translate the company’s website into one or more languages. Even if you don’t work for a big company, you may have a new service to launch in your native language (assuming you’re not a native English speaker) to target your local market, and in English for the global one. As developers, our role isn’t to translate texts but to prepare the website to support translations. The most popular method to do that in PHP is via Gettext. It’s a great method because it allows to separate translations from the application, enabling the parallelization of the process. The problem with it, is that Apache caches the translations, so unless you can restart the engine, any update to a translation file won’t be seen. This fact is particularly annoying if you work on a shared hosting where you don’t have administrator permissions. In this article, I’ll describe this issue in detail and explain the solution I found to avoid it.

Note: If you aren’t familiar with the concepts of I18N, translations, and Gettext, I strongly encourage you to read this series before exploring this article further. It will provide you with more details than the brief overview you’ll find here, and will help you have a better comprehension of these topics.

Setting up the Environment

There a lot of ways to use translations in PHP. The simplest one I can recall is to have an associative array containing all the translated strings and then use the keys to retrieve the right. As you may guess, this solution doesn’t scale well and should be avoided unless you’re working on a very very small project (perhaps something not longer than 5 lines of code). For serious translations we can use Gettext. This approach enables us to have different files for every targeted language which helps in maintaining separation between the business logic, the presentation layer, and the translations (which we can see as an add-on of the presentation layer). With Gettext, we can parallelize the process because while we’re working on some features of the website, translators can still work on translations using software like Poedit.

Translations should be stored in a path having a fixed structure. First of all, we’ll have a root folder named to your taste (for example “languages”). Inside it, we have to create a folder for every targeted language whose name must comply to the ISO 3166 standard. So, valid names for an Italian translation can be “it_IT” (Italian of Italy), “it_CH” (Italian of Switzerland), “en_US” (English of USA), and so on. Within the folder having the language code, we must have a folder named “LC_MESSAGES” where, finally, we’ll store the translation files.

Poedit, analyzing the source of the website, extracts the strings to translate based on one or more patterns we set in the software. It saves the strings in a single file having .po (Portable Object) as its extention that this software (or one equivalent) will compile into binary .mo file. The latter is the format of interest for us and for the PHP’s gettext() function. The .mo file is the one we must place inside the “LC_MESSAGES” directory we created earlier.

A sample code that uses gettext() is the following (the code is commented to give you a quick grasp of what it does):

<?php
   // The name of the root folder containing the translation files
   $translationsPath = 'languages';
       // The language into which to translate
       $language = 'it_IT';
   // The name of the translation file (referred as domain in gettext)
   $domain = 'audero';

   // Instructs which language will be used for this session
   putenv("LANG=" . $language); 
       setlocale(LC_ALL, $language);

   // Sets the path for the current domain
   bindtextdomain($domain, $translationsPath);
   // Specifies the character encoding
   bind_textdomain_codeset($domain, 'UTF-8');

   // Choose domain
   textdomain($domain);

   // Call the gettext() function (it has an alias called _())
   echo gettext("HELLO_WORLD"); // equivalent to echo _("HELLO_WORLD");
?>

Once you save the previous code in a page and load it in your browser, if gettext() is able to find the translation file, you’ll see the translations you made on the screen.

So far, so good. The bad news is that once a translation is loaded, Apache caches it. Therefore, unless we can restart the engine, any update to a translation file won’t be seen. This is particularly annoying if we work on a shared hosting where we don’t have administrator permissions. How to solve this issue? Audero Shared Gettext to the rescue!

What’s Audero Shared Gettext

Audero Shared Gettext is a PHP library (actually is just a single class, but let me dream) that allows you to bypass the problem of the translations, loaded via the gettext() function, that are cached by Apache. The library employs a simple yet effective trick so that you’ll always have the most up-to-date translation in use. Audero Shared Gettext requires PHP version 5.3 or higher because it uses namespaces, and the presence of the structure described in the previous section. It has two main methods: updateTranslation() and deleteOldTranslations(). The former is the core of the library and the method that implements the trick. But what is this trick? Let’s see its code, to discover more. To fully understand it, it’s worth highlighting that the constructor of the class accepts the path where the translations are stored, the language into which to translate, and the name of the translation file (domain).

/**
 * Create a mirror copy of the translation file
 *
 * @return string The name of the created translation file (referred as domain in gettext)
 *
 * @throws \Exception If the translation's file cannot be found
 */
public function updateTranslation()
{
    if (!self::translationExists()) {
        throw new \Exception('The translation file cannot be found in the given path.');
    }
    $originalTranslationPath = $this->getTranslationPath();
    $lastAccess = filemtime($originalTranslationPath);
    $newTranslationPath = str_replace(self::FILE_EXTENSION, $lastAccess . self::FILE_EXTENSION, $originalTranslationPath);

    if(!file_exists($newTranslationPath)) {
            copy($originalTranslationPath, $newTranslationPath);
    }

    return $this->domain . $lastAccess;
}

The first thing the method does is to test if the original, binary translation file exists (the .mo file). In case it doesn’t exist, the method throws an exception. Then, it calculates the complete path to the translation file based on the parameters given to the constructor, and the timestamp of the last modification of the file. After, it creates a new string concatenating the original domain to the previously calculated timestamp. Once done, and here is the trick, it creates a mirror copy of the translation file. The class is smart enough to avoid this copy if a file with such a name already exists. Fianlly, it returns the new name that we’ll employ in bindtextdomain(), bind_textdomain_codeset(), and textdomain(). Doing so, Apache will see the translation as if it isn’t related to the original one, avoiding the caching problem. As I said, simple but effective!

“Great Aurelio!”, you’re thinking, “but in this way my folders will be bloated by these replications.” Right. That’s why I created deleteOldTranslations(). It removes all the mirror copies but the last from the folder of the chosen translation.

Now that you know what Audero Shared Gettext is and what it can do for you, let’s see how to obtain it.

Installing Audero Shared Gettext

You can obtain “Audero Shared Gettext” via Composer adding the following lines to your
composer.json:

"require": {
    "audero/audero-shared-gettext": "1.0.*"
}

And then run the install command to resolve and download the dependencies:

php composer.phar install

Composer will install the library to your project’s vendor/audero directory.

In case you don’t want to use Composer (you should, really) you can obtain Audero Shared Gettext via Git running the command:

git clone https://github.com/AurelioDeRosa/Audero-Shared-Gettext.git

The last option you have is to visit the repository and download it as an archive.

How to use Audero Shared Gettext

Assuming you obtained Audero Shared Gettext using Composer, you can rely on the its autoloader to dynamically load the class. Then, you have to create an SharedGettext instance and call the method you need. You can use one of the previously cited methods as shown in the following example.

<?php
    // Include the Composer autoloader
    require_once 'vendor/autoload.php';

    $translationsPath = 'languages';
        $language = 'it_IT';
    $domain = 'audero';

    putenv('LC_ALL=' . $language);
        setlocale(LC_ALL, $language);

    try {
       $sharedGettext = new Audero\SharedGettext\SharedGettext($translationsPath, $language, $domain);
       // Create the mirror copy of the translation and return the new domain
       $newDomain = $sharedGettext->updateTranslation();

       // Sets the path for the current domain
       bindtextdomain($newDomain, $translationsPath);
       // Specifies the character encoding
       bind_textdomain_codeset($newDomain, 'UTF-8');

       // Choose domain
       textdomain($newDomain);
        } catch(\Exception $ex) {
       echo $ex->getMessage();
    }
?>

Conclusions

This article has introduced you to Audero Shared Gettext, a simple library (emh…class) that allows you to bypass the problem of the translations, loaded via the gettext() function, cached by Apache. Audero Shared Gettext has a wide compatibility because it requires that you have at least PHP 5.3 (released for a while now) because of its use of namespaces. Feel free to play with the demo and the files included in the repository, to submit Pull Requests and issues if you find them. I’ve released Audero Shared Gettext under the CC BY-NC 4.0 license, so it’s free to use.

Have you ever encountered this issue? How did you solve it? Don’t be shy and post your solutions in the comments!

Frequently Asked Questions (FAQs) about Managing Gettext Translations on Shared Hosting

How can I install Gettext on my shared hosting?

Installing Gettext on shared hosting can be a bit tricky as you may not have root access. However, you can still install it by using the cPanel or Plesk. In cPanel, you can find the option to enable Gettext under the “Select PHP Version” section. In Plesk, you can enable it under the “PHP Settings” section. If you can’t find these options, you may need to contact your hosting provider for assistance.

Why is my Gettext translation not working?

There could be several reasons why your Gettext translation is not working. One common issue is that the .mo file is not compiled correctly or the file path is incorrect. Make sure that the .mo file is in the correct directory and that the file path in your code is correct. Another issue could be that the locale you’re using is not supported by your server. You can check this by using the locale -a command.

How can I debug Gettext translations?

Debugging Gettext translations can be done by checking the .po and .mo files for errors. You can use tools like Poedit to open these files and check for any syntax errors. Additionally, you can use the gettext function in your code to check if the translation is being fetched correctly. If the function returns the original string, it means that the translation is not found.

Can I use Gettext with WordPress?

Yes, you can use Gettext with WordPress. WordPress uses Gettext for its localization framework. You can use the __() and _e() functions in WordPress to fetch translations. These functions use Gettext under the hood.

How can I update my Gettext translations?

You can update your Gettext translations by updating the .po file and then compiling it into a .mo file. The .po file is a plain text file that you can open with any text editor. Once you’ve made your changes, you can use a tool like Poedit to compile the .po file into a .mo file.

Why am I getting an “Unable to locate package php-gettext” error?

This error usually occurs when the package is not available in the repositories that your server is using. You can try updating your package list by using the sudo apt-get update command. If the error persists, you may need to add additional repositories or manually install the package.

How can I use Gettext with PHP?

You can use Gettext with PHP by using the gettext function. This function takes a string as an argument and returns the translated string. Before you can use this function, you need to set the locale using the setlocale function and specify the path to your .mo files using the bindtextdomain function.

Can I use Gettext on Ubuntu?

Yes, you can use Gettext on Ubuntu. You can install it by using the sudo apt-get install gettext command. Once installed, you can use the gettext command in the terminal to work with .po and .mo files.

How can I create .po and .mo files?

You can create .po and .mo files by using a tool like Poedit. This tool allows you to create and edit .po files and automatically compiles them into .mo files. You can also create .po files manually by using a text editor, but you’ll need a tool like Poedit or msgfmt to compile them into .mo files.

What is the difference between .po and .mo files?

.po files are plain text files that contain the original strings and their translations. They can be opened and edited with any text editor. .mo files, on the other hand, are binary files that are generated from .po files. They are used by Gettext at runtime to fetch translations.

Aurelio De RosaAurelio De Rosa
View Author

I'm a (full-stack) web and app developer with more than 5 years' experience programming for the web using HTML, CSS, Sass, JavaScript, and PHP. I'm an expert of JavaScript and HTML5 APIs but my interests include web security, accessibility, performance, and SEO. I'm also a regular writer for several networks, speaker, and author of the books jQuery in Action, third edition and Instant jQuery Selectors.

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