Problem/Motivation
Discovered while working on #2611298: Port potx to Drupal 8 and #3159874: Port Plural formula configurator (l10n_pconfig) to D9.
Plural formulas are used to define how many variants should plural translations have. Also the formula defines when to choose which translaton. The only way Drupal core allows to set a plural formula is to import a .po file. Drupal 7 usedto eval()
the plural formula, however for performance optimisation, Drupal 8 does not store the formula, but stores a lookup array that makes it faster to look up the translations. See #1273968: remove eval from locale.module.
This leads to two problems:
- When exporting translations, the .po file does not contain the right amount of variants. This is handled in #2918489: Plurals are not exported correctly when exporting source translations .
- When exporting translaitons, the .po file does not contain the right plural formula. This would be in scope here.
Proposed resolution
Either store the plural formula and export it when needed for a .po file export. Or include a reverse lookup table in core to map the arrays back to plural formulas.
Remaining tasks
- Decide which approach to use.
- Fix exporting .po files to use the plural formula as well.
User interface changes
None.
API changes
To be determined.
Data model changes
To be determined.
Comment | File | Size | Author |
---|---|---|---|
#52 | interdiff-34-52.txt | 2.77 KB | RobinCS |
#52 | 2882617-52.patch | 9.38 KB | RobinCS |
#15 | drupal-plural-formula-string-2882617-15.patch | 7 KB | Sutharsan |
#15 | interdiff-2882617-8-15.txt | 3.05 KB | Sutharsan |
#8 | drupal-plural-formula-string-2882617-8.patch | 4.12 KB | Sutharsan |
Comments
Comment #2
idebr CreditAttribution: idebr at ezCompany commentedAdded #2882617: String version of plural formula is not available, exported .po files contain an incorrect default as a related issue
Comment #3
Gábor HojtsyI think we could store the plural formula in core for later use like this even if core does not actively use it. That would not solve existing sites/imports, but when a localization update happens, that should fix/update the plural info. (I think we should make sure that happens).
Comment #4
Sutharsan CreditAttribution: Sutharsan at LimoenGroen commentedI agree that storing the formula during the import is the most sensible to do. Forcing an import is one way to get the formula string stored. But alternatively and temporary Potx module can store the formula directly from user entered form input. But perhaps a (readme) instruction is the simplest to implement.
Comment #5
idebr CreditAttribution: idebr at ezCompany commentedI expected the plural formula to be available in PluralFormulaInterface->getFormula(). However, since this property and its getter/setter method is already taken, what about PluralFormulaInterface->getPluralRule($langcode)? Or perhaps this would warrant an API change?
Comment #6
Sutharsan CreditAttribution: Sutharsan at LimoenGroen commentedA first stab at the code. Not sure about whether to add the formula string as a parameter to the existing
::setPluralFormula()
or use a new::setPluralFormulaString()
.Needs tests
Comment #7
Gábor HojtsyI think the two new methods need to be on a different interface to keep backwards compatibility proper. Otherwise anybody implementing the interface will get a white screen due to the missing two methods when updating. We can implement the new interface as well in our own class though, so its the same from there.
Comment #8
Sutharsan CreditAttribution: Sutharsan at LimoenGroen commentedCreated an new interface
\Drupal\locale\PluralFormulaStringInterface
that defines the new methods.Class PluralFormula additionally implements the new interface.
Continuing with test code...
Comment #9
Sutharsan CreditAttribution: Sutharsan at LimoenGroen commentedThe above code stores the formula string it in a state variable, like the formulae array is currently stored in. I think it is better store it in the language (config) entity.
Further I discovered that the header of an exported translation file does not contain the actual formula, but the default ("nplurals=2; plural=(n > 1);") that is set in
\Drupal\Component\Gettext\PoHeader::__construct()
. This should go into a separate issue.Comment #10
Sutharsan CreditAttribution: Sutharsan at LimoenGroen commentedUsing the Language entity to store the plural_forms
- Requires a change of language.schema.yml (language.entity.*)
- Non-standard plural_forms values can be defined in \Drupal\Core\Language\LanguageManager::getStandardLanguageList / ::getUnitedNationsLanguageList()
- Adding ConfigurableLanguage::getPluralFormulaString requires an extra interface
I can not oversee the backwards compatibility consequences of the schema change.
Let's first decide where to store the plural formula string value.
Comment #11
idebr CreditAttribution: idebr at iO commentedFurther I discovered that the header of an exported translation file does not contain the actual formula, but the default ("nplurals=2; plural=(n > 1);") that is set in \Drupal\Component\Gettext\PoHeader::__construct(). This should go into a separate issue.
This is the subject of #2882667: [PP-1] Interface translations export contains incorrect plural formula that I linked as a related issue.
Comment #12
Gábor HojtsyI would store the string formula at the same place as the rest of the data about plurals for the sake of encapsulation.
Comment #13
andypostIIRR initially there was intent for separation of locales and languages
From BC POV I'd better added kinda "locale" entity with relation to language and next minors changes API to use it
But this requires split this issue
maybe
__toString()
better?Comment #14
Sutharsan CreditAttribution: Sutharsan at LimoenGroen commentedSo we agree that the formula string and the formula array should be stored together and separated from the Language entity.
That will only work if the plural formula is an object. But @anypost, that was your point. Architectural wise, it makes sense. But it would require a fair amount of reworking above and beyond this issue. I agree that this would deserve a separate issue.
Comment #15
Sutharsan CreditAttribution: Sutharsan at LimoenGroen commentedAdded unit test.
Comment #17
Gábor HojtsyComment #19
Tess BakkerAdded a small patch as proof that patch #15 works on exporting PO files.
Functional test:
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
""Plural-Forms: \n"
"Plural-Forms: nplurals=3; plural=((n==1)?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));\n"
Should there be a functional test in core/modules/locale/tests/src/Functional/LocaleImportFunctionalTest.php to check if an empty $formulaStrings[$langcode] isn't empty after an import?
Comment #20
Sutharsan CreditAttribution: Sutharsan at LimoenGroen commentedIn this issue a test should be added that PoDatabaseWriter::setHeader() sets the plural formula string.
Back to needs work to add this test.
Using the formula string in the Gettext component is a different issue and its tests should be added in that issue. Note that the Gettext component should not depend of a Drupal core service. But that is a different issue altogether.
Comment #22
mpp CreditAttribution: mpp at AmeXio for District09 commentedDon't forget to check if the nplurals is valid or you'll get a nasty bug in the User interface translation UI.
Comment #26
penyaskitoAdded the requested test with plurals for Arabic.
I'm not superhappy with the approach, as we are keeping the responsibility to the caller for keeping them consistent (e.g. I could call setPluralFormulaString without calling setPluralFormula).
But I don't think we can do this in a BC way without changing the interface per #7
Comment #27
penyaskitoTagging for tracking Global2020 related issues.
Comment #29
penyaskitoFixed errors and coding standards.
Comment #32
hestenetComment #33
darvanenCode is all clean and readable, test coverage looks good, and tests are passing.
I see no outstanding issues after reading the IS and comments, so I'm going to say this passes community review.
Comment #34
quietone CreditAttribution: quietone as a volunteer commentedI noticed this needs a reroll and I don't see a fail patch. Adding both now.
Attached is the reroll and a diff of that. I made one additional change and that was to a comment. Changing 'de' to 'Arabic'.
Also, I made a fail patch which only contains the test files. I didn't make an interdiff between the patch and the fail patch.
Comment #36
andypostback to rtbc
Comment #37
alexpottI don't get why we need to store the plural forms as a string. We're already reading it and storing it as an array after calling parsePluralForms.
I think storing both the string and array is a recipe for things getting out of sync. If need to be able to retrieve the plural form as a string again after loading can't we convert the array back to a string in getFormulaString()?
Comment #38
alexpottOr if we can't convert the array form back into a string form can we change setPluralFormula() to...
This way the string and array representations are stored together.
Comment #39
Gábor HojtsyThe array is optimised for lookup. Not sure if we can optimise it back for the string format again without it being too unwieldy honestly. It would be great if someone comes up with a magic solution to that.
Comment #40
andypostIIRC there was issue to use compiled/executable formula as performance improvement (maybe chx could recall) but I can't find it.
The idea was to speed-up evaluation of formula but then it was considered having not viable effect.
So maybe compromise here is to save both?
Comment #41
andypostComment #42
Ghost of Drupal PastLooking at http://docs.translatehouse.org/projects/localization-guide/en/latest/l10... there are only a finite and a very small finite number of possible plural strings, run each of them and compare to the array, you will get a match quick. Especially so if you pick them by npurals.
Comment #43
andypost++ I like the idea of having plural formulae (string representation) like there's
CountryManager
OTOH it could be part of standard language list shipped in core or even in
core/lib/Drupal/Component/Gettext
Comment #44
Gábor HojtsyI think the idea from #42 to take the finite (short) list of plural formulas and compare the arrays to what comes out of those is a good one. That would even allow potx to implement this without a core change. However, even though the issue summary does not mention core impact, core also supports exporting a .po file with translations in it? How does it do that and how is this not a problem there?
Comment #45
alexpott@Gábor Hojtsy it looks like core currently provides a default -
$this->pluralForms = 'nplurals=2; plural=(n > 1);';
- and allows you to set from an existing file - see \Drupal\Component\Gettext\PoStreamReader::readHeader() - but if you export a new file it looks like it is up to you to fix it after the fact. It looks like you've identified a bug.Comment #47
Gábor HojtsyPicking this up today as part of helping with porting localize.drupal.org to Drupal 9.
@alexpott, I would return to this:
Rethinking this, I think the suggestiont to include a mapping table of plural formats to arrays in core so we can look up the right string for the array we store is interesting. However it locks us into a finite set up plural forms and lock this part of the API in. While there may seem to be a finite set of plural forms used, it would be great to have a way to keep this open instead of making it locked in. I know its currently even more locked in with this bug, but yeah.
As for the .po file export not using the right formula, that was supposed to be #2882667: [PP-1] Interface translations export contains incorrect plural formula which was closed down in favour of #2918489: Plurals are not exported correctly when exporting source translations which only deals with the number of plural variants exported now, not the formula. I think it would be more within the scope of this issue to resolve the export of the formula as well, after all that is the ONLY place where this bug shows in core, otherwise itsjust a nice to have issue.
Comment #48
Gábor HojtsyRetitling based on the problem rather than the solution.
Comment #52
RobinCSRecap from DrupalCon Lille 23:
Reroll the latest patch:
I reroll the latest patch. A merge conflict (changes in an adjacent line) has been solved.