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
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
Comment #1
alberto56 commentedIf this is indeed a bug, a test might be needed in
and the code can be changed in the formatPlural() function at
Comment #2
alberto56 commentedHere is a patch of what I have in mind. Also, better title.
Comment #4
alberto56 commentedThe 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.
Comment #5
alberto56 commentedPatch at #4 no longer applied, here is an updated patch.
Comment #6
alberto56 commentedThe 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.
Comment #7
jhedstromPatch 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.
Comment #8
sivaji_ganesh_jojodae commentedPatch re-rolled with and without test.
Comment #11
sivaji_ganesh_jojodae commentedUpdated patch. Hope this will fix the failing test.
Comment #13
alberto56 commented@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".
Comment #14
alberto56 commented[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:
Comment #15
alberto56 commentedComment #16
alberto56 commentedComment #17
alberto56 commentedComment #19
alexrayu commentedStill reproducable with German as well.
Comment #20
alberto56 commentedI think this might meet the requirements of a major bug, specifically:
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:
Comment #21
alberto56 commentedOops, I think this still should be 8.1.x.
Comment #22
alberto56 commentedI 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.
Comment #24
alberto56 commentedThere 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.
Comment #25
alberto56 commentedComment #32
alberto56 commentedHere is the same patch as in #24, renamed; it should test with 8.5.x now.
Comment #34
alberto56 commentedSee also the possibly related #2660338: [pp-3] locale_get_plural call in PluralTranslatableString is wrong
Comment #36
pasqualleComment #38
alberto56 commentedHere 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.
Comment #42
alberto56 commentedThis 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.
Comment #44
alberto56 commentedThe 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".
Comment #50
d34dman commentedLooks 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 == 1to 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.
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);\nThus another case where Singular variation of translation can be found on index 1 (as opposed to index 0 in English).
Comment #51
d34dman commentedComment #52
ghost of drupal pastI 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.
Comment #53
d34dman commented@ghost of drupal past,
I have created a new issue here #3496223: Plural Formula gets ignored for Singular Case for languages where Singular Form is not found in the first index for #50.
Comment #54
d34dman commentedAfter 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.
Comment #55
d34dman commentedUn-assigning myself as I don't see any clear way to resolve this in core.