Making Plugins and Themes Translation-Ready
Internationalization makes WordPress accessible in other languages, and it’s a must-have for work intended for wide distribution.
As WordPress continues to grow, not just in English-speaking countries but around the world, a tension grows: most programming is done in English, and most WordPress add-ons (either plugins or themes) are written with English strings. However, many WordPress users—now significantly more than 50%—are not first-language English speakers.
This is why, over the last decade, we’ve seen more and more discussion of and support for internationalization. Internationalization makes WordPress accessible in other languages, and it’s a must-have if you’re working on themes or plugins for wide distribution.
Term Note: “i18n” and “l10n”
Because this took me a while to understand: You’ll frequently see “internationalization” abbreviated as “i18n.” This is because, between the opening “i” and final “n” of the word <eminternationalization, eighteen letters exist. So you’re skipping 18 of the 20 letters in the word, and replacing them with the numeral of the count.
Localization, the word for taking advantage of internationalization, is often abbreviated as l10n, with the same logic. Again, “l-ten-n” doesn’t itself mean anything special; spelling it that way is just a clever numeral thing.
The Steps to Internationalization
Making it easy to translate a plugin or theme, with its native English strings for interface elements, requires three steps:
- Decide on, declare, and use a “text domain.”
- Use the relevant functions with your strings and that text domain.
- That allows you to generate translation files.
Armed with those translation files, people can translate your plugin or theme.
What Is a “Text Domain”?
A text domain is a way to segregate one set of “strings to translate” from other strings in the broader environment.
A text domain is the “translation namespace” for your plugin or theme. It’s a way for WordPress, your plugin, or your theme to segregate its list of “strings to translate” from other strings that might need to be translated in other parts of the broader environment.
Why is a text domain necessary or useful? Because different strings may mean different things in different plugins or themes. For example, “Let’s go!” could mean “Create a membership account” in one context and “Start the survey” in a different context.
So you declare a text domain, and use it with all your “gettext” or translating functions, which we explain in depth in the next section. That’s not too hard. (The established gettext project underpins the way the whole WordPress translation system works.)
You’ll want to list your text domain in your plugin or theme’s top comment block, located in
style.css for themes, or in your plugin’s main PHP file. That looks like this for plugins:
<?php /* Plugin Name: Pretend Plugin [Other comment-block information goes here] Text Domain: wpshout */
Or like this for themes:
/* Theme Name: Pretend Theme [Other comment-block information goes here] Text Domain: wpshout */
Gettext Functions: Keeping Your
So to make your texts translatable, they need to pass though a function that might replace “Let’s go!” with the Spanish, Korean, or Swahili equivalent. And for the sake of convenience, WordPress gives all of the functions that accomplish this goal very short names.
I think of the double underscore function,
__() as the master one. It’s got the most confusing name — but its basic goal is to do exactly what I described: to allow all strings to be translated into a language if they should be. You use it by passing it two parameters: your string, and your text domain. So it’ll look like:
echo __( 'WPShout is a great WordPress site!', 'wpshout' );
The double-underscore just returns the string; you have to echo it yourself. If that’s annoying you,
_e() can help: it’s the equivalent of
echo __(). It saves you a few characters, but doesn’t do anything else.
Our example, again:
_e( 'WPShout is a great WordPress site!', 'wpshout' );
Then, you deal with some of the finer points of translation.
Providing Context With
First, sometimes a word or phrase is pretty ambiguous. Let’s imagine, as a translator, you’re asked to just translate the word “post” into Arabic. Well, was that referring to a noun, like “a WordPress post”? Or maybe it was a noun, but a lamp post? Or possibly you meant the verb “to post,” which is the equivalent of “to publish.”
__( 'Post', 'wpshout' ) and
_e( 'Post', 'wpshout' ), where you’d leave the translator uncertain about the meaning, you can provide a context with the
_x() function. It looks like:
_x( 'Post', 'verb, as in "to publish"', 'wpshout' );
The middle parameter string is never presented to the user, but will show to translators to help them understand. (Also worth knowing, an
_ex() function exists, that as you might guess is the combinations of the
_x functions: it prints the translated, annotated string.)
Another place that languages get complicated is around pluralization. In English, I have “1 Comment” or “3 Comments”. To account for that, WordPress offers the
_n() function, which can process the difference between the two.
_n() usually requires that you make another call, to the
sprintf — the
s means it returns a string, while
echos for you) function. These — also strangely named — functions replace a token in a string with a value passed to them. So to use our comments example, it’ll looks something like:
printf( _n( 'One comment', '%s comments', $comments, 'wpshout' ), $comments );
You’ll note we’re using a numerical value, assigned to the variable
$comments twice. Let’s say
$comments has a value of 3. The first use of
$comments is for
_n to determine which pluralization to use: since 3 is more than 1, it’ll pick
%s comments rather than
The second use is for
printf to actually put that number into the string, replacing the
%s with the
$comments was set to. So we’d print “3 comments” in this case.
Other Gettext Functions
This covers all the essentials of the “gettext” functions, but there are others worth noting:
esc_attr__(), which will do the same as
__(), but it’ll take care of
esc_attr()(escaping HTML attributes) for you for safety’s sake. (Also in existence are
esc_attr_x(): those auto-
echoand allow for annotations, as you’d expect.)
esc_html__(): again, for safety’s sake you can combine your HTML escaping with your translation. (And again,
_n_noop()which “registers but doesn’t translate.” In other words, it does the same thing as
_n()with “no operation.” For more detail, or just some interesting insight into translation’s finer points, I can point you to this post from Konstantin Kovshenin.
In all cases, you use
wp_localize_script like so:
wp_localize_script( 'wpshout-js', 'strings', array( 'hello' => __( 'Hello!', 'wpshout' ); ) );
alert( strings.hello );
wp_localize_script() has three parameters:
- The handle we used in
wp_register_script()) for the JS we’re localizing
- An associative array of the translations, with “index” or property name as the key, and the value as the actual string to be used
The translation itself is handled by the same PHP functions we covered in the last section.
What on Earth are POT, PO, and MO files?
So far we’ve mainly been talking about programming PHP. But there’s a secondary part of the whole gamut that doesn’t really involve authoring PHP at all, and that’s generating translation files.
This has always been a bit of a hard point. Slowly the WordPress ecosystem is starting to build more robust, documented, and useful solutions for dealing with the actual work of translation — see GlotPress, translate.wordpress.org, and the Polyglot Make blog — but it’s still a bit of pain for an average user. We’ll not go into detail here as this is a shifting area, but I’ve used Poedit with marginal satisfaction and success myself.
Whatever form you used to create, edit, and read these translation files, there are three core types:
- POT (.pot) files: master files for a plugin or theme, which have gathered up all the strings from your
_ex()s, etc. These are then scooped up in the generation of…
- PO (.po) files, which are the plaintext translations of your English strings into the target language. You’ll rarely edit one directly — the format has a lot of noisy and intimidating parts — but you can open them in a text editor to make quick tweaks to the translation.
- MO (.mo) files: the final translations from English into the target language which are actually used and read by WordPress. These are binary files, so while you open can one in a text editor it won’t look like anything you’ll be editing… You actually you have to instead go back to the PO file and regenerate the MO file from there.
As I said, the generation and use of these files is one of the harder parts of i18n for me. But if your theme or plugin’s in the WordPress.org repository, they’ll take care of the .pot file for you, and then your translators will likely know how to manage the rest. If you need more details or want other tools, check out the “Translating WordPress” page.
What We’ve Learned About Internationalizing WordPress Code
We’ve covered the three core parts of making your WordPress code translation-ready: decide a text-domain, use it in your “gettext” functions, and then get a hold of the POT file to make your PO and MO files. The steps are each small and doable, and they make a big impact for non-English speakers, or those who are simply more comfortable in a different language they learned earlier. Happy hacking!