Developing WordPress Themes - Translation

Internationalization, often abbreviated as i18n, means making your site tailored for different audiences around the world. This includes translation (e.g. 'Contact Us' vs '聯繫我們') and localization (e.g. dd/mm/yyyy vs yyyy年m月d日).

In this article, we will focus on translation.

Theme vs Content Translation

There are two areas that needs to be translated - the templates, and the user-submitted content.

The templates must be internationalized so the text "Featured" on the menu in the English version of the theme will be shown as "焦點" in the Traditional Chinese version. Because templates are provided by the theme, translating the template is the job of the theme author, and does not rely on plugins.

Although some plugins that uses machine-translation can translate the entire page, including theme-content as well as user-content, we will not consider machine-translation here.

The user content must also be translatable; this is not supported natively in WordPress, but there are community plugins which enables this.

This series focuses on developing themes, and so we will consider only theme translation here. You can explore more on content translation in the article WordPress - Content Translation.

gettext

Translation in WordPress is done through gettext, a system which provides libraries and utilities for translation, originally developed as part of the GNU Translation Project.

Five Functions

To ultilize gettext, we'd need to wrap any content in our theme that we want translated, with the special gettext functions.

After translation has been done, these functions will use those translations and output the translated string.

There are five functions we will be dealing with:

  • __() - return the translated string; used in functions. Example: __('foobar');
  • _e() - echo the translated string; used in templates. Example: echo __('foobar');
  • _n() - return the translated string, but also specify the plural version. It takes three arguments - the singular version, the plural version, and a function that returns a number. If the number is higher than 1, the plural version will be outputted. Example: _n( 'comment' , 'comments' , get_comments_number() );
  • _x() - return the translated string, but provides a short description to give ambiguous terms some context. For example, the term table can mean 'a piece of furniture with a flat top and one or more legs' or 'a set of facts or figures systematically displayed, especially in columns'. _x() takes two arguments - the string to be translated, and the short description. Example: _x('table', 'a piece of furniture with a flat top and one or more legs'). This will give the translators some context to translate correctly.
  • _ex() - Same as _x() but echo instead of return

The following are equivalent:

  • _e('foobar');
  • echo __('foobar');

WordPress actually has 12 functions available, you can find them all in the Codex

Basic Translation

This is our string before wrapping it in a gettext function:

<span>Warning!</span>

And here it is after:

<span><?php _e('Warning!', 'brew'); ?></span>

Text Domain

brew is our text domain, which should be the same as the one we specified as the Text Domain header name in our style.css. The text domain acts as a sort of namespace to house our translations.

Remember to specify the text domain! Many developers forget!

Variables

Sometimes you'd have a variable in your to-be-translated string. For example, the copyright notice at the bottom of most pages:

© 2015 Brew Creations. All Rights Reserved.

The year is a variable, and, ideally, it should update automatically.

Instead of <?php _e('&copy; 2015 Brew Creations. All Rights Reserved.', 'brew'); ?>, we can instead use a placeholder, %.

echo sprintf( __( '&copy; %s Brew Creations. All Rights Reserved.', 'brew' ) , date("Y") );

The %s is a placeholder for a string, which is defined as the second argument of sprintf() - date("Y").

See the PHP documentation on sprintf for more examples of placeholders.

This is especially useful for translation between languages which have very different word order typology - some languages put the verb at the end of a sentence whereas others put the verb in the middle.

.po and .pot

After you've wrapped all the content inside gettext functions, you'd need to run a program which will scan the PHP source code for those gettext functions, and produce a .pot (Portable Object Template) file, which contains all the translatable strings (those wrapped in __() or _e()) in the original language.

The .pot file is in plaintext and acts a template for translators to translate from.

Translators then use this .pot template and fill it in with the translation for the strings. There should be one language for each .po file.

.pot - A template containing original translation

...
msgid "Description"
msgstr ""
...

.po - A filled .pot file - there should be one for each language

...
msgid "Description"
msgstr "簡介"
...

As you can see, in terms of structure, .pot and .po are the same, only difference being that the latter is filled in with the translated string.

Poedit

poedit is the most popular translations editor for gettext. Download it.

On Ubuntu, you can install poedit using apt-get.

$ apt-get install poedit

Then run poedit

$ poedit

A GUI will pop up.

Go to File > New Catalog.

Enter details about your project. Input nplurals=2; plural=n != 1; for the Plural Forms field. Refer to the documentation on Plural Forms for help.

Next, go to the 'Sources paths' tab and specify the directory you want poedit to scan. There are two input areas - 'Base path' and 'Paths'. The paths in 'Paths' are relative to the 'Base path', which in turn is relative to the directory where the .pot is located. So if your file is in the /languages directory of your theme directory, enter .. as the base path.

If your templates are in the theme directory root, add . as a path.

In the 'Sources keywords' tab, enter the following entries to tell which function names poedit should scan for.

__
_e
__ngettext:1,2
_n:1,2
__ngettext_noop:1,2
_n_noop:1,2
_c
_nc:4c,1,2
_x:1,2c
_nx:4c,1,2
_nx_noop:4c,1,2
_ex:1,2c
esc_attr__
esc_attr_e
esc_attr_x:1,2c
esc_html__
esc_html_e
esc_html_x:1,2c

If you're lazy, just enter the tags that you have used, although I'd recommend you to be as thorough as practical.

Click 'OK' save the file as a .pot file

After you click save, poedit will scan the directories you specified and generate a list of translatable texts, which should appear on your screen.

It will save this into the .pot file. It also generates a .mo file; you'd need to delete that.

Include the .pot file in your theme; it will be used by translators.

Translating

If you are a translator, or you want to provide some translations with your theme, you can. Open up your .pot file in poedit and start translating. Save the file this time as .po.

.mo

.pot and .po files are in plaintext and human-readable, but machines can't parse this file quickly. So .po files are compiled to their binary equivalent - .mo (Machine Object) files - using msgfmt. WordPress will use the .mo to serve the actual translations.

WordPress

In WordPress, the .po and .mo files must be named based on the locale exactly. You can find all WordPress locale codes at wpcentral.

next, we must let WordPress know which .mo files it should be looking for in the wp-config.php file.

define('WPLANG', 'zh_HK');

Also, inside one of the base templates that always gets loaded (e.g. index.php), run

load_theme_textdomain('brew');

This will load all the theme's translated strings in the specified text domain.

Conclusion

Now we are done making our theme translatable. When parsing the templates, gettext will use the key to look for the translation, and if none are found, the supplied string is used as a fallback.

If you enjoyed this article, why not take a look at the rest of the series on Developing WordPress Themes!

comments powered by Disqus