Lokalise graphic of PHP localization

Implementing PHP localization: A complete guide

In this tutorial, we’ll discuss how to implement PHP translation, localization, and internationalization. PHP remains a stable and popular choice for web development, and despite the rise of other programming languages like Python, Ruby, and Node.js, a large number of applications are still built using flat PHP without any framework. Effective translation management system can streamline your localization efforts. So, mastering PHP localization techniques is a must-have skill for web developers.

First off, let’s clarify what localization (l10n) is and why it’s such a common task for developers. In software engineering, localization means adapting the content of a system to suit a specific locale, ensuring the target audience in that locale can understand it. It’s not just about translating content from one language to another—it also involves handling date formats, number formats, currencies, text direction, and proper pluralization.

Before diving into localization, it’s important to understand software internationalization (i18n), which prepares your PHP application to handle multiple languages and cultural differences. This ensures that your application is flexible and ready for localization when needed.

For more details, check out our ultimate guide to software localization.

For example, pluralization rules are fairly straightforward in English compared to other languages. In some, like Slavic languages, you may have up to three or four plural forms for a given noun. Similarly, date, time, and number formatting can vary significantly depending on the target language.

So, as you can see, there’s a lot more to PHP localization than just simple language switching.

Let’s dive into how we can implement PHP l10n. This tutorial will cover five main approaches:

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

    PHP arrays

    This is the most basic method for implementing PHP translation and has been around for a long time. It involves creating an associative array for each language. These arrays map source language keys to localized content. When you want to display localized content, you simply select the appropriate array and key for the desired language.

    Plain text translation with PHP arrays

    Let’s look at a quick example. We’ll implement localization for three languages—English, Arabic, and Spanish—but of course, you can expand this to include any other locales. We’ll use English as the base language since it’s the primary language of most systems and applications.

    To keep things readable, we’ll place each array in its own separate PHP file, although you can store them all in a single file if you prefer.

    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  

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

    Next, in your index.php file, create a simple form with a dropdown to allow the user to select their preferred language:

    <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, let’s go back to our index.php file and display the localized text. The content should be translated according to the language selected from the dropdown. Add the following PHP code snippet to index.php:

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

    Here, we fetch the selected language code, load the corresponding translation file, and output the welcome message based on the imported array.

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

    Pluralization with PHP arrays

    Now, let’s handle pluralization. Suppose you need to display sentences like: “I bought 1 book”, “I bought 4 books”, “I bought 10 books”, etc.

    We can see that the word “book” changes based on the count, requiring singular or plural forms. Let’s add a text box to input the number of books:

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

    We can handle pluralization by adding this PHP code snippet to index.php:

    <?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 numberOfBooks equals 1, we use the i_bought_single_book key. For anything greater, we use i_bought_many_books. If zero or invalid, we fallback to i_bought_no_books.

    Now, let’s add these keys to our 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' => 'م أشتري أي كتب',
    ];

    And that’s it! This way, you can handle basic text translation and pluralization using PHP arrays.

    PHP translation with gettext

    As we saw in the first section, PHP translation using arrays is straightforward. However, as the system scales, maintaining translation arrays becomes cumbersome.

    That’s where the GNU gettext system comes in—a widely used localization method across many programming languages, including PHP, Python, and C++. With gettext, it’s easy to keep locale files in sync with codebase changes.

    Let’s build a small example to demonstrate PHP localization using gettext. Before diving in, you’ll need to set up your environment as described in the manual.

    Setting up the project

    Create a separate folder for this project to keep things organized. We’ll call it PHP-Gettext. Inside, create a file named index.php and a locale folder. This folder should contain subdirectories named after the locale codes, each holding messages.po and messages.mo files.

    Here’s the folder structure:

    PHP-Gettext/
     ├── index.php
     └── locale/
         ├── ar_EG/LC_MESSAGES/
         │   ├── messages.po
         │   └── messages.mo
         ├── en_US/LC_MESSAGES/
         │   ├── messages.po
         │   └── messages.mo
         └── es_CO/LC_MESSAGES/
             ├── messages.po
             └── messages.mo
    • .po (portable object) files store translations in a human-readable format.
    • .mo (machine object) files are compiled versions of .po files and are used during runtime.

    Translate plain text

    Let’s create a simple HTML form to allow language selection:

    <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 match the locale codes.

    Now, create a PHP snippet to switch between locales based on user selection:

    index.php

    <?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");

    Explanation:

    • putenv() sets the environment variable for the chosen locale.
    • setlocale() sets the application locale.
    • bindtextdomain() points to the folder containing the locale files.
    • textdomain() sets the default domain.
    • gettext() retrieves the translated string based on the set locale.

    Creating translation files

    Open the messages.po file for each locale and add your translations like so:

    en_US/LC_MESSAGES/messages.po

    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 "

    Compile the .po files into .mo files by running:

    msgfmt messages.po -o messages.mo

    That’s it!

    Pluralization with Gettext

    Gettext also supports pluralization using ngettext. Let’s set up plural rules by adding msgid and msgid_plural pairs:

    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"

    Next, add a text box to get the number of books from the user:

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

    And modify index.php:

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

    Great job! You now know how to implement PHP translation using Gettext for both plain text and pluralization. This method scales better for larger applications and is easier to manage compared to PHP arrays.

    The define function in PHP

    So far, we’ve covered two different methods for PHP translation: PHP arrays and gettext. In this section, we’ll learn how to implement PHP localization using the define function. This approach is simple and straightforward—let’s see how to set it up.

    Setting up the project

    Start by creating a new project with the following structure:

    index.php  
    locale/  
     ├── arab.php  
     ├── en.php  
     └── es.php  

    Adding translations

    Now, add translations to each file based on the locale:

    en.php

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

    es.php:

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

    arab.php:

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

    Displaying translated content

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

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

    This method works by defining constants for each localized message. When a user selects a language, the corresponding file is included, and the constant (welcome) is used to display the correct translation.

    The HTML form setup is the same as in previous examples, allowing users to select a language. This approach is clear and easy to implement, making it a great option for smaller projects or when managing a limited number of messages.

    The PHP setlocale() function

    setlocale() is a built-in function that supports PHP localization. It assigns your system a geographical location and helps control how content is displayed, including currency and date formatting, based on the specified locale. The syntax is:

    setlocale($category ,$locale) 

    This function accepts two parameters:

    • $category: A constant that specifies which aspect of localization to change.
    • $locale: A string representing the desired locale (e.g., en_US, es_CO).

    Let’s look at a few of the most common categories:

    • LC_ALL: Applies the locale settings to all categories below.
    • LC_COLLATE: For string comparison.
    • LC_CTYPE: For character classification and conversion.
    • LC_MONETARY: For monetary and currency formatting.
    • LC_NUMERIC: For numeric formatting.
    • LC_TIME: For date and time formatting with strftime().
    • LC_MESSAGES: For system responses.

    Let’s see how to use the setlocale() function in PHP for localization. To start, create a new project with an index.php file.

    Date formats

    Date formats vary significantly based on locale, so it’s crucial to set the correct locale when displaying dates.

    To check the locales currently installed on your machine, use the following command:

    locale -a

    Next, define a locale based on the language selected:

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

    Note: If the specified locale is not installed on the machine, setlocale() will return false. This can be used to detect missing locales.

    Once the locale is set, let’s format and display date and time using the strftime() method. In this example, we’ll show the time for two locales:

    <?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 "));

    strftime() Placeholders

    You can customize date formats using the placeholders in strftime(). Here are a few examples:

    • %m: Month (01 to 12).
    • %d: Day of the month (01 to 31).
    • %Y: Year with century (e.g., 2023).
    • %X: Preferred time representation (without the date).

    With setlocale() and strftime(), you can easily adapt date and time formats to match the conventions of different locales, enhancing the user experience for global applications.

    Currency formatting

    Currency formatting is an essential feature provided by PHP for localizing financial information. Let’s go through a simple example to see how to format currency based on locale settings.

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

    In the snippet above, we used the LC_MONETARY category with the setlocale() function and the localeconv() function to retrieve numeric formatting details specific to the set locale.

    Using the money_format() Function

    Now let’s format a currency value using money_format():

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

    The output of is USD 1,200.00.

    Note: money_format() is only available on systems that support the strfmon capabilities, so it won’t work on Windows environments.

    You can change the locale as required to format currency values for different regions. For instance, switching to en_GB would format the same number in a different way.

    Number formatting

    Next, let’s see how to perform general number formatting in PHP using setlocale().

    $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:

    1. The number to be formatted.
    2. The number of decimal places to display.

    This makes it easy to customize the presentation of numbers according to the needs of your application.

    Frameworks

    Nowadays, most developers rely on frameworks to streamline development and make it more efficient. Let’s take a quick look at the localization support provided by some popular PHP frameworks.

    Almost all major PHP frameworks offer built-in support for PHP translation, and some even include advanced features like currency and date formatting. While we won’t dive into detailed implementations here, we’ll outline the methods these frameworks use for localization:

    • CodeIgniter and Kohana: Use PHP arrays for localization.
    • CakePHP: Utilizes the gettext method for translation.
    • Fat-Free Framework (F3): Supports both PHP arrays and INI files for localization.
    • Laminas Project: Offers several methods, including PHP arrays, CSV, TBX/TMX, gettext, Qt, XLIFF, and INI.

    Many frameworks also provide internationalization modules that you can integrate into standalone PHP applications, giving you flexibility in how you approach localization.

    What is the best method for PHP localization?

    In this tutorial, we covered multiple PHP localization techniques. So, which one is the best?

    There’s a lot of debate on platforms like Stack Overflow, but many PHP developers consider gettext the most robust method. It separates the translation text from your code, so you can update translations by modifying the .po files without touching the application logic.

    Gettext is widely adopted and has native support in PHP. Even WordPress uses the GNU gettext system to handle translations. However, using gettext alone might not be sufficient for large applications—you might need to combine it with other methods, such as intl for advanced localization features.

    That said, it’s rare to build large-scale applications today without using a PHP framework. When choosing a framework, consider its localization options and decide which techniques align best with your project needs.

    Simplifying the translation process with Lokalise

    Once your app is localized, you might wonder: How can I invite professional translators to work on the texts? Should you send them the translation files directly? While it’s possible, it’s not the most efficient approach. If multiple people are working on the same project, it can quickly become chaotic. Plus, you may want to introduce quality assurance or take advantage of machine translation to speed things up.

    Is there a better solution? Meet Lokalise—a translation management system designed for agile teams. It offers all the core translation features, the ability to work collaboratively, and advanced tools and integrations with third-party services like Amazon S3, GitHub, Figma, and many more.

    Getting started with Lokalise

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

    Follow the setup wizard to create a new project. A project is essentially a collection of your keys and translations, along with settings, contributors, and other useful resources.

    Once inside your project, click More > Settings and take note of the Project ID.

    Lokalise project ID

    Click on your avatar in the bottom-left corner, choose Profile settings, and navigate to the API tokens tab. Generate a new read-write token.

    Generating Lokalise API token

    Using Lokalise CLI

    With Lokalise’s CLI, you can easily upload and download translation files:

    Upload translation file:

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

    Download translation file:

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

    And that’s it! Using Lokalise streamlines your translation workflow, making it easy to collaborate with translators and manage localization projects without the headache.

    Conclusion

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

    In this tutorial, we explored various methods for PHP translation, including PHP arrays, define functions, gettext, and setlocale. We also highlighted the localization capabilities of popular PHP frameworks.

    The PHP array and define function methods are simple and easy to implement, making them ideal for smaller applications. However, as your project grows, maintaining these translation files becomes more challenging. For larger projects, gettext offers a better solution by separating translations from code, but it lacks support for number, date, and currency formatting. Meanwhile, the setlocale function is a straightforward option for locale-based formatting.

    If you’re building larger applications, consider using a PHP framework with built-in localization support. For more details, check out our Laravel guide and Symfony tutorial.

    Related articles
    Stop wasting time with manual localization tasks. 

    Launch global products days from now.