What’s the difference between __(), _e(), _x(), and _ex()?

If you're a thorough plugin developer you're internationalizing (i18n) all your strings. WordPress includes several functions that allow the programmer to easily make his plugin usable natively worldwide:

  • __()
  • _e()
  • _x()
  • _ex()
  • _n()

In some older plugins you may find the function _c() which is deprecated and replaced by _x() since version 2.9. If you're not familiar with i18n of your plugin you should take a look at the Codex.

All these functions translate a string to the language defined in wp-config.php. So what's the difference between them?

__() and _e() are the simplest functions: they return or echo the translated string. Their usage should be obvious: one string, one translation.
Not so obvious is the function of _x(). Suggest you have two strings in two different contexts in your plugin which are totally the same in your language. Are you sure they are also the same in the remaining 3000+ languages spoken on earth? Even Sebastian Heine is unlikely to know it so you shouldn't be sure neither.
Fortunately the creators of WordPress have a solution in their bag: _x().This function contains an additional parameter:

string _x (string $text, string $context, [string $domain = 'default']) 

By using the parameter $context you can differentiate identical strings in different contexts. Simple, eh? The remaining problem is to define two different string in the .po file. That's not too difficult neither:

msgctxt "test1"
msgid "testing"
msgstr "context1"

msgctxt "test2"
msgid "testing"
msgstr "context2"

The magic token ist msgctxt which you might translate to "message context". The code

echo 'Context: test1 -> ' . _x('testing', 'test1', 'test');
echo '<br>Context: test2 -> ' . _x('testing', 'test2', 'test');

would create the output

Context: test1 -> context1
Context: test2 -> context2

The remaining function _ex is a combination of _e and _x: it echoes a translated string using a context.

If you're using a visual editor for your .pot files you should be aware that not every editor is capable of handling context definitions and might destroy your data.

Last but not least _n() for retrieving the plural or single form based on the amount.

_n( $single, $plural, $number, $domain = 'default' )

If the domain is not set in the $l10n list, then a comparison will be made and either $plural or $single parameters returned. The function returned via the filter ngettext and about this you can filter the returned strings.

Update (10 JUN 2011): The above explanation of _n() seems to have confused some people. Maybe a few examples will clarify the usage of the function.

A simple example of _n() would be:

$domain = 'test';
$comment_count = 1;
echo _n('comment', 'comments', $comment_count, $domain) . '<br/>';
$comment_count = 2;
echo _n('comment', 'comments', $comment_count, $domain);

the corresponding German (de_DE) language file entries are:

msgid "comment"
msgid_plural "comments"
msgstr[0] "Kommentar"
msgstr[1] "Kommentare"

and the output:

Kommentar
Kommentare

which are the correct singular and plural translations.

If you're planning to output the value of the numbers you have to use sprintf(). Here's an example:

$approved = 1;				
echo sprintf( _n( '%s comment approved', '%s comments approved', $approved, 'test' ), $approved);
echo '<br/>';
$approved = 2;				
echo sprintf( _n( '%s comment approved', '%s comments approved', $approved, 'test' ), $approved );

The code might look a bit confusing so let's rewrite it:

$approved = 1;
$text = _n( '%s comment approved', '%s comments approved', $approved, 'test' );
echo sprintf($text, $approved);
echo '<br/>';

$approved = 2;
$text = _n( '%s comment approved', '%s comments approved', $approved, 'test' );
echo sprintf($text, $approved);

First a correctly translated singular or plural string is stored in $text. Then this string is evaluated by sprintf() which replaces the specifier "%s" with the value of $approved.

Using the following language file definitions

msgid "%s comment approved"
msgid_plural "%s comments approved"
msgstr[0] "%s Kommentar genehmigt"
msgstr[1] "%s Kommentare genehmigt"

the output is

1 Kommentar genehmigt
2 Kommentare genehmigt

Of course there's also a combination of _n() and _x() called _nx()

function _nx($single, $plural, $number, $context, $domain = 'default')

Adding Input Fields To Comment Form

Most comment forms contain the same input fields: Name, Email, URL and the comment text field. This is sufficient for most use cases but there are situations where you might want to know a bit more about your commenter: their age, the city they live in, or the color of their underwear. This article explains how to add an input field, store the data in the database and how to display the additional data in your blog if your theme uses the comment_form() function provided by WordPress. Continue reading …

Comment Form Hooks Visualized

Most themes (e.g. TwentyTen) use the comment_form() function to insert the comment form after posts. There are quite some hooks inside the function but they are hard to localize. The codex documentation isn't too helpful, neither.
To give you an easy overview the following diagrams visualize the points where the various hooks are anchored. The number of available hooks depend on the discussion settings and the user's capabilities.

In the most common scenario the user is not logged in, is allowed to comment on the article and the comments are not closed:

There are six hooks available:

  • comment_form_before
  • comment_form_top
  • comment_form_before_fields
  • comment_form_after_fields
  • comment_form
  • comment_form_after

You might have noticed that the hooks comment_form and comment_form_after seem to be anchored almost at the same point but depending on the user's role and discussion settings they are not always available so you should take care which hook you are using in your code.

User logged in
If you are logged in you have fewer hooks available since the name, email and URL input fields are not needed. The missing hooks are

  • form_comment_before_fields
  • form_comment_after fields

User is not logged and "Users must be registered and logged in to comment" activated
If your blog is configured that only registered users can comment, an unregistered user will see this comment form and additionally the hook comment_form_must_log_in_after is available. Please notice that in this case the hook comment_form is left out.

Comments closed
If the comments on the post are closed you have only one hook left (comment_form_closed) since the form is not displayed at all: