How to Master the Translation of PHP Apps

How to translate php apps. This post reviews the available solutions. Make your application available in various languages.

Despite alternative web programming languages like Ruby (on Rails), Python or NodeJS dominating the industry headlines, PHP remains a popular choice for building web applications.

Despite this fact, there is still no real standard solution for localizing PHP applications.

Building a truly international application is not just about translating strings. Other issues to consider are date and time formats, currency symbols and pluralization. Programmers often underestimate the complexity of localization and get stuck with homemade code that is a pain to maintain. So, let’s talk about PHP Arrays, gettext, frameworks, and Intl.

PHP Arrays

Associative Arrays have been the primitive approach of many big and small PHP projects for a long time. Translatable strings are stored in an associative array, one file per language.

<?php
/* en.php - english language file */
$messages['hello'] = 'Hello';
$messages['signup'] = 'Sign up for free';
?>
<?php
/* de.php - german language file */
$messages['hello'] = 'Hallo';
$messages['signup'] = 'Kostenlos registrieren';
?>

The appropriate language file is loaded at the beginning of your application code:

<?php
    require('de.php');
    echo($messages['hello']);
    echo($messages['signup']);
?>

This approach is simple and easy to use, but it quickly reaches its limits. Consider an application that displays a notification when the user uploads files:

3 files uploaded successfully

What if it was only one file? Maybe do this:

1 file(s) uploaded successfully

Nah! That’s just plain ugly. I could come up with a hack like:

<?php
if($number_of_files == 1 ) {
  echo $number_of_files.messages['upload_success_single'];
  // 1 file uploaded successfully
} else {
  echo $number_of_files.messages['upload_success_multiple'];
  // 4 files uploaded successfully
}
?>

Not only does this clutter up the code, it doesn’t really solve the problem. In the English language appending a ‘s’ is enough, but pluralization works differently in other languages. For example, the pluralization of the world “file” (plik) in the Polish language works like this:

1 plik
2,3,4 pliki
5-21 pliko'w
22-24 pliki
25-31 pliko'w

gettext

The GNU gettext system has been around for more than 20 years. It is widely used and is the de-facto standard for localization in many programming languages.

Using gettext with PHP can be tricky in some setups.

If you are running a stock Ubuntu VPS, gettext will only support the locales installed on the machine. Or perhaps you are on a hosting plan where the gettext extension isn’t available.

In both cases, php-gettext can help. php-gettext it is a drop-in replacement for PHP enviroments where the gettext extension isn’t installed.

Basic Setup

In this example, i want to use English and German. I create the following directory structure:

index.php
    /locale
        /en_US
            /LC_MESSAGES
                messages.po
                messages.mo
        /de_DE
            /LC_MESSAGES
                messages.po
                messages.mo

Translations are stored in .po files, a simple plain-text file format. Using just a text editor I create en/LC_MESSAGES/messages.po:

msgid "hello"
msgstr "Hello"
msgid "signup"
msgstr "Sign up for free"

I also create a messages.po for the de_DE locale:

msgid "hello"
msgstr "Hallo"
msgid "signup"
msgstr "Kostenlos registrieren"

In the next step, .po files are compiled to .mo files. This can be done using the msgfmt command line utility:

msgfmt messages.po -o messages.mo

This is done for each .po file.

Usage

I can now use the .mo files via gettext in my PHP app:

<?php
    $language = "de_DE";
    putenv("LANG=".$language);
    setlocale(LC_ALL, $language);
    $domain = "messages";
    bindtextdomain($domain, "locale");
    textdomain($domain);
    echo gettext("hello");
    /* echo _("hello"); // Hint: _() is equal to gettext() */
?>

This script creates a new gettext enviroment using the de_DE locale. Then the message with the id ‘hello’ is echo’ed which will output the german “Hallo”.

The gettext system supports plural forms. Using the example from above, I create a plural msgid:

m
sgid "upload"
msgid_plural "uploads"
msgstr[0] "file uploaded successfully"
msgstr[1] "files uploaded successfully"

which I can use in my app code:

<?php
  echo $number_of_files." ".ngettext('upload', 'uploads', $number_of_files);
?>

gettext can handle pluralization but it has no tools for working with numbers, currency, date/time formats.

Frameworks

All major PHP frameworks have built-in support for creating translations. Some offer additional features such as classes for currency and date/time formatting.

Language files Plurals Date/Time Currency
Symfony YAML, XLIFF, PHP Arrays
F3 PHP Arrays, INI
CodeIgniter PHP Arrays
Kohana PHP Arrays
CakePHP gettext
Zend PHP Arrays, CSV, TBX/TMX, gettext, Qt, XLIFF, INI, …

The localization modules of these frameworks can be used as a standalone tool without a lot of overhead code from the framework itself. Check out our other tutorials to see what that could potentially look like:

4. Intl

PHP 5.3 introduces the Intl class. Intl is a set of convenient helpers for formatting dates, time, numbers and working with currency. It can be used to complement gettext or frameworks that lack some of the functionality.

Conclusion

Localization can sometimes seem challenging. Fortunately, you don’t have to try to solve the problems with homemade code.

Unfortunately, gettext doesn’t play very smoothly with PHP so I’d recommend using a framework.

Symfony, Zend, and F3 all do a great job and are easy to use. After playing around with all the frameworks, I really like the F3 approach. Here’s a step-by-step guide on getting started with F3.

Keep exploring

Photo-realistic sheet music featuring developer-style translation code in place of musical notes. The staff lines show snippets like t('auth.signin.button') and JSON structures, combining the aesthetics of musical notation with programming syntax to illustrate the idea of “composable localization.”

Blog post

Localization as code: a composable approach to localization

Why is localization still a manual, disconnected process in a world where everything else is already “as code”? Learn how a composable, developer-friendly approach brings localization into your CI/CD pipeline, with automation, observability, and Git-based workflows built in.

A woman in a light sweater sits in a home office, focused on her laptop, representing a developer or content manager working on WordPress localization tasks in a calm, professional environment.

Blog post

How to build a scalable WordPress i18n workflow

WordPress powers the web, but translating it well takes more than plugins. Discover how to build a scalable localization workflow using gettext, best practices, and the Phrase plugin.

Blog post

Localizing Unity games with the official Phrase plugin

Want to localize your Unity game without the CSV chaos? Discover how the official Phrase Strings Unity plugin simplifies your game’s localization workflow—from string table setup to pulling translations directly into your project. Whether you’re building for German, Serbian, or beyond, this guide shows how to get started fast and localize like a pro.

Blog post

Internationalization beyond code: A developer’s guide to real-world language challenges

Discover how language affects your UI. From text expansion to pluralization, this guide explores key i18n pitfalls and best practices for modern web developers.

A digital artwork featuring the Astro.js logo in bold yellow and purple tones, floating above Earth's horizon with a stunning cosmic nebula in the background. The vibrant space setting symbolizes the global and scalable nature of Astro’s localization capabilities, reinforcing the article’s focus on internationalization in web development.

Blog post

Astro.js localization part 2: dynamic content localization

Learn how to localize your Astro.js website with static and dynamic content translation. Explore Astro’s built-in i18n features and Paraglide for handling UI elements, navigation, and dynamic text seamlessly.