Skip to content

Commit

Permalink
I18N: Fix plural forms parsing in WP_Translation_File.
Browse files Browse the repository at this point in the history
Ensures the plural expression from the translation file header is correctly parsed.
Prevents silent failures in the attempt to create the plural form function.

Adds additional tests.

Props Chouby.
See #59656.

git-svn-id: https://develop.svn.wordpress.org/trunk@57518 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
swissspidy committed Feb 1, 2024
1 parent 9486dc1 commit 5e178ff
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 4 deletions.
25 changes: 21 additions & 4 deletions src/wp-includes/l10n/class-wp-translation-file.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public function translate( string $text ) {
}

/**
* Returns the plural form for a count.
* Returns the plural form for a given number.
*
* @since 6.5.0
*
Expand All @@ -219,9 +219,9 @@ public function get_plural_form( int $number ): int {
$this->parse_file();
}

// In case a plural form is specified as a header, but no function included, build one.
if ( null === $this->plural_forms && isset( $this->headers['plural-forms'] ) ) {
$this->plural_forms = $this->make_plural_form_function( $this->headers['plural-forms'] );
$expression = $this->get_plural_expression_from_header( $this->headers['plural-forms'] );
$this->plural_forms = $this->make_plural_form_function( $expression );
}

if ( is_callable( $this->plural_forms ) ) {
Expand All @@ -231,13 +231,30 @@ public function get_plural_form( int $number ): int {
* @var int $result Plural form.
*/
$result = call_user_func( $this->plural_forms, $number );

return $result;
}

// Default plural form matches English, only "One" is considered singular.
return ( 1 === $number ? 0 : 1 );
}

/**
* Returns the plural forms expression as a tuple.
*
* @since 6.5.0
*
* @param string $header Plural-Forms header string.
* @return string Plural forms expression.
*/
protected function get_plural_expression_from_header( $header ) {
if ( preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s+plural\s*=\s*(.+)$/', $header, $matches ) ) {
return trim( $matches[2] );
}

return 'n != 1';
}

/**
* Makes a function, which will return the right translation index, according to the
* plural forms header.
Expand All @@ -247,7 +264,7 @@ public function get_plural_form( int $number ): int {
* @param string $expression Plural form expression.
* @return callable(int $num): int Plural forms function.
*/
public function make_plural_form_function( string $expression ): callable {
protected function make_plural_form_function( string $expression ): callable {
try {
$handler = new Plural_Forms( rtrim( $expression, ';' ) );
return array( $handler, 'get' );
Expand Down
Binary file added tests/phpunit/data/l10n/plural-complex.mo
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/phpunit/data/l10n/plural-complex.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
return ['x-generator'=>'GlotPress/4.0.0-beta.2','translation-revision-date'=>'2024-01-18 05:40:05+0000','plural-forms'=>'nplurals=4; plural=(n % 100 == 1) ? 0 : ((n % 100 == 2) ? 1 : ((n % 100 == 3 || n % 100 == 4) ? 2 : 3));','project-id-version'=>'WordPress - 6.4.x - Development','language'=>'sl_SI','messages'=>['%s update available'=>'%s razpoložljiva posodobitev' . "\0" . '%s razpoložljivi posodobitvi' . "\0" . '%s razpoložljive posodobitve' . "\0" . '%s razpoložljivih posodobitev']];
46 changes: 46 additions & 0 deletions tests/phpunit/tests/l10n/wpTranslations.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,52 @@ public function test_translate_plural() {
$this->assertTrue( $unload_successful, 'Text domain not successfully unloaded' );
}

/**
* @covers ::translate_plural
* @covers WP_Translation_File::get_plural_form
*/
public function test_translate_plural_complex() {
load_textdomain( 'wp-tests-domain', DIR_TESTDATA . '/l10n/plural-complex.mo' );

$this->assertSame( '%s razpoložljiva posodobitev', _n( '%s update available', '%s updates available', 101, 'wp-tests-domain' ) ); // 1, 101, 201
$this->assertSame( '%s razpoložljivi posodobitvi', _n( '%s update available', '%s updates available', 102, 'wp-tests-domain' ) ); // 2, 102, 202
$this->assertSame( '%s razpoložljive posodobitve', _n( '%s update available', '%s updates available', 103, 'wp-tests-domain' ) ); // 3, 4, 103
$this->assertSame( '%s razpoložljivih posodobitev', _n( '%s update available', '%s updates available', 5, 'wp-tests-domain' ) ); // 0, 5, 6
}

/**
* @covers ::translate_plural
* @covers WP_Translation_File::get_plural_form
*/
public function test_translate_plural_complex_php() {
load_textdomain( 'wp-tests-domain', DIR_TESTDATA . '/l10n/plural-complex.php' );

$this->assertSame( '%s razpoložljiva posodobitev', _n( '%s update available', '%s updates available', 101, 'wp-tests-domain' ) ); // 1, 101, 201
$this->assertSame( '%s razpoložljivi posodobitvi', _n( '%s update available', '%s updates available', 102, 'wp-tests-domain' ) ); // 2, 102, 202
$this->assertSame( '%s razpoložljive posodobitve', _n( '%s update available', '%s updates available', 103, 'wp-tests-domain' ) ); // 3, 4, 103
$this->assertSame( '%s razpoložljivih posodobitev', _n( '%s update available', '%s updates available', 5, 'wp-tests-domain' ) ); // 0, 5, 6
}

/**
* @covers WP_Translation_File::get_plural_form
*/
public function test_get_plural_form() {
$moe = WP_Translation_File::create( DIR_TESTDATA . '/l10n/plural-complex.mo' );

$this->assertSame( 0, $moe->get_plural_form( 1 ) );
$this->assertSame( 0, $moe->get_plural_form( 101 ) );
$this->assertSame( 0, $moe->get_plural_form( 201 ) );
$this->assertSame( 1, $moe->get_plural_form( 2 ) );
$this->assertSame( 1, $moe->get_plural_form( 102 ) );
$this->assertSame( 1, $moe->get_plural_form( 202 ) );
$this->assertSame( 2, $moe->get_plural_form( 3 ) );
$this->assertSame( 2, $moe->get_plural_form( 4 ) );
$this->assertSame( 2, $moe->get_plural_form( 103 ) );
$this->assertSame( 3, $moe->get_plural_form( 0 ) );
$this->assertSame( 3, $moe->get_plural_form( 5 ) );
$this->assertSame( 3, $moe->get_plural_form( 6 ) );
}

/**
* @covers ::translate_plural
*/
Expand Down

0 comments on commit 5e178ff

Please sign in to comment.