Localizing WordPress Themes and Plugins

What WordPress provides

Look through almost any of the core WordPress files and you will see that it is littered with English:

<?php
menu[0] = array(__('Dashboard'), 'read', 'index.php');

if (strpos($_SERVER['REQUEST_URI'], 'edit-pages.php') !== false)
	$menu[5] = array(__('Write'), 'edit_pages', 'page-new.php');
else
	$menu[5] = array(__('Write'), 'edit_posts', 'post-new.php');
if (strpos($_SERVER['REQUEST_URI'], 'page-new.php') !== false)
	$menu[10] = array(__('Manage'), 'edit_pages', 'edit-pages.php');
else
	$menu[10] = array(__('Manage'), 'edit_posts', 'edit.php');

At first glance this would suggest that to get WordPress in another language will require a special version of the code directly translated for that language. This sounds like a lot of work! Fortunately this is not the case, and WordPress makes use of a clever localization system called GNU gettext. This is a very stable and widely used framework that allows pieces of text to be ‘tagged’ as translatable, and then extracted into a dictionary where a lookup can made. Before we get too far into the specifics, let’s look at how text is tagged by taking a line from the above code:

$menu[5] = array(__('Write'), 'edit_pages', 'page-new.php');

Did you notice that surrounding the text ‘Write‘ is the strange looking __ ()? This is actually a special WordPress function that takes the text content (whatever is inside the brackets) and looks for a translated version. If a translation can be found it is used instead of the original. If no translation can be found then the original text is used.

WordPress provides two such localization functions:

  • __($text) – Looks for a translated version of $text and returns the result
  • _e($text) – Looks for a translated version of $text and echo the result to the screen (i.e. effectively it is echo __($text))

The basic premise of localization is that you mark all your displayable text with these special functions. This applies to both themes and plugins. Once marked you then run a localization tool over the code which will extract out all marked text into a template file. In the GNU gettext world this is called a POT file – Portable Object Template.

#: wp-admin/menu.php:10
msgid "Write"
msgstr ""

The POT file is simply a text file containing all the marked pieces of text, but organized in a special format.

When you have a POT file a translator can then add translations. This can be done by using a text editor, or by using a specialized localization tool. The role of the translator is to provide a translation for every marked string:

#: wp-admin/menu.php:10
msgid "Write"
msgstr "Ecrire"

Notice how you write your code in your native language (in this case English) and the file has an entry for each item of text with a translation into the localized language. This is effectively a dictionary, and in the GNU gettext world is called a PO file (Portable Object). The special WordPress functions __() and _e() take a piece of text and look through this ‘dictionary’, returning any translation that matches. If no translation matches then the original text is returned.

There is a final stage which involves ‘compiling’ the text PO file into a high-speed binary file called a MO (Machine Object) file. This makes the dictionary lookup process much faster.

To recap this chapter we’ve discovered that WordPress uses a standard localization framework called GNU gettext. The process involved in localizing a theme or plugin is:

  • Prepare the file by tagging all text (wrapping the text in the functions __() or _e())
  • Extract a localization template file (POT) from the file
  • Translate the template file into a specific locale (resulting in a PO file)
  • Compile the PO file to produce a high-speed MO file

Configuring the WordPress locale

WordPress supports localizations for the core code, plugins, and themes. Each localization is separate from the other. For example, if you use the French WordPress localization then all the core text will be in French, but your theme and plugins will not be. If you want everything in French then you must find a localization for the theme and each plugin.

Language files for WordPress can be downloaded from WordPress In Your Own Language. A downloaded language file should consist of a .mo file (possibly zipped) that should be placed in the WordPress language directory (wp-includes/languages/). For example, downloading and installing the Chinese localization
would result in this file being store:

wp-includes/languages/zh_CN.mo

By default, WordPress will always display text in US English, unless configured otherwise. To change this you must edit the file wp-config.php. Located in this file is a WPLANG setting:

<?php
// ** MySQL settings ** //
define('DB_NAME', 'putyourdbnamehere');    // The name of the database
define('DB_USER', 'usernamehere');     // Your MySQL username
define('DB_PASSWORD', 'yourpasswordhere'); // ...and password
define('DB_HOST', 'localhost');    // 99% chance you won't need to change
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');

// You can have multiple installations in one database if you give each a
$table_prefix  = 'wp_';   // Only numbers, letters, and underscores please!

// Change this to localize WordPress.  A corresponding MO file for the
// chosen language must be installed to wp-content/languages.
// For example, install de.mo to wp-content/languages set WPLANG to 'de'
// to enable German language support.
define ('WPLANG', '');

/* That's all, stop editing! Happy blogging. */

define('ABSPATH', dirname(__FILE__).'/');
require_once(ABSPATH.'wp-settings.php');
?>

Change this to reflect your chosen locale (without any file extension):

define ('WPLANG', 'zh_CN');

The next time you view your site the core of WordPress should be in your chosen locale:

Wordpress In Chinese

48 Responses to Localizing WordPress Themes and Plugins

  1. This is a great tutorial! After reading it I’m considering to internationalize my blog theme.

    However, the instructions about load_plugin_textdomain are incompatible with Gengo, a Compatibility Page:

    Just like the code that adds widget-capabilities to plugins, calls to load_plugin_textdomain cannot be made immediately. Plugins must call load_plugin_textdomain inside a function that runs on the ‘init’ hook, or at the earliest, the ‘plugins_loaded’ hook. Plugins that do not do this are coded incorrectly, according to advice from WordPress core developers.

  2. Hi Leonardo, I’ve updated the guide to reflect this. While it may cause incompatibility with Gengo, the method is only a suggestion by the WordPress developers and not a requirement (according to the Codex). Still, it is better to show the ideal method!

  3. I managed to create a pot file (this step is missing in the tutorial), translated it and compiled the po file into a mo file. "F jS, Y" is translated as "jS \\d\\e F \\d\\e Y" and I get times like "5th de April de 2007". PHP or WordPress aren’t translating "5th" to "5º" and "April" to "abril". I did set my browser and wp-config.php to pt_BR. What else should I do to get "5º de abril de 2007"?