WordPress.org

Make WordPress Core

Opened 4 years ago

Last modified 2 years ago

#34308 new enhancement

The _n*() functions don't cater for a string that represents exactly one item

Reported by: johnbillion Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: I18N Keywords: dev-feedback 2nd-opinion
Focuses: Cc:
PR Number:

Description (last modified by johnbillion)

The first and second parameters of _n() and _n_noop() are sometimes (incorrectly) thought to represent one item and more than one item. They actually represent the singular form and the plural form, and in languages that have complex plural structures the singular form can be used for numbers such as 21 and 31, not just 1.

Recent tickets concerning this: #28502, #33239, #34127, #34307.

The solution to requiring a string to represent exactly one item and another string to represent multiple items is usually to use some simple logic:

if ( 1 === $number ) {
  _e( 'Item updated' );
} else {
  printf( _n( '%s item updated', '%s items updated', $number ), number_format_i18n( $number ) );
}

This is fine in most cases, however a problem arises when you want to register strings for translation in advance of knowing the number. For this, _n_noop() is used, but in the example above we'd also need to use __() to register the string which represents exactly one item.

While working on #32532, I came across a situation that would benefit from the introduction of a variant of _n_noop() which also caters for a string that represents exactly one item.

In order to allow bulk_post_updated_messages to be provided in the post type labels array, the messages need to use _n_noop() as they're registered in advance of knowing the number. Unfortunately, the locked message is the ugly duckling here and also requires a string that represents exactly one item, in addition to the nooped singular and plural forms.

This means that either a new bulk message needs to be introduced into the array (ugh), or the value of the message needs to be an array containing two items: the 'one item' string, and the nooped singular and plural forms. Both of these options turn this otherwise peachy array of nooped labels into a mess.

The introduction of a function like _n_noop() but which also supports a string that represents exactly one item would allow us to do this:

array(
	'updated'   => _n_noop( '%s post updated.', '%s posts updated.' ),
	'locked'    => _XYZ_noop(
		'1 post not updated, somebody is editing it.',
		'%s post not updated, somebody is editing it.',
		'%s posts not updated, somebody is editing them.'
	),
	'deleted'   => _n_noop( '%s post permanently deleted.', '%s posts permanently deleted.' ),
	'trashed'   => _n_noop( '%s post moved to the Trash.', '%s posts moved to the Trash.' ),
	'untrashed' => _n_noop( '%s post restored from the Trash.', '%s posts restored from the Trash.' ),
);

The translate_nooped_plural() function could be altered to support a $nooped_plural array which also contains an element representing exactly one item, return it if so, and return the singular form if not.

So, does this warrant the introduction of another l10n function?

Change History (14)

#1 @johnbillion
4 years ago

  • Description modified (diff)

#2 @johnbillion
4 years ago

  • Description modified (diff)

#3 follow-up: @johnbillion
4 years ago

Another option would be to extend _n_noop() and allow a fourth parameter for the 'exactly one item' string, but this would result in it being the only l10n function which accepted a parameter that came after its textdomain parameter.

#4 in reply to: ↑ 3 @GaryJ
4 years ago

Replying to johnbillion:

Another option would be to extend _n_noop() and allow a fourth parameter for the 'exactly one item' string, but this would result in it being the only l10n function which accepted a parameter that came after its textdomain parameter.

I would discount this option, for exactly the reason you've given. Automated tools that check and add textdomains to known functions, would now need to account for the potential occurrence of this extra param.

Plugins and themes which need to use _n_noop() with this extra param would need to specify a textdomain, instead of letting automated tools do it.


Could the first param accept an array? If it's a string, then treat it as now. If it's an array, have it known which is the exactly-one key, and which is the singular-but-not-one key?

Last edited 4 years ago by GaryJ (previous) (diff)

#5 @dd32
4 years ago

We also need to keep in mind gettext tools with these changes, for example, the gettext spec and PO/POT files don't have the provision for having both a one, singular, and plural form.

We could of course parse out _blah( 'one', 'singular', 'plural' ) into two strings (one, and the plural form) but that feels weird to me, especially since those strings wouldn't be related to one another when it got to the other end in the translation tool.

This ticket was mentioned in Slack in #meta-i18n by sergey. View the logs.


4 years ago

#7 follow-up: @Otto42
4 years ago

Doesn't the plural form system used in gettext already handle this?

https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html

Plural forms can be defined in extremely complex ways without changing the way that things like _n are defined. Doesn't seem like we need additional logic in PHP to handle those cases.

#8 in reply to: ↑ 7 @SergeyBiryukov
4 years ago

Replying to Otto42:

Doesn't the plural form system used in gettext already handle this?

Not if we make an (incorrect) assumption that _n() represents one item and more than one item. It actually represents the singular form and the plural form, and in languages that have complex plural structures the singular form can be used for numbers such as 21 and 31, not just 1.

See comment:23:ticket:28502.

#9 follow-up: @Otto42
4 years ago

@SergeyBiryukov Yes, I am aware of that. But the gettext system allows you to specify more than two strings for Plurals already. It can handle cases where there are three or four or even six different ways to pluralize things just fine.

If a language needs a specific rule for the "one" case, then it can already do that without any code changes. So, my question is: What is the specific case where this is needed? What can not be accomplished currently using a Plural-Forms ruleset?

Edit: I agree that _n( 'One item', '%d items', $number ) is an incorrect use of _n. Both strings for _n should always specify a number, not "One item".

Last edited 4 years ago by Otto42 (previous) (diff)

#10 in reply to: ↑ 9 @SergeyBiryukov
4 years ago

Replying to Otto42:

But the gettext system allows you to specify more than two strings for Plurals already. It can handle cases where there are three or four or even six different ways to pluralize things just fine.

Yes, and I used to have a fourth form in our .po files to handle this, see comment:5:ticket:28502.

But I couldn't use GlotPress, because:

  • It still only had three forms recommended by the gnu.org link you posted.
  • Changing the Plural-Forms ruleset for Russian across all projects just to fix a few incorrect strings in core did not seem feasible.
  • It would also make the Plural-Forms ruleset inconsistent with WordPress.com GlotPress instance.
  • Other Slavic languages have similar rules, not just Russian.

So before [31941], I still had to use Poedit to edit .po files and SVN to build packages.

So, my question is: What is the specific case where this is needed? What can not be accomplished currently using a Plural-Forms ruleset?

The issues in #28502 and this ticket could indeed be accomplished by changing the Plural-Forms ruleset, but changing it globally just to account for a few edge cases is complicated for the reasons listed above.

This ticket was mentioned in Slack in #core-i18n by ocean90. View the logs.


3 years ago

This ticket was mentioned in Slack in #core-i18n by ocean90. View the logs.


3 years ago

This ticket was mentioned in Slack in #polyglots by sergey. View the logs.


2 years ago

This ticket was mentioned in Slack in #core-i18n by sergey. View the logs.


2 years ago

Note: See TracTickets for help on using tickets.