More Internationalization Fun
So in my last post about Internationalization, I covered some non-obvious things that you should consider when adding translation capabilities to your code.
Today, let’s add to that by covering some non-obvious translation functions. You’re probably not using these, since they don’t get talked about as much. But there’s probably places where you should be using them, so knowing about them is the first step. And knowing is half the battle.
Basic functions, again
Last time I talked about these functions:
__()
_e()
_x()
_ex()
_n()
Let’s cover the ones I didn’t talk about.
Escaping output
In practice, you tend to use these mostly when outputting things onto the main page or in the admin. But, one thing you also use a lot when outputting text is the standard escaping functions. These are things like esc_html()
, which outputs text in a way that makes it “safe” to go onto a webpage, without being interpreted as HTML. If the text comes from user input, then this is a good idea.
Now, if you think about it, then the text you have may be translated in some other file, which you don’t control either. So escaping that text might be a good idea too. If somebody snuck bad code into a translation file, a user might get bad things displayed without being able to easily find it.
So you could write something like echo esc_html(__('text','text-domain'))
, but that’s a bit wordy. Let’s talk about some shortcuts.
The esc_html__()
function is the equivalent of esc_html(__(...))
. It does the escaped html and the double-underscore translation all in one go. Similarly, the esc_html_e()
function does the same thing, but it echoes the result, just like the _e()
function would. And there’s also esc_html_x()
, which is the equivalent of combining esc_html()
and _x()
.
Along with those three are the three identical equivalents for attributes: esc_attr__()
, esc_attr_e()
, and esc_attr_x()
. As the name implies, these combine the translation functions with esc_attr()
, which is the escape function specifically intended when you’re outputting text into html attributes.
Also note there’s no shortcut for the equivalent of _ex()
. It’s just not used that much, or at least not enough to need something special for it. Use an echo esc_html_x()
instead.
There are no shortcuts for the other escaping functions as yet, but these can save a few keystrokes and make your code that much more readable.
The Numerical No-op
So we’ve got some shortcuts for escaping with those three functions, but where’s the love for _n()
?
One of the problems with _n()
is that it tends to require the strings to be in the same place that the PHP variable is. For all the other functions, you could have a big file of strings in an array, and then reference those strings by name or something elsewhere because they don’t require any PHP variables. Nothing about them is computed at the time of the output.
But not so with _n()
, that $number to decide which string to use means that the strings have to be right there, they can’t be translated separately and referenced.
This is where _n_noop()
comes in. The _n_noop()
function basically takes the singular and plural strings for something, along with the text domain, and stores them in an array so that they can be referenced later by a function named translate_nooped_plural()
.
Perhaps an example is in order. Let’s go back to the tacos:
$string = sprintf( _n('You have %d taco.', 'You have %d tacos.', $number, 'plugin-domain'), $number );
What if we wanted those strings somewhere else? Like in a big file with all of our strings. Here’s a way to separate the strings from the _n() call:
$taco_plural = _n_noop('You have %d taco.', 'You have %d tacos.', 'plugin-domain'); $string = sprintf( translate_nooped_plural( $taco_plural, $number) , $number );
Now, that $taco_plural can be defined anywhere. Note that it contains no references to PHP variables. It’s basically static and unchanging. This allows us to separate it, then reference it elsewhere for the actual translation. The translate_nooped_plural()
function performs the same job as _n()
does, choosing which string to use based on the $number of tacos. The sprintf then pushes the $number into the chosen string, replacing the %d with the number.
Thus, that lets us extract the translatable strings out and put them anywhere we choose.
Also of note: The _nx_noop()
function is a cross between _n_noop()
and _x()
. It takes a context for the translators as the third argument, and the domain becomes the fourth argument. Useful if you need to explain to the translators the context surrounding the pluralization choice.
Numbers and Dates
The number_format_i18n()
function is functionally equivalent to the PHP number_format function. It lets you format numbers with commas at the thousands mark and so forth, except that it also takes localization into account. Not everybody uses commas for thousands and periods for decimals. This function will do the translation appropriately for that aspect.
The date_i18n()
function is functionally equivalent to the PHP date function. It will handle all the same string formatting parameters as date() will, but it will cause output to be translated for month names, day-of-week names, and so forth. Of note is that it doesn’t change the format requested. If some places put days before months, for example, it won’t handle that. But it will output the month name in the native language (if the translation pack has the right month name in it). So you may want to run the date formatting string through __()
as well, to let translators adjust the date format accordingly.
Wrap up
And that’s pretty much all the rest of the translation functions that I didn’t cover before. I may have forgotten a few useful ones here or there. Feel free to comment about anything I missed, or what you see most often, especially if you’re doing translations yourself.
[…] More Internationalization Fun […]
Thanks for this further inside on the topic, Otto. I’ve a question though, and it would be great if you will find the time to give me a feedback.
I’m coding an option page (and the related option managements code). I would like the whole to be easily translated and ported.
For few specific options I would like to have a dynamic URL in the description. So far I ended up with the following solution (which is part of an array containing the setting of the field).
'info' => sprintf( __( 'Enter your <strong>username</strong> to hide the Settings Page to other Admins. To restore a free access to this page, empty the field here or on the <a href="%s">WP options page under <em>framework_woo_super_user.', 'optimathemes' ), home_url().'/wp-admin/options.php')
Perhaps I could write it better, but the point is, is it a valid solution?
Thanks!
Seems perfectly fine to me, although I’d use the admin_url() function instead of home_url().
Also, you’re missing the closing (/a) tag in the string, unless the copy/paste ate it. Possible. 🙂
Thanks for your prompt reply!
Going after your suggestion, I’ve discovered the function network_admin_url(). I’ll try with that. But here we’re going off topic.
ps. You’re right about the missing closing tags: they are due to my failed attempts to make visible the html :-p
Otto,
Is it possible to make the following code
_e('hello',',mytheme');
work the following way?
_e('hello');
As you see, the second argument has been omitted.
It is easy to get the theme name ('mytheme') on the fly.
What does it take to teach the _e function to work with a global so that we do not have to type in 'mytheme' every single time we want to use the _e function? Is there any hook for that? If not, would you think that adding such a hook into the core would be a nice addition to WordPress?
In the core, we have this;
132 /**
133 * Displays the returned translated text from translate().
134 *
135 * @see translate() Echoes returned translate() string
136 * @since 1.2.0
137 *
138 * @param string $text Text to translate
139 * @param string $domain Optional. Domain to retrieve the translated text
140 */
141 function _e( $text, $domain = 'default' ) {
142 echo translate( $text, $domain );
143 }
Obviously, there is no filter hook here to interfere with the $domain value.
I’m also not sure what the string ‘default’ mean here? does it mean to say, current theme ( whatever the active theme is _ at the moment? If so, my question would then be totally void.
never mind this question.
After a little research, I came to know that _e’s 2nd argument cannot be omitted.
It is needed for .po .mo parsers to work.
Not being able to omit an argument is not a typical case for a regular function but that’s the way it is with _e due to the translation parsers.
Otto,
One topic you have neglected to mention is a plugin translating strings for use in javascript. WordPress provides the wp_localize_script function, but what is a good way to use it with messages that contain numbers? When I read about _n_noop, I thought it might be the answer.
I’m thinking that a rewording of the message is the best solution, although I’ve considered using ajax.