Lokalise graphic of PHP localization

Implementing PHP localization: A complete guide

In this tutorial, we are going to discuss how to implement PHP translation, localization, and internationalization. The PHP language is quite a stable and popular choice for web application development. Despite the existence of many other programming languages, such as Python, Ruby, and Node.js, a large number of applications are still built using flat PHP without any framework. Therefore, learning PHP localization techniques is undoubtedly useful for web developers. 

First off, let’s briefly discuss what localization (l10n) is and why it is such a common task for developers. In software engineering, localization is the process of adapting the content of a system to a particular locale so the target audience in that locale can understand it. It is not just about changing content from one language to another. You have to take date formats, number formats, currencies, text direction, and proper pluralization into account as well. For more information, here is our ultimate guide to software localization

As an example, you may know that pluralization is comparatively easy in English versus other languages. In some languages, such as Slavic ones, there are two plural forms for a given singular one. Likewise, the date, time, and number formatting in some languages can differ significantly from those used in English. 

So, now you understand that we have to consider multiple things in PHP translation besides simple language switching.

Let’s move on to discover how we can implement PHP l10n – this tutorial will cover the following five ways to achieve this:

  1. PHP arrays
  2. Gettext
  3. Define functions in PHP
  4. Setlocale
  5. Frameworks

Huge thanks to my colleague Ondřej Frei for all the assistance and technical insights.

    PHP arrays

    This is the primitive method that has been used to implement PHP translation for a long time. For this, you need to create an associative array for each language. These arrays are used to map the source language or keys to localized content. When you need to view localized content, you simply have to select the array and key of the relevant language. 

    Plain text translation with PHP arrays

    Let’s take a look at an example. In it, we will implement localization for three languages — English, Arabic, and Spanish — but of course you can choose any other locales. Please note that we are using English as the base language as it’s the primary language of most systems and applications.

    Here we will use a separate PHP file for each array to increase readability. However, if you like, you can use the same file to store all the arrays.

    Let’s see how we can display the sentence “Welcome to the tutorial” with PHP arrays in the languages mentioned above. First, you’ll need to create a project directory with an index.php file inside, along with the locale folder. Here’s the file structure:

    • index.php
    • locale/
      • arab.php
      • en.php
      • es.php

    arab, en, and es are the locale codes for Arabic, English, and Spanish, respectively.

    In the index.php file, create a simple form that includes a dropdown to ensure that the user can easily select languages:

    <form name="langSelect" action="" method="get">
         <select name="langID" id="langID">
             <option>Select Language</option>
             <option value="arab">Arabic</option>
             <option value="en">English</option>
             <option value="es">Spanish</option>
        </select>
    
        <br><br>
    
        <button type="submit">Submit</button>
    </form>
    

    Now we’ll create our translation files for the three languages inside the locale folder.

    Here’s the en.php file:

    <?php
    
    declare(strict_types=1);
    
    return [
        "welcome" => "Welcome to the tutorial",
    ];
    

    The arab.php:

    <?php
    
    declare(strict_types=1);
    
    return [
        "welcome" => "مرحبًا بكم في البرنامج التعليمي",
    ];
    

    And the es.php:

    <?php
    
    declare(strict_types=1);
    
    return [
        "welcome" => "Bienvenida al tutorial",
    ];
    

    Next, we’ll go back to our index.php file and display the “Welcome to the tutorial” line. The content should be translated according to the language the user selects from the dropdown. To do this, let’s add the following PHP code snippet to the index.php file:

    <?php
    
    declare(strict_types=1);
    
    $lang = $_GET['langID'] ?? 'en';
    
    $langArray = require 'locale/' . $lang . '.php';
    echo $langArray['welcome'];
    

    So, here we’re fetching the selected language code and importing that particular translation file. Then finally, we’re reading the contents of the welcome key from the imported file.

    Now let’s check out how to perform pluralization with PHP arrays.

    Pluralization with PHP arrays

    Okay – assume you need to display sentences such as, “I bought 1 book”, “I bought 4 books”, “I bought 10 books”, etc. You can see that the word for the number of items is either “book” or “books”, depending on how many books we are adding. To achieve this change, we’ll create a text box to input the number of books as below:

    <input type="text" id="numBooks" name="numBooks" placeholder="Add number of books.">

    We can successfully perform pluralization by simply adding the following part to the index.php file:

    <?php
    
    // ...
    
    $numberOfBooks = (int)($_GET['numBooks'] ?? 1);
    
    if ($numberOfBooks === 1) {
        $sentence = sprintf($langArray['i_bought_single_book'], $numberOfBooks);
    } 
    elseif ($numberOfBooks > 1) {
        $sentence = sprintf($langArray['i_bought_many_books'], $numberOfBooks);
    }
    else {
        $sentence = $langArray['i_bought_no_books'];
    }
    echo '<br/>';
    echo $sentence;

    If the number of books equals 1, it refers to the single_book key in the langArray array, and if not, it refers to the many_books key.

    Now let’s add these keys to the translation files.

    en.php:

    return [
        'i_bought_single_book' => 'I bought a book.',
        'i_bought_many_books' => 'Books bought: %d.',
        'i_bought_no_books' => 'I didn\'t buy any books.',
    ];

    es.php:

    return [
        'i_bought_single_book' => 'Compré un libro.',
        'i_bought_many_books' => 'Libros comprados: %d.',
        'i_bought_no_books' => 'No compré ningún libro.',
    ];

    arab.php:

    return [
        'i_bought_single_book' => 'شتريت كتابًا',
        'i_bought_many_books' => '%d شترى الكتب',
        'i_bought_no_books' => 'م أشتري أي كتب',
    ];

    Great job!

    PHP translation with gettext

    As you’ve seen in the first section, PHP translation is quite straightforward with the help of arrays. But as the system expands, it can be hard to maintain translation arrays. 

    The GNU gettext system is a widely used localization method in many programming languages such as PHP, Python, C++, and so on. In this section, we’ll look at how we can use the gettext technique for PHP translation. With gettext, it is very easy to sync locale files with changes to the codebase.

    Now, let’s make another small application to illustrate PHP localization with gettext. Before using gettext, you need to follow a few steps, as mentioned in the manual, to set it up.

    Firstly, create a separate folder for gettext localization implementation to avoid any possible confusion. I have named the folder PHP-Gettext here. Inside it, create a file named index.php and a locale folder. This folder should contain nested directories named after the corresponding locale codes. These folders should, in turn, contain messages.po and messages.mo files.

    Here’s the folder structure:

    • index.php
    • locale/
      • ar_EG/LC_MESSAGES/
        • messages.mo
        • messages.po
      • en_US/LC_MESSAGES/
        • messages.mo
        • messages.po
      • es_CO/LC_MESSAGES/
        • messages.mo
        • messages.po

    A .po is a portable object file. These files are saved in a human-readable format. In the context of gettext, all translations are stored in .po files.

    A .mo is a machine object file that is generated with the help of a special tool. These files are not meant to be edited directly. When the actual translation process takes place, gettext reads data from these files.

    Translate plain text

    Now let’s make an HTML form that includes a dropdown to pick a language from:

    <div class="col-md-6">
      <form name="langSelect" action="" method="get">
        <select name="langID" id="langID">
          <option>Select Language</option>
          <option value="ar_EG">Arabic</option>
          <option value="en_US">English</option>
          <option value="es_CO">Spanish</option>
        </select>
    
        <br><br>
    
        <button type="submit">Submit</button>
     
      </form>
    </div>

    Note that the value attributes are set to the corresponding locale codes.

    Next, we’ll need to create a PHP code in order to change the content between locales:

    <?php
    
    declare(strict_types=1);
    
    $lang = $_GET['langID'] ?? 'en_US';
    
    putenv("LANG=" . $lang);
    
    setlocale(LC_ALL, $lang);
    
    $domain = "messages";
    bindtextdomain($domain, "locale");
    textdomain($domain);
    
    echo gettext("welcome");

    This code snippet contains some important things to discuss. First, we’re fetching the chosen locale or reverting to the default option.

    The putenv() the function sets the environment variable of the LANG variable.

    Then the setlocale() method is employed to set the app locale.

    Next, we use the bindtextdomain() function and pass the folder and domain names as parameters. This function sets the path for a given domain. The textdomain() function is used to set the default domain. Finally, we’ve employed the gettext() function to look up a message in the given domain.

    Let’s now discover how to create the translation files – it’s quite straightforward. All you have to do is add the msgid and msgstr key and value pairs to the file as shown below.

    First, open the en_US/LC_MESSAGES/messages.po file. Once again, make sure to edit the PO, not the MO file!

    msgid "welcome" 
    msgstr "Welcome to the tutorial"
     
    msgid "i_bought" 
    msgstr "I bought "
     
    msgid "her_birthday"
    msgstr "Her birthday"
     
    msgid "now_time"
    msgstr "Current time"

    ar_EG/LC_MESSAGES/messages.po:

    msgid "welcome" 
    msgstr "مرحبًا بكم في البرنامج التعليمي"
     
    msgid "i_bought" 
    msgstr " اشتريت"
     
    msgid "her_birthday"
    msgstr "عيد ميلادها"
     
    msgid "now_time"
    msgstr "الوقت الحالي"

    es_CO/LC_MESSAGES/messages.po:

    msgid "welcome" 
    msgstr "Bienvenida al tutorial"
     
    msgid "i_bought" 
    msgstr " yo compré"
     
    msgid "her_birthday"
    msgstr "Su cumpleaño"
     
    msgid "now_time"
    msgstr "hora actual "

    Now we should create the MO files, which are effectively the compiled versions of the PO. To do this, run the following command:

    msgfmt messages.po -o messages.mo

    That’s it!

    Pluralization with gettext

    Next, we’ll discuss how to perform pluralization with gettext. Let’s start by creating our translation file contents by adding msgid and msgid_plural to the translation files as follows:

    en_US/LC_MESSAGES/messages.po

    msgid "single_book" 
    msgid_plural "many_books" 
    msgstr[0] "book"
    msgstr[1] "books"

    ar_EG/LC_MESSAGES/messages.po:

    msgid "single_book" 
    msgid_plural "many_books" 
    msgstr[0] "كتاب"
    msgstr[1] "كتب"

    es_CO/LC_MESSAGES/messages.po:

    msgid "single_book" 
    msgid_plural "many_books" 
    msgstr[0] "libro"
    msgstr[1] "libros"

    And so, we are done with our translation files.

    Open the index.php file and create a text box to get the user’s input:

    <input type="text" id="numBooks" name="numBooks" placeholder="Add number of books.">

    Now add:

    <?php
    if (!isset($_GET['numBooks'])) {
        $numberOfBooks = 1;
    }
    else {
        $numberOfBooks = $_GET['numBooks'];
    }
    echo $numberOfBooks . " " . ngettext('single_book', 'many_books', $numberOfBooks);

    Nice job!

    The define function in PHP

    So far, we have talked about two different methods of PHP translation: PHP arrays and gettext. In this section, we are going to learn how to perform PHP localization with the define function. This is a simple and straightforward method — let’s find out how we can implement it.

    First, create a new project with the following structure:

      • index.php

      • lo
          • arab.php

          • en.php

          • es.php

    Next, add translations to the en.php file:

    <?php
    
    declare(strict_types=1);
    
    define("welcome", "Welcome to the tutorial");
    

    The es.php:

    <?php
    
    declare(strict_types=1);
    
    define("welcome", "Bienvenida al tutorial");
    

    And the arab.php:

    <?php
    
    declare(strict_types=1);
    
    define("welcome","مرحبًا بكم في البرنامج التعليمي");
    

    Return to the index.php file and add the following code snippet:

    <?php
    
    declare(strict_types=1);
    
    $lang = $_GET['langID'] ?? 'en';
    
    include('locale/' . $lang . '.php');
    
    echo welcome;

    Please note that use of the HTML code is similar to that mentioned in previous sections. This is a very clear method for PHP translation as we’re simply including the translation file based on the user’s language selection.

    The PHP setlocale() function

    setlocale is an inbuilt function that supports PHP l10n. This assigns your system a geographical location and performs certain functions, such as content translation and currency and date formatting, based on the locale of that place. The syntax of this method is:

    setlocale($category ,$locale) 

    As you can see, the method accepts two parameters.

    The category is a constant that specifies the category of the functions. There are multiple categories that you can use depending on the functionality you need. Let’s discuss a few of those categories.

      1. LC_ALL – for all of the below

      1. LC_COLLATE – for string comparison

      1. LC_CTYPE – for character classification and conversion

      1. LC_MONETARY – for monetary/currency formatting

      1. LC_NUMERIC – for numeric formatting

      1. LC_TIME – for date and time formatting with strftime()

      1. LC_MESSAGES – for system responses

    Let’s discuss how we can use the setlocale method in PHP for localization. To get started, create a new project with an index.php file inside.

    Date formats

    As we know, date formats can change depending on locale. So, it is important to consider the locale when displaying date formats.

    You can check the locales you currently have installed by using the following command:

    locale -a

    We need to change the locale according to the selected language:

    $locale = match ($lang) {
        'en' => 'en_US',
        'es' => 'es_CO',
        'arab' => 'ar_EG',
        default => 'en_US',
    };
    
    setlocale(LC_ALL, $locale);
    

    Please note that if the locale you need to use is not already installed on the machine, the setlocale method returns false. You can use this return value to identify whether or not the given locale is installed.

    Once we have set the required locale, we need to display the time and date. To do this, we will use the strftime method. In this scenario, we will display two dates:

    <?php 
    
    declare(strict_types=1);
     
    setlocale(LC_ALL, "en_US"); 
    echo "The time in US is";
    echo(strftime("%m/ %d/ %Y   , %X "));
     
    echo ("<br/>");
     
    setlocale(LC_ALL, "zh_CN"); 
    echo "The time in China is ";
    echo(strftime("%m/ %d/ %Y   , %X "));

    Here we can use any date format by providing special placeholders. For example:

      1. %mmonth (01 to 12).

      1. %d day of the month (01 to 31).

      1. %Yyear including the century.

      1. %Xpreferred time representation without the date.

    So that’s how we can change date formats with setlocale i10n in PHP.

    Currency formatting

    Currency formatting is an important feature that PHP offers. Let’s go through a simple code snippet that uses currency formatting based on locale in PHP.

    <?php
    
    declare(strict_types=1);
    
    setlocale(LC_MONETARY, "en_US"); 
    $loc = localeconv(); 
    print_r($loc);
    

    Here we have used the LC_MONETARY category and the localeconv function. This function is used to obtain numeric formatting information.

    Now let’s see how to use the currency formatter:

    $number = 12000;
     
    setlocale(LC_MONETARY, 'en_US');
    echo money_format('%i', $number)

    The output of this code is USD 1,200.00.

    Likewise, you can change the locale of your code and format currency as required. Please note that the money_format method is only defined if the system has strfmon capabilities. Therefore, you can’t use money_format in Windows as it doesn’t have strfmon capabilities.

    Number formatting

    Next, let’s discover how to perform number formatting in PHP with setlocale. Take a look at the following code:

    $num = 1314.15;
    setlocale(LC_ALL, 'en_US');
    echo number_format($num, 1);
    echo "<br/>";
    echo number_format($num, 2);

    The number_format function takes two parameters. The first one is the number that should be displayed, and the other parameter is the number of digits after a decimal point. This is the output of the above code: 1,314.2 and 1,314.15.

    Frameworks

    Nowadays, most developers use frameworks in order to make development quick and easy. So, let’s briefly discuss the localization support that is provided by frameworks.

    Actually, almost all major PHP frameworks support PHP translation. Moreover, some even support features such as currency and date formatting. We are not going to have an in-depth discussion on how to implement PHP localization with frameworks here. So, we’ll briefly look at the methods these frameworks use for localization.

    The CodeIgniter and Kohana frameworks use PHP arrays for localization. The CakePHP framework uses the gettext method for translation. Both PHP arrays and INI techniques are used in the F3 for localization. The Laminas Project uses several methods for translation such as PHP arrays, CSV, TBX/TMX, gettext, Qt, XLIFF, and INI. These methods as introduced by the frameworks can be easily used for l10n in PHP.

    Note, however, that you may only load internationalization modules from your favorite framework and use it in your pure PHP application. So, ultimately the choice is yours.

    What is the best method for PHP localization?

    In this tutorial, we discussed multiple localization techniques that we can use in PHP. So, let’s see which one of them is the best.

    There are so many open questions in Stack Overflow regarding the best technique for PHP localization. The majority of PHP developers are of the opinion that gettext is the best method of localization. In the gettext method, you separate the translation text from the code. So, if you want to change the translation text, all you have to do is change the .po file without touching the code.

    Gettext is also very commonly used and has built-in support in PHP. Even WordPress uses the GNU gettext method to provide localized translations. However, it will not be easy to internationalize a whole application using gettext alone. You will need the other methods mentioned here and intl as well. In any case, developing large applications without using a PHP framework is very rare these days. So, when you select a framework, make sure to consider the localization methods it uses.

    Simplifying the translation process with Lokalise

    When your app is localized, you might be wondering: What if I would like to invite professional translators to work on the texts? Should I send them the translation files directly? Well, of course it is possible but perhaps not too convenient. Moreover, if you have many people working on the same project it will quickly turn to chaos. On top of that, you might need to introduce quality assurance or take advantage of machine translation.

    Is there a solution that can do the heavy lifting for you? Meet Lokalise, a translation management system for agile teams that is here to save the day! It offers all the basic translation features with the ability to work in teams, and advanced tools and integrations with dozens of third-party services including Amazon S3, GitHub, Figma, and many others.

    To get started, navigate to app.lokalise.com/signup and grab your free 14-day trial.

    Enter your details and follow the wizard to create a new project. A project is basically a collection of your keys and translations along with contributing users, settings, and other useful things.

    Once you’ve navigated to the newly created project, click More > Settings and take note of the project ID.

    Next, click on your avatar in the bottom left corner and choose Profile settings. Switch to the API tokens tab and generate a new read-write token.

    Next, you can use the Lokalise CLI to easily upload and download translation files.

    For example, to upload your translation file, it is:

    lokalise2 file upload --lang-iso en --file "PATH_TO_PROJECT\PATH_TO_LOCALE\messages.po" --project-id PROJECT_ID --token YOUR_TOKEN

    To download translation file back to the project, it is:

    lokalise2 file download --unzip-to PROJECT_PATH\PATH_TO_LOCALE --format po --token YOUR_TOKEN --project-id PROJECT_ID

    That’s it!

    Summary

    In this tutorial, we discussed several methods that we can use for PHP translation such as, PHP arrays, PHP define functions, and gettext and setlocale methods. Also, we’ve seen that the majority of PHP frameworks support localization.

    The PHP array and define function methods are quite simple and easy to work with. However, as the system grows it gets harder to maintain translation files. Because of that, those two methods are more suitable for small applications. Then we use the gettext method for PHP translation. But it does have the drawbacks of not supporting number, date, or currency formatting. Finally, we discussed the setlocale function which is quite an easy method for performing localization. 

    After all this, you might be interested in using PHP frameworks. In that case, be sure to read our Laravel guide and the Symfony tutorial.

    Talk to one of our localization specialists

    Book a call with one of our localization specialists and get a tailored consultation that can guide you on your localization path.

    Get a demo

    Related posts

    Learn something new every two weeks

    Get the latest in localization delivered straight to your inbox.

    Related articles
    Localization made easy. Why wait?
    The preferred localization tool of 3000+ companies