Any string in core or contrib modules will show 1 instead of 0 on languages with formula ($n1>1) with format_plural(), from the moment the string is introduced in the code until the moment the translation is imported into the site.

To reproduce this problem:

(1) New Drupal 8 site in English with standard install (problem also present on Drupal 7).
(2) drush en locale language -y
(3) Go to admin/config/regional/language/add and add French as a language (your server should have internet access, and the French language file will be automatically imported).
(4) Go to your command line with Drush and type:

drush eval "print_r(\Drupal::translation()->formatPlural(0, '1 comment', '@count comments', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(0, '1 comment', '@count comments', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(1, '1 comment', '@count comments', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(1, '1 comment', '@count comments', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(2, '1 comment', '@count comments', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(2, '1 comment', '@count comments', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(0, '1 whatever', '@count whatevers', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(0, '1 whatever', '@count whatevers', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(1, '1 whatever', '@count whatevers', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(1, '1 whatever', '@count whatevers', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(2, '1 whatever', '@count whatevers', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(2, '1 whatever', '@count whatevers', array()) . PHP_EOL);"

The result is:

0 commentaire
0 comments
1 commentaire
1 comment
2 commentaires
2 comments
1 whatever
0 whatevers
1 whatever
1 whatever
2 whatevers
2 whatevers

On line 7, I would expect to see "0 whatevers", not "1 whatever", because the cardinality is clearly 0.

On line 1, we do not have this problem because "1 comment/@count comments" has been translated to French and is present in the .po file:

"Plural-Forms: nplurals=2; plural=(n>1);\n"
...
msgid "1 comment"
msgid_plural "@count comments"
msgstr[0] "@count commentaire"
msgstr[1] "@count commentaires"

This issue can cause some confusion, for example:

#1788008: Good practice change: use @count in the singular form, so that the @count is exported in the .pot file
#170334: format_plural() correction on singulars
#384866: Clarify documentation for format_plural()
#816166: Documentation problem with format_plural

Possible solution

Perhaps \Drupal::translation()->formatPlural() (format_plural() in Drupal 7) could return the string in the cardinality of English in cases where it is not yet translated and the amount to be shown is 0.

Issue fork drupal-2273889

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

alberto56’s picture

If this is indeed a bug, a test might be needed in

core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php

and the code can be changed in the formatPlural() function at

core/lib/Drupal/Core/StringTranslation/TranslationManager.php
alberto56’s picture

Title: Untranslated strings show 1 instead of 0 on languages with formula ($n1>1) with format_plural(). » Don't use one language's plural index formula with another language's string in the case of untranslated strings using format_plural().
Status: Active » Needs review
StatusFileSize
new2.54 KB

Here is a patch of what I have in mind. Also, better title.

Status: Needs review » Needs work
alberto56’s picture

Status: Needs work » Needs review
StatusFileSize
new3.83 KB

The patch causes a problem with the unit test and the mock translator. I modified the unit test slightly, mostly because I don't understand how expects() works: the return for the anonymous function can be anything and the unit test will still pass without the patch and fail with the patch.

alberto56’s picture

Patch at #4 no longer applied, here is an updated patch.

alberto56’s picture

The problem occurs also in D7. Here is a quick and dirty patch which fixes it. I have set it to not test because:

(a) we should fix it in D8 before thinking about D7
(b) there is no test in the D7 version
(c) the technique used will work except in cases where the string is translated in the target language but identical to the source language

I'm putting the patch here because I needed it for a project and figured it might be useful to others as well.

jhedstrom’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Patch no longer applies.

Also, since there does appear to be a test here, that should be uploaded separately (as well as with the rerolled patch) to illustrate the current failure.

sivaji_ganesh_jojodae’s picture

Status: Needs work » Needs review
StatusFileSize
new3.83 KB
new1.5 KB

Patch re-rolled with and without test.

The last submitted patch, 8: 2273889-8-with-test.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 8: 2273889-8-without-test.patch, failed testing.

sivaji_ganesh_jojodae’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new3.85 KB
new1.5 KB

Updated patch. Hope this will fix the failing test.

Status: Needs review » Needs work

The last submitted patch, 11: issue-2273889-11-without-test.patch, failed testing.

alberto56’s picture

@sivaji@knackforge.com Thanks for this. You need to put the string "do-not-test" in the filename of the purposefully failing test to avoid the testbot setting the issue to "needs work".

alberto56’s picture

Status: Needs work » Fixed

[edit] this is actually still a problem, but format_plural() has been replaced with \Drupal::translation()->formatPlural(). Now you can reproduce this by installing the French language and running:

drush eval "print_r(\Drupal::translation()->formatPlural(0, '1 comment', '@count comments', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(0, '1 comment', '@count comments', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(1, '1 comment', '@count comments', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(1, '1 comment', '@count comments', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(2, '1 comment', '@count comments', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(2, '1 comment', '@count comments', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(0, '1 whatever', '@count whatevers', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(0, '1 whatever', '@count whatevers', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(1, '1 whatever', '@count whatevers', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(1, '1 whatever', '@count whatevers', array()) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(2, '1 whatever', '@count whatevers', array(), array('langcode' => 'fr')) . PHP_EOL);
print_r(\Drupal::translation()->formatPlural(2, '1 whatever', '@count whatevers', array()) . PHP_EOL);"
alberto56’s picture

Status: Fixed » Needs work
alberto56’s picture

Issue summary: View changes
alberto56’s picture

Issue summary: View changes

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

alexrayu’s picture

Still reproducable with German as well.

alberto56’s picture

Version: 8.1.x-dev » 8.3.x-dev
Priority: Normal » Major

I think this might meet the requirements of a major bug, specifically:

  • Interfere with normal site visitors' use of the site (for example, content in the wrong language, or validation errors for regular form submissions), even if there is a workaround.

  • Cause a significant admin- or developer-facing bug with no workaround.

This bug will cause the number 1 to be displayed instead of the number 0 in an untranslated string, and it is reasonable to expect certain sites to not have 100% translation of all strings and still be used by visitors or developers in the target language (French, German...). Also, during active development of a site, stakeholders are aware that not all strings are translated but interact with the site nonetheless; they will expect some strings to show up in English, but they will not expect underlying data (numbers) to be wrong.

The following (hypothetical) examples might cause site visitors or admins to take action based on false information:

  • If "You need one 1 badge to move to the next level; you currently have 1 badge" is displayed instead of "You need one 1 badge to move to the next level; you currently have 0 badges", users will react differently.
  • If "You currently have 1 article to approve" is displayed instead of "You currently have 0 articles to approve", admins might assume there is an article to approve even when there is none.
alberto56’s picture

Version: 8.3.x-dev » 8.1.x-dev

Oops, I think this still should be 8.1.x.

alberto56’s picture

Status: Needs work » Needs review
StatusFileSize
new1.5 KB
new3.85 KB

I renamed @Sivaji's patches from #11. These are otherwise identical but I renamed one to use the "do-not-test" string, which should avoid a failure if the other one passes.

Status: Needs review » Needs work

The last submitted patch, 22: issue-2273889-22-with-test.patch, failed testing.

alberto56’s picture

There seems to have been a lot of change in the class structure since the last patch, which is why it no longer applies. Here is a version which does apply, but I have not added a test yet:

The code is added to ./core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php, and we probably need a unit test in ./core/tests/Drupal/Tests/Core/StringTranslation/PluralTranslatableMarkupTest.php

Setting to needs review to see if we do not break existing tests.

alberto56’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 24: 2273889-24-core-8.1.x-plural-index-no-test.patch, failed testing.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

alberto56’s picture

Status: Needs work » Needs review
StatusFileSize
new1005 bytes

Here is the same patch as in #24, renamed; it should test with 8.5.x now.

Status: Needs review » Needs work

The last submitted patch, 32: 2273889-31-core-8.5.x-plural-index-no-test.patch, failed testing. View results

alberto56’s picture

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

pasqualle’s picture

Version: 8.6.x-dev » 8.8.x-dev

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

alberto56’s picture

Status: Needs work » Needs review
StatusFileSize
new6.46 KB
new7.42 KB

Here are two patches, one which only provides a failing test demonstrating the problem. The second is the same test along with a fix in code taken from previous patches.

Status: Needs review » Needs work

The last submitted patch, 38: 2273889-38-core-plural-index.diff, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

alberto56’s picture

Status: Needs work » Needs review
StatusFileSize
new7.34 KB

This is still an issue on the Drupal 9.2.x branch in my tests.

The patch still applies; here is a new version (without the fuzziness) to trigger the testbot.

Status: Needs review » Needs work

The last submitted patch, 42: 2273889-42-core-plural-index.diff, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

alberto56’s picture

The test is failing because the following code gives "0 heure" without the patch (as it should) and "0 heures" (wrongly) with the patch. The patch needs to be updated so that the following code gives "0 heure".

use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Component\Gettext\PoItem;
$langcode= 'fr';
$count = 0;
$translated_string = \Drupal::translation()->translate('1 hour' . PoItem::DELIMITER . '@count hours', [], ['langcode' => $langcode]);
$plural = PluralTranslatableMarkup::createFromTranslatedString($count, $translated_string, [], ['langcode' => $langcode]);
$plural->render();

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

d34dman’s picture

Looks like the bug in Core plural formula handling is in PluralTranslatableMarkup::render() function where it ignores the Plural Formula Rule and hardcode the case for $count == 1 to use first index.

https://git.drupalcode.org/project/drupal/-/blob/11.x/core/lib/Drupal/Co...

The patches in this issue thus far is not addressing this issue. Shouldn't we re-evaluate the situation and direction of the fix?

As you can see from the latest code, It is ignoring the getPluralIndex for case where count is 1.

     $index = $this->getPluralIndex();
    if ($this->count == 1 || $index == 0 || count($translated_array) == 1) {
      // Singular form.
      $return = $translated_array[0];
    }

More info

I was testing Plural Formula for Arabic, where singular form has index 1. Looking at the code, it seems like for count 1, we have a very special case where it ignores the formula and uses index 0.

"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n

Thus another case where Singular variation of translation can be found on index 1 (as opposed to index 0 in English).

d34dman’s picture

Assigned: Unassigned » d34dman
ghost of drupal past’s picture

I am fairly sure the issue in #50 is a different bug and needs a new issue: that one is about a bad assumption about the plural formula while this issue is about mixing the plural index from one language with the formula of another. Which also explains why the patch didn't fix the problem.

d34dman’s picture

After reading the comments and issue description, it looks like this is what is happening,

After following these steps,

1) New Drupal 8 site in English with standard install (problem also present on Drupal 7).
(2) drush en locale language -y
(3) Go to admin/config/regional/language/add and add French as a language (your server should have internet access, and the French language file will be automatically imported).

We ship only one Plural Formula in Drupal. Which is the default one which is "Two forms, singular used for one only" with formula n != 1.
Unless the site admin knowingly or un-knowingly tell Drupal about the formula to be used for a language, this default one is used. Setting Plural formula in Drupal is not obvious. This can be done by

- A) uploading a PO file for the language, where the importer sets the formula in the backgroun

- B) OR by explicitely setting this via module like Plural formula configurator

Until the above configuration is created, for all languages, the default Plural Formula would be applied. This configuration is instance specific as the values are saved in Drupal's state. Should we create a follow up issue to move this to configuration so that it can be made deployable?

Now why does French has an issue with this?

French Plural form is "Two forms, singular used for zero and one", with formula n>1. Thus, the singular form is used for both "0" and "1", unlike the default formula. So applying the default formula would experience buggy behaviour.

To resolve this issue correctly, Drupal will have to ship correct plural form for all languages. This is quite a complex task since we can have 1:N mapping for language and plural_form.

So the work-around for now is to guide the Site Admin to make sure correct formula is configured before translations are added.

Another manifestation of the "missed-configuration" can be found in Interface Translation Edit form, which would show only two editable entries for languages which have more than 2 Plural Forms like Polish, Arabic, Ukranian,....

---

P.S. I came to this issue due to a bug discovered while writing this module string_plural_form which provides an alternate approach to manage Plural Formulas in Drupal site.

d34dman’s picture

Assigned: d34dman » Unassigned

Un-assigning myself as I don't see any clear way to resolve this in core.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.