Problem/Motivation
Plural formula handling in PluralTranslatableMarkup::render() function 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...
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];
}
This leads to a problem where Plural Form Rules for languages where case for "count = 1" uses any other index but 0, leads to wrong Plural Form shown to user.
Example,
I was testing Plural Formula for Arabic, where singular form has index 1.
"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
Steps to reproduce
- On Standard Drupal installation enable locale module
- Add and enable "Arabic" language
- Upload a Arabic PO file with translation for "@count items", this way you can set the rules for Arabic.
- Update Plural Translation for "1 comment" by visiting Translation edit for "/admin/config/regional/translate" to something as follows,

Notice that the labels provided for translators are already wrong here, since the first item is pointing to form for "zero" and second item points to "Singular" form.
- Run following code to display translation for singular form in Arabic,
ddev drush eval "print_r(\Drupal::translation()->formatPlural(1, '1 comment', '@count comments', array(), array('langcode' => 'ar')) . PHP_EOL);"
Result
1 - zero
Expected Result
1 - one
Proposed resolution
TBD
Remaining tasks
TBD
User interface changes
TBD
Introduced terminology
TBD
API changes
TBD
Data model changes
TBD
Release notes snippet
TBD
| Comment | File | Size | Author |
|---|---|---|---|
| Monosnap User interface translation | Circle Dot 2024-12-27 15-14-35.png | 157.92 KB | d34dman |
Issue fork drupal-3496223
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 #2
d34dman commentedIf anybody shed light on why the conditional check for
$this->count == 1was included, it would be super helpful.Comment #3
ghost of drupal pastThe logic change was introduced in #532512-38: Plural string storage is broken, editing UI is missing, likely a simple faulty assumption was made.
It seems to me the following simplification can be made as a step forward:
From if ($this->count == 1 || $index == 0 || count($translated_array) == 1) { I kept only the last condition. As this issue states, the presumption $this->count == 1 indicates $index = 0 is faulty, so that needs to be removed and the $index == 0 case is superfluous anyways because both the if and the else case is $return = $translated_array[0] for zero index.
I think the fallback only exists when somehow locale_get_plural doesn't exist or a translation is not provided for all cases of the plural formula and so while it's not quite correct to use the presumption that [1] is somehow appropriate it is also quite irrelevant. I think so but I am not sure. Perhaps the last translation should be used instead of the second as that's usually more of an "open" case?
Ps1.: please continue not crediting me on core issues.
Ps2: speaking of faulty presumptions in this area, #1273968-2: remove eval from locale.module presumed precomputing 0-199 is enough but quite a few languages including French, Italian, Portuguese and Spanish contain a special case for 1000000. And Cornish is a nightmare. Another issue should be filed to fix this . It'll be fun. https://php-gettext.github.io/Languages/
Comment #4
d34dman commentedI have updated the MR with suggested changes.
>... precomputing 0-199
@ghost of drupal past, so we have a bug due to what seems to be a performance optimization. Thanks for the issues links which are quite helpful.
---
P.S. It seems like some comment/s are vanishing, like ghost comments! Is it Drupal.Orgs way of keeping discussion between a dead guy and a ghost spooky ¯\_(ツ)_/¯
Comment #5
smustgrave commentedDon't see an MR. but it should have test coverage when opened please
Comment #7
d34dman commentedI am happy to write some test, but would like to clarify few things,
Question A:
Am not sure about core policy regarding test, I think we would need a kernel test for this one, Is that assumption correct?
Question B:
I see testing this is tricky. This refactor touches part which needs locale module to be enabled and configured in a certain way. So should this test be part of Locale Module testing scope? i.e. test be written under namespace Drupal\KernelTests\Core\Locale instead of Drupal\KernelTests\Core\StringTranslation
Comment #8
ghost of drupal pastYeah, tests fail... here's a slightly better version, with comments:
The fallback was changed from fixed [1] to [$this->count != 1] (perhaps amend the comment to emphasize strict comparison doesn't work here -- it can be float for example). There are multiple cases when it's incorrect, like "0 is a special case" which what this issue is about but also say Czech has n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2 which means for all n > 4 the guess will use the second translation but the third would be correct. But: since either the plural formula is not accessible or there are no translations for this count , it's just impossible to know for sure which is the correct one. So it tries to do ... something. By this point we know there are two translations and the most common plural formula is n!=1 and so the highest chance of being correct is to just use the second translation. It's ... not possible to do much better because , really, if you have three Slovenian translations which does n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : (n % 100 == 3 || n % 100 == 4) ? 2 : 3 well good luck knowing which one is missing.
Comment #9
d34dman commented@ghost of drupal past,
I am afraid, we are increasing the scope of the fix.
As far as the scope of this bug report is about, it feels removing the hardcoded condition for "$this->count == 1" should be enough. Trying to find a sane default for cases under following scenario should be followed up in a separate issue (or issues),
- Case A: Translation data is corrupted, i.e. Message array doesn't contain the string at a particular index.
- Case B: Translation index formula can be wrong because of using default plural formula for language where it doesn't make sense.
- Case C: Translation index formula can be wrong due to corrupted Plural Form header in the PO file.
The main source of above scenario would be content/configuration done by User. And as pointed out in comment #8, the outcome has too many variations to handle.
As reported in this issue, despite having configured messages and Plural Formula correctly, Drupal Core fails to use the correct index, and hence that needs fixing.
Comment #10
ghost of drupal pastSure, it might be too much I was trying to optimize but it was just an idea.