Understanding _n_noop()

_n_noop() is one of the many functions overlooked by WordPress developers, probably because of it’s somewhat cryptic name, or perhaps due to lack of a good use case. Let’s see what the docs say:

Register plural strings in POT file, but don’t translate them.

Exactly. No, seriously, that’s what it does.

_n()

Before diving into how and why _n_noop() works, let’s revisit _n() — return the translated singular or plural form based on a given $number, so:

printf( _n( '%s comment', '%s comments', 1 ), 1 ); // 1 comment
printf( _n( '%s comment', '%s comments', 5 ), 5 ); // 5 comments

Quite simple, but why go through the trouble? You see, English is pretty straightforward, we either have one “comment” or we have two or more “comments”, that’s singular and plural. Some languages, including Russian, are not that simple:

1 комментарий
2 комментария
5 комментариев
21 комментарий

These are what we call plural forms, and gettext allows us to define a magic formula for each language when creating .po/mo files, which then all (quite magically) works with our _n() functions. Yey! But where does _n_noop() fit in?

The Problem

Let’s consider a situation, where we need to provide a string for translation, but we don’t really know that $number yet.

function my_comments_number( $singular, $plural ) {
    $count = get_comments_number();
    printf( _n( $singular, $plural, $count ), $count );
}
my_comments_number( '%s comment', '%s comments' );

_n() returns a single string based on a $count which we don’t have outside of our function, so we really to pass both strings to my_comments_number(), which then chooses one.

And it works.

However, if you’ve ever tried creating a .pot file from sources, you’ll know that Poedit and other tools can scan your .php files for references of __(), _e(), _n() and so on, and grab those strings for translation, which is awesome, because otherwise you’d have to manually add every single string.

Now, when these tools come across _n() in our sources, they know it’s a plural thing, because of a special keyword setting which looks something like _n:1,2, meaning _n() takes at least two arguments, where the first argument is the singular, and the second argument is a the plural, so it grabs both strings.

Let’s take a look at how Poedit and other tools will parse our function above:

  • Hello there _n() on line 3! I’m supposed to grab two of your arguments because I have this smart keyword setting, but none of these arguments are strings, so I’ll just skip to the next match
  • Done.

Whoops! Our actual strings never got interpreted, and that makes sense, because Poedit in this case was not asked to look for my_comments_number().

Sure, we can add a new my_comments_number:1,2 keyword to Poedit, scan our sources again and we’ll have our strings. But tomorrow we’re going to write a new function, and another one the day after. And before we know it, we have 700 keywords in our Poedit settings. Ouch.

This will also not work for functions that take a more complex set of arguments, like an array instead of two strings. Remember: POT generators can scan your files, but they can’t really understand your code.

Hmm, what if there was a single generic function that would allow us to add strings to our POT file, but not actually translate those strings at that very moment.

_n_noop()

Here’s how our my_comments_number function looks like with the use of _n_noop():

function my_comments_number( $nooped_plural ) {
    $count = get_comments_number();
    printf( translate_nooped_plural( $nooped_plural, $count ), $count );
}
my_comments_number( _n_noop( '%s comment', '%s comments' ) );

We’re using our singular and plural strings directly with _n_noop(), so Poedit and other tools will pick the strings up with the right _n_noop:1,2 keyword setting. The result of _n_noop() is just an array with our untranslated strings, which we pass on to our my_comments_number() function, which then calls translate_nooped_plural().

translate_nooped_plural() is like _n(), only instead of taking a singular and plural, it takes them as an array. The function is there for convenience only, and there’s absolutely nothing special about it. In fact, we might as well have written:

$singular = $nooped_plural['singular'];
$plural = $nooped_plural['plural'];
printf( _n( $singular, $plural, $count ), $count );

As you can see, there’s no magic going on here, and “noop” in _n_noop() does in fact mean “no operation”, because it doesn’t really do anything. The main purpose of _n_noop() is for Poedit and other tools to recognize strings when scanning sources.

Examples and use cases

Comments count is probably the most simple example to illustrate the use of _n_noop(), but in real-world you’ll probably use _n() directly because you can easily get the comment count with get_comments_number().

A good real-world example of _n_noop() is the post statuses count label, for custom statuses added with register_post_status(). By default core registers “draft”, “pending”, “published”, “private” and “trashed” along with a few internal ones.

These statuses have labels labels in the edit posts screen, for example “Draft (1)” or “Drafts (20)”, and you as a developer don’t have that number to pass on to _n() while registering your new post status, which is why register_post_status() accepts a label_count argument, and that’s where _n_noop() fits in:

register_post_status( 'idea', array(
    'label_count' => _n_noop( 'Idea (%s)', 'Ideas (%s)' ),
) );

If you take a close look at class-wp-posts-list-table.php in wp-admin/includes, you’ll see that $status->label_count is used with translate_nooped_plural(). Bingo.

Custom Post Status

A similar approach is used in the get_post_mime_types() function and the labels in the Media Library, like “Images (5)” or “Video (1)”, and you can add your own with a filter:

function my_post_mime_types( $types ) {
    $types['text'] = array(
        __( 'Text Files' ),
        __( 'Manage Text Files' ),
        _n_noop( 'Text File (%s)', 'Text Files (%s)' ), // Boom
    );
    return $types;
}
add_filter( 'post_mime_types', 'my_post_mime_types' );

Mime Type Labels

The code that outputs the labels is in media.php in wp-admin/includes, and it also uses translate_nooped_plural().

A rather “hackish” example is when you want Poedit and other translation tools to pick up certain strings, but you won’t necessarily use them. Core does this in wp-admin/about.php:

_n_noop( 'Security Release', 'Security Releases' );

Translation tools will pick these strings up, but core won’t use them until a security release, and when it does, the strings have already been translated in an earlier release. Regardless of text changes, there are no new strings to translate between a major release and a security release, which means it can ship faster. I think that’s pretty smart.

I guess _n_noop() is pretty interesting for developers building APIs in larger plugins, ones that let you register or override labels that may or may not be used with a number at a later stage.

What do you think? Can you come up with another use case for _n_noop()?

About the author

Konstantin Kovshenin

WordPress Core Contributor, ex-Automattician, public speaker and consultant, enjoying life in Moscow. I blog about tech, WordPress and DevOps.