WordPress Developers: Test your i18n (internationalization) knowledge!

Alex Kirk lives in Austria and is a developer on the i18n (internationalization) team at Automattic. We’re looking for talented people wherever they live —why not join our team


Whenever we write plugins or themes, there is one thing that needs a little extra attention and is quite frankly hard to get right: Translatable text.

Be it a button or some explanatory text, you generally will want to make that text be translatable to other languages, so that even more people can use your piece of software. While there is a very extensive guide available in the WordPress Handbook, we have created a fun way to brush up your knowledge on how to get things right: a quiz.

If you’re reading this post via a feed reader or an e-mail subscription, we encourage you to view the post on our developers blog to take the test (there are no winners or losers, this is meant to help you learn!), as it uses a little JavaScript to tell you whether an answer is right or wrong.

For each answer, we also provide an explanation, whether it’s right or wrong. So after clicking the answer that you think is right, make sure to click the other ones to explore what might be wrong about them.

So without further ado, take the quiz below!

You want to output the username in a sentence. Assume that the $username has been escaped using esc_html(). How do you do that?
<?php printf( __( 'Howdy, %s!' ), $username ); ?>
Good! Some languages may need to switch the location of the username to the front of this string. This code provides needed flexibility by including both the placeholder and the punctuation mark. Check the other answers though, there is an even improved answer.
<?php /* translators: %s is a username */ printf( __( 'Howdy, %s!' ), $username ); ?>
Awesome, the comment for translators is the cherry on the cake, as they cannot see variable names. Some languages may need to switch the location of the username to the front of this string. This code provides needed flexibility by including both the placeholder and the punctuation mark.
<?php printf( __( 'Howdy, %s' ), $username ); ?>!
This is almost correct. The punctuation mark should be included in the translatable string.
<?php echo __( 'Howdy' ) . ', ' . $username; ?>!
Translators may need to put the username first in other languages. That’s not possible with this code because it isn’t using a placeholder and a function that does substitution such as printf.
<?php _e( 'Howdy, %s!', $username ); ?>
The _e() function can only output text. It does not substitute variables.
<?php _e( "Howdy, $username!" ); ?>
Variables in a string are a no-no because the translated text is loaded by using the original English text which needs to be the same for all possible outputs.
You need to include a link in a sentence. How can you do that?
printf( __( 'Publish something using our <a href="%s">Post by Email</a> feature.'), 'http://support.wordpress.com/post-by-email/' );
Correct. Embed HTML in the string when it is necessary to keep the sentence structure intact for translators. Some examples would be href tags or bold/italics around a mid-sentence word.
_e( 'Publish something using our <a href="http://support.wordpress.com/post-by-email/">Post by Email</a> feature.' );
We don’t want to include URLs in the translation because we don’t want to expose them as translatable to translators. Also, if the URL is hardcoded within the string and then we ever change it, the entire string will become a new translation which will require re-translation.
printf( __( 'Publish something using our %s feature.' ), sprintf( '<a href="http://support.wordpress.com/post-by-email/">%s</a>', __( 'Post by Email' ) ) );
This code breaks the sentence up which causes a loss of full context during translation. We always try to keep full sentences/phrases together because having the whole string leads to much better translations.
Which of these is the correct way to use the single/plural _n() function?
printf( _n( '%d person has seen this post.', '%d people have seen this post.', $view_count ), $view_count );
Correct. Always use a placeholder in both singular and plural strings.
printf( _n( 'One person has seen this post.', '%d people have seen this post.', $view_count ), $view_count );
The hardcoded “One” in the singular string is problematic. We always want to use a placeholder in both singular and plural strings. Some languages (such as Russian) have multiple plurals which require the flexibility provided by using the placeholder in the singular string (#).
“So and so many people have seen this post” should be output like this:
printf( _n( '%d person has seen this post.', '%d people have seen this post.', $view_count ), $view_count );
Correct. We use the variable twice: 1) we need the number for the _n() function to determine the correct singular/plural text and 2) we need the number for the subsequent substitution in the printf. Also, it’s very important that the %d placeholder is used in the singular string (and not a hardcoded “1”) because some languages, such as Russian, have multiple plural forms. Those languages rely on that flexibility in the singular string.
printf( __( '%d people have seen this post.' ), $view_count );
For strings like this containing a numerical count, we want to use _n() instead because we always need to include the singular form of the string–even if the singular case should never happen. Why? Some languages, such as Russian, have multiple plural forms and they rely on flexibility provided by the singular string.
printf( _n( '%d person has seen this post.', '%d people have seen this post.' ), $view_count );
Almost. The _n() function also needs to know about the count value via its third parameter so it can determine the correct text.
printf( 1 == $view_count ? __( '%d person has seen this post.' ) : __( '%d people have seen this post.' ), $view_count );
Some languages have multiple plural forms–not just the typical singular/plural distinction–so this approach is problematic. We need to use _n() instead as it accounts for those multiple plural form complexities.
echo _n( 'One person has seen this post', "$view_count people have seen this post." );
Several things are amiss here. First, the hardcoded “One” needs to be a %d placeholder because some languages have multiple plural forms–not just the typical singular/plural distinction–and _n() with proper placeholdering handles that. The second issue is that $view_count needs to be a %d placeholder as well. Finally, all the above means that we need to switch the echo to a printf to use the placeholders and we’ll also want to add $view_count as a third argument to _n() as it expects a count value to determine which string to use.
How do you deal with outputting a variable in the context of a translation?
<h1><?php printf( __( 'Hello %s' ), esc_html( $world ) ); ?></h1>
Correct. Here PHP 1) swaps in the translated string which also contains the %s placeholder, 2) escapes the $world var safely, and then 3) substitutes the now escaped $world value into the placeholder spot. Exactly what we want.
One reminder, though: if you use this piece of code you need to be sure that you have verified your translations, so that your translation of Hello %s doesn’t include malicious code. If you don’t trust your translations, you should use a esc_html(sprintf()) construction instead of the printf.
<h1><?php printf( esc_html__( 'Hello %s' ), $world ); ?></h1>
This code is unsafe because it isn’t escaping $world at all. PHP runs esc_html__ first which swaps in the translated string (eg, "Hola %s") and then escapes it. Unfortunately, after that, printf swaps the value of $world into the placeholder which is unescaped. Danger, Will Robinson, danger!
<h1><?php echo esc_html__( sprintf( 'Hello %s' ), $world ) ); ?></h1>
We never want a sprintf inside a translation function. Translation files are generated by a cron job that parses (not execute!) PHP files looking for the translation functions sprintf isn’t resolved when that parsing happens which means this code will just be garbage translation data.
<h1><?php esc_html_e( 'Hello %s', $world ); ?></h1>
The second parameter of esc_html_e() is for a context value. We need printf here to do the variable substitution.
What’s the best practice to include formatted numbers in strings?
printf( _n( 'Today you already got %s view.', 'Today you already got %s views.', $view_count ), number_format_i18n( $view_count ) );
Correct. Use _n() for the possibly singular/plural string and use number_format_i18n() to actually format the number to local rules (for example some locales have a different thousand separator). We do indeed use %s here for the number because number_format_i18n() returns a formatted string.
$views = number_format( $view_count );
printf( _n( 'Today you already got %d view.', 'Today you already got %d views.' ), $views );
There are a few problems here. We want to be using number_format_i18n(). Also, number_format_i18n() produces strings, not numbers, so we need to use %s. Finally, in addition to printf, we need to give the count number to the _n() function so it knows which string variant to use.
_en_fmt( 'Today you already got %d view.', 'Today you already got %d views.', $views );
Arrowed! There isn’t a _en_fmt() function.
How to deal with multiple variables in a translated string?
printf( __( 'Posted on %1$s by %2$s.' ), $date, $username );
Almost correct. The placeholders are numbered so their values can be re-arranged if need be in translations. The remaining problem, though: translators don’t see the variable names, therefore they can only guess that the one variable is a date and the otherone is a username.
/* translators: %1$s is a date, %2$s is a username */
printf( __( 'Posted on %1$s by %2$s.' ), $date, $username );
Perfect. We make sure to number our placeholders so their values can be re-arranged if need be in translations. Also we give additional info to translators so that they can know which variable means what.
printf( __( 'Posted on %(date)s by %(username)s.' ), $date, $username );
Good thinking, but this syntax unfortunately is not available in PHP.
printf( __( 'Posted on %s by %s.' ), $date, $username );
We want to make sure we use numbered placeholders (ie, %1$s, %2$s, etc) whenever there is more than one placeholder because translators may need to re-arrange their locations in their translations.
Which of these is correct?
switch ( $type ) {
    case 'date':
        printf( __( 'Sorted by date' ) );
    case 'comments':
        printf( __( 'Sorted by comments' ) );
Correct. We want to give translators full sentences/phrases.
switch ( $type ) {
    case 'date':
        printf( __( 'Sorted by %s.' ), __( 'date' ) );
    case 'comments':
        printf( __( 'Sorted by %s.' ), __( 'comments' ) );
Unnecessarily breaking up sentences/phrases is a problem for translators. “Date” by itself may be translated differently from when it is used in a sentence, so we want to keep complete sentences/phrases together whenever possible.
$pattern = __( 'Sorted by %s.' );
switch ( $type ) {
    case 'date':
        printf( $pattern, __( 'date' ) );
    case 'comments':
        printf( $pattern, __( 'comments' ) );
This looks so efficient but unfortunately it’s wrong: essentially this is a concatenation of strings, which can’t be done in translations, because a generic translation of “date” might be wrong in the context of sorting. Or it would need to be in another grammatical case. Or other reasons. Short: don’t do that.
printf( __( 'Sorted by %s.' ), __( $type ) );
The code here won’t work because translation functions cannot be fed PHP variables. Translation files are generated by a cron job that parses (not execute!) PHP files looking for the translation functions. It doesn’t execute any of the PHP so the variable is unresolved which leads to garbage translation data (actually, the parsing just rejects it).

Meet Justin Shreve

Justin Shreve

Justin Shreve

Meet Justin Shreve: self-taught coder, cat daddy to Harris and Skylar, Google Summer of Code grad, and all around awesome guy.

What motivated you to become a developer? What do you like best about coding?

When I was little I used to sit in on the HTML classes that my parents would teach during the summers. I found it fascinating and wanted to learn more. By the next year I was answering questions that their students raised. From there I taught myself PHP and Javascript and eventually began working with WordPress.

Teaching himself PHP, probably.

Teaching himself PHP, probably.

I was so enthralled with how the different pieces worked together. That’s what really made me want to become a developer. To figure out how things were built, and how I could take them apart, rebuild them, and improve them.

The thing I like best about coding is there’s always something to do. Coding doesn’t get boring because there’s always a feature to add or a bug to fix.

Describe the killer app you’d build, if your wildest fantasy came true.

It’s a vague idea but I’d love to do some kind of augmented reality (AR) game. Maybe an app for something like Google’s Project Glass. I’ve always been interested in games and game design. I think AR and AR games could really be brought to a whole new level with the stuff that’s being developed. It’d be amazing to be a part of that and build a “killer app/game.”

If you could share just one piece of hard-won advice with young coders, what would it be?

Don’t be afraid to break stuff. It’s how you learn. Just jump in and start coding. There are plenty of resources available to you and plenty of people willing to help you learn.

Also, seriously consider playing with open source projects. I learned a lot more once I started working with WordPress and moved away from proprietary code bases.

Meet Justin Shreve is the second in our developer interview series. We’d like you to meet Beau Lebens, too.