wppaste
WordPress

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

Since
2.8.0, 5.5.0
Source
wp-includes/l10n.php:482

Chooses between a singular and plural translated string by evaluating $number against the loaded translations for $domain. It returns the raw translated string, so you still need sprintf() or a similar call to insert the actual number into the placeholder. Reach for _n_noop() instead when the number isn't known until later and you need to defer the plural decision.

Translates and retrieves the singular or plural form based on the supplied number.

Description

Used when you want to use the appropriate form of a string based on whether a number is singular or plural.

Example:

printf( _n( '%s person', '%s people', $count, 'text-domain' ), number_format_i18n( $count ) );

Compatibility

WordPress
since 5.5.0
PHP
7.4–8.6-dev
  • 6.7.7
  • 6.8.8
  • 6.9.7
  • 7.0.4
  • 7.1.0

Present in every tracked release (6.7.7 to 7.1.0), and compiles on PHP 7.4 through 8.6-dev.

Parameters

$singlestring
The text to be used if the number is singular.
$pluralstring
The text to be used if the number is plural.
$numberint
The number to compare against to use either the singular or plural form.
$domainstringoptional
Text domain. Unique identifier for retrieving translated strings.
Default 'default'.Default: 'default'

Return value

string
The translated singular or plural form.

Code examples

Every example is editable and runs in a real WordPress booted in your browser by WordPress Playground. Press Run, then edit the code: clicking away re-runs it. Nothing is sent anywhere until you do.

Print a plural-aware count of posts in the site

Count the baseline posts and let _n() pick the right form of the label before formatting the output.

$posts = get_posts( array( 'post_type' => 'post', 'numberposts' => -1 ) );
$count = count( $posts );

$label = sprintf(
	_n( '%s post found', '%s posts found', $count, 'text-domain' ),
	number_format_i18n( $count )
);

echo esc_html( $label );

Report how many posts have a price set in post meta

Query for posts carrying the price meta key seeded on post 2 and pluralize the result count.

$priced_posts = get_posts( array(
	'post_type'  => 'post',
	'meta_key'   => 'price',
	'numberposts' => -1,
) );
$count = count( $priced_posts );

printf(
	esc_html( _n( '%s post has a price set.', '%s posts have a price set.', $count, 'text-domain' ) ),
	number_format_i18n( $count )
);

Common problems and fixes · 3

Why does _n() return the plural form for a count of 0?

The singular or plural choice comes from the locale's plural rule inside translate_plural(), not from a simple check for $number equal to 1. In English, and in many other locales, zero items use the plural grammatical form, so this is expected behavior rather than a bug.

Why doesn't the number show up in the string _n() returns?

_n() only picks which of $single or $plural to translate and return, it does not substitute $number into the string itself. The returned string still contains the %s placeholder from your source text.

Why does my custom text domain's plural form never get filtered?

_n() runs the generic 'ngettext' filter first and then the domain specific 'ngettext_{$domain}' filter, built from the exact $domain string you pass. A typo, a mismatched domain, or a domain that was never loaded with get_translations_for_domain() means your add_filter() callback for 'ngettext_your-domain' never fires.

Alternatives and related functions

_n_noop
When the singular and plural strings need to be registered for translation before the actual number is known, such as inside a static array evaluated later with translate_nooped_plural().
_nx
When the same singular or plural word needs different translations depending on grammatical context, requiring an extra context string alongside $single and $plural.
__
When there is only one string to translate and no singular or plural variation to choose between.
number_format_i18n
When the numeric value returned by _n() as a placeholder needs locale-aware formatting before it is inserted into the translated string.

Performance profile

How much work a call to _n() does, and what it touches: the algorithmic scaling, the Zend instruction count per call across PHP versions, the hooks it hands control to, and the core code that calls it. Measured from the compiled opcodes, not a stopwatch, so every number is identical on any machine running the same PHP version, and every function in core is ranked by cost.

Cost class
Trivial

Touches nothing outside its own arguments.

Scaling
Constant

No loop in the body: the same number of instructions runs whatever you pass in.

Instructions
34

Executed per call on PHP 8.5. The body compiles to 34.

Plugin surface
2 hooks

Third-party callbacks on 'ngettext', 'ngettext_{$domain}' run inside this call, and their cost is not bounded by anything here.

Called by
45

45 places in core call this, so the cost is paid more often than your own code shows.

What it touches

  • hookthird-party callbacksapply_filters()called directly

Further down the call graph this can also reach query, option, cache, serialize and transient. Those are the worst case, several calls deep and usually down an error path, not what a normal call pays.

What one call costs · 1 distinct outcome

One number would be a lie: the work depends on which branch runs. These are every distinct cost _n() can have, taken from its control-flow graph on PHP 8.5.

WhenInstructionsCalls it makes
always34get_translations_for_domain(), ->translate_plural(), apply_filters(), apply_filters()

Across PHP versions

PHPCompiledExecutedBranchesNotes
8.6-dev34340
8.53434017 fewer instructions than PHP 8.4
8.451170
8.351170
8.251170
8.151170
7.451170

An instruction is not a fixed amount of time, so a matching count is not necessarily the same speed; what it rules out is a difference in the work itself.

Hooks and filters fired · 2

2 hooks fire while _n() runs, in this order:

  1. apply_filters( ngettext )filterline 497 (+15 into the body)

    Filters the singular or plural form of a string.

  2. apply_filters( ngettext_{$domain} )filterline 512 (+30 into the body)

    Filters the singular or plural form of a string for a domain.

Uses · 2

Used by · 45

Show all 45

Source code

function _n( $single, $plural, $number, $domain = 'default' ) {	$translations = get_translations_for_domain( $domain );	$translation  = $translations->translate_plural( $single, $plural, $number ); 	/**	 * Filters the singular or plural form of a string.	 *	 * @since 2.2.0	 *	 * @param string $translation Translated text.	 * @param string $single      The text to be used if the number is singular.	 * @param string $plural      The text to be used if the number is plural.	 * @param int    $number      The number to compare against to use either the singular or plural form.	 * @param string $domain      Text domain. Unique identifier for retrieving translated strings.	 */	$translation = apply_filters( 'ngettext', $translation, $single, $plural, $number, $domain ); 	/**	 * Filters the singular or plural form of a string for a domain.	 *	 * The dynamic portion of the hook name, `$domain`, refers to the text domain.	 *	 * @since 5.5.0	 *	 * @param string $translation Translated text.	 * @param string $single      The text to be used if the number is singular.	 * @param string $plural      The text to be used if the number is plural.	 * @param int    $number      The number to compare against to use either the singular or plural form.	 * @param string $domain      Text domain. Unique identifier for retrieving translated strings.	 */	$translation = apply_filters( "ngettext_{$domain}", $translation, $single, $plural, $number, $domain ); 	return $translation;}

Changelog

Introduced in 5.5.0. Unchanged from 6.7.7 through 7.1.0.

  1. 6.7.7
  2. 6.8.8
  3. 6.9.7
  4. 7.0.4
  5. 7.1.0

Signature, return type and hooks compared across 5 parsed releases.

5.5.0
Introduced ngettext-{$domain} filter.from the docblock
2.8.0
Introduced.from the docblock

About this page

Parsed data
Generated from the wordpress-develop 6.7.7 tag, from src/wp-includes/l10n.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it.
Corrections
Something wrong on this page? Report it and it gets fixed in the next regeneration.