Problem/Motivation
In a .po file, gettext identifies an entry by its context (msgctxt) and its source text (msgid). For a plural entry, the msgid is the singular text only.
On the other hand Drupal identifies and stores a plural string with its singular and its plural text together, so for Drupal, a plural string and another string with the same text as the singular are two different strings. But in a .po file, they are two entries with the same key, and gettext tools reject the file:
msgid "1 minute"
msgstr ""
msgid "1 minute"
msgid_plural "@count minutes"
msgstr[0] ""
msgstr[1] ""
msgfmt -cstops with "duplicate message definition".msguniqmerges the two entries into one fuzzy entry, without the plural. Poedit runsmsguniqon such files when it opens them.
Currently, the translatable strings of Drupal core have five such duplicates.
| Text | Plural string | Other string with the same text |
|---|---|---|
| "1 minute" | core/misc/time-diff.js: Drupal.formatPlural(units, '1 minute', '@count minutes') |
$this->t('1 minute') in TimestampFormatter::getRefreshIntervals(), and in the time increments of DateTimeDatelistWidget and DateRangeDatelistWidget |
| "1 second" | time-diff.js: Drupal.formatPlural(units, '1 second', '@count seconds') |
$this->t('1 second') in TimestampFormatter::getRefreshIntervals() |
| "Display" | views-ui-view-info.html.twig: {% trans %}Display{% plural displays %}Displays{% endtrans %} |
$this->t('Display') in several places |
| "@count item" | The Some pager of Views: "@count item" / "@count items" | The Full pager of Views: "@count item" / "Paged, @count items" |
| "@count item, skip @skip" | The Some pager: "@count item, skip @skip" / "@count items, skip @skip" | The Full pager: "@count item, skip @skip" / "Paged, @count items, skip @skip" |
This problem has been raised many times with potential outside solutions such as #556498: Singular string being the same as the msgid of a plural string is invalid Gettext but Drupal relies on it anyway, but we can also just fix the five strings that are misusing source strings anyway. Some of these strings even have other problems:
- The refresh intervals and the time increments currently use
t()with a number, like$this->t('@count minutes', ['@count' => 5]). Witht(), a language has one translation for 5, 10, 15 and 30, but some languages need different plural forms for these numbers: Arabic, for example, uses one form for 5 and 10, and another one for 15 and 30. #3378675: Remove hardcoded "X minutes" translatable strings noted that a plural string would be better for translators. time-diff.jscurrently uses "1 minute" as the singular, butDateFormatter::formatInterval()uses "@count minute" since #2712603: Counts for singulars in DateFormatter::formatInterval() are hardcoded to 1, as in some languages, the singular form is also used for 21, 31 and so on. So currently, the PHP and the JavaScript code have different plural strings for the same time units, and translators translate both.- The summary of the Full pager is currently "1 item" for one item, but "Paged, 5 items" for five.
Proposed resolution
The source strings should be changed so that no plural string has the same singular as another string:
- Time units in JavaScript:
time-diff.jsshould reuse the existing plural strings ofDateFormatter::formatInterval(): "@count year" / "@count years", and the same for months, weeks, days, hours, minutes and seconds. - Time units in PHP:
TimestampFormatter::getRefreshIntervals(), and the time increments ofDateTimeDatelistWidgetandDateRangeDatelistWidget, should useformatPlural()with the same strings, for example$this->formatPlural(5, '@count minute', '@count minutes')in place of$this->t('@count minutes', ['@count' => 5]). - Views pagers: the Full pager should use "Paged, @count item" and "Paged, @count item, skip @skip" as the singulars.
- Views UI: the "Display" / "Displays" plural of
views-ui-view-info.html.twigshould have a context, for example{% trans with {'context': 'View displays'} %}. On 11.x, the copy of the template in the stable9 theme should have the same change.
Testing for the criteria of "no source strings have the singular as the same as a standalone string or another singular" would only be possible if potx would be integrated in core testing. I don't think that should be solved in this issue, but it could be a followup if desired. That would make core testing noticeably slower though, so not sure it is worth it.
Remaining tasks
- Review.
User interface changes
The summary of a Full pager of one item will be "Paged, 1 item" in place of "1 item". The other English text will not change.
Introduced terminology
None.
API changes
None.
Data model changes
None.
Release notes snippet
Not applicable.
LLM disclosure
LLM was used to draft this issue with human review.
Issue fork drupal-3631439
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 #3
gábor hojtsyComment #4
gábor hojtsyThere is the more general #3122231: Singular variant for plural string must contain @count, otherwise Drupal displays wrong data for untranslated strings which sets up a new practice to avoid this in the future. That intentionally avoids changing these strings though, so the two issues can work together.
Comment #5
kingdutchApproving this based on reviewing the MR and matching that against my experience with translations of our own product and the translation tooling we use that builds on POTX.
The text changes are as expected.
Comment #6
fmb commentedAlso looks good to me.
Comment #7
quietone commentedUpdated title, I remove 'Drupal core' since this is the core queue and removed the explanation, which can stay in the issue summary.
Comment #8
gábor hojtsyComment #9
alexpottCommitted and pushed 89df3a065de to main and 69ead8f8540 to 12.0.x. Thanks!
Backported to 12.0.x as allowed changes to strings before release.
Comment #13
gábor hojtsyYay, thanks! Let's continue in #3122231: Singular variant for plural string must contain @count, otherwise Drupal displays wrong data for untranslated strings.