Translating PHP application using gettext & msgfmt with .po / .mo files

January 17, 2013

Firstly install the gettext cmd line tools.

brew install gettext
brew link gettext

If your not a big fan of homebrew you can do the same using macports. Personally I really like homebrew and if you’d like to give it a try go to Homebrew. This will install useful cmds such as xgettext, msgfmt, msginit & msgmerge.

Fire up a fresh terminal and cd into your PHP apps working directory /Applications/MAMP/htdocs/my_app/ in my case.

I’d suggest getting yourself a local environment to test this on. I’m using MAMP and have created a new folder in the htdocs called localetest. Call it whatever you like and create a few PHP files with strings of echo gettext("This is translatable string"); or echo _("This is translatable string");

Once you’ve done this open up a terminal and cd into your cd /Applications/MAMP/htdocs/yourfoldername/

Once in it your going to run a command which will recursively search through the PHP files in this entire directory looking for translatable strings.

find . -name "*.php" | xargs xgettext -a --from-code=utf-8

or more simply

find . -iname "*.php" | xargs xgettext

The first part of this command will search recursively starting from the current level of directory structure for .php files. xargs builds and executes from standard input and executes the xgettext command for each file. This command searches the input from the found files for strings of gettext() or _() in a .php file and outputs them into a messages.po file.

Run the command and you’ll end up with a file a little like this one.

# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <email@address>, YEAR.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSIONn"
"Report-Msgid-Bugs-To: n"
"POT-Creation-Date: 2013-01-17 10:13+0000n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONEn"
"Last-Translator: FULL NAME <email@address>n"
"Language-Team: LANGUAGE <>n"
"Language: pt_PTn"
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=UTF-8n"
"Content-Transfer-Encoding: 8bitn"

#: another.php:3
msgid "Another string in file 2"
msgstr "mumbo jumbo"

#: index.php:3
msgid "Lets make this a translatable string"
msgstr "oh mumbo jumbo la la la"

#: index.php:4
msgid "Another translatable string"
msgstr "Otra cadena traducible"

#: index.php:5
msgid "Something here"
msgstr "Algo aquí"

If you look toward the bottom of this file you’ll notice it’s picked up our translatable strings. Also, notice, I have manually filled in the language this is going to correspond to under “Language: pt_PTn” and also set the character under the content type: “Content-Type: text/plain; charset=UTF-8n”. You’ll also notice I made some mumbo jumbo translations (which are not portuguese) to illustrate the translation.

Create a directory structure in your app which is going to help you organise your translations for instance mine is something like this.

Once you create this you will need to fire off a few more cmd line cmds. So fire up your terminal boys…

The next cmd you will run will create a .mo file which is a machine readable binary file used by gettext in PHP to quickly provide the translations.

msgfmt -cv -o locale/pt/LC_MESSAGES/ locale/messages_pt.po

After you run this cmd you should get a response finishing in 4 translated messages. This is actually the last step in the creation of files. Next we need to configure our app to detect the locale and forcibly translate our gettext snippets on the fly for us.

This can be achieved in a multitude of different ways and my solution below will not suit all. It is mainly here for demonstration purposes only. In the top of any of your .PHP files in your local directory add the following code.

$locale = "pt_PT";
setlocale(LC_ALL, $locale);
bindtextdomain("messages", dirname(__FILE__).'/locale');

This will force your app to use the locale pt_PT and in doing so use the translations provided by you in the .po file.

Important: Each time you provide more translations you must re-build the .mo files with the following command.

msgfmt -cv -o locale/pt/LC_MESSAGES/ locale/messages_pt.po

Edit: I recently found that when receiving translations from Windows users (as is the case quite often) the .po files need to be re-saved with UTF-8 encoding. I also found sometimes Windows users might get .BOM extension added to the files as well. This will not be interpreted by msgfmt correctly and will result in a fatal error on compilation. A save in the correct encoding will fix this.

Also another handy tip when installing gettext for translating:

Since I wrote this post the brew formula for gettext has changed and now is ‘keg-only’ you will find it is not symlinked and symlinking it may cause issues when installing and compiling other applications.

Even though it is not symlinked you can still find the binary like so:?

/usr/local/Cellar/gettext/0.18.2/bin/msgfmt -cv -o locale/fr_FR/LC_MESSAGES/ translations/messages_fr.po
About me

Hello! I'm David Heward, how are you going? I'm a Senior Devops/Build Engineer, specialising in AWS & Cloud Automation. Based in London. Strong 10+ year background in Software development. Have a read of my blog. Have a look at some of my working projects. Contact me at @davehewy or on Linkedin.