fix(i18n): fail CI when a translated plurals has no "other" form
Codeberg #297 and #298 are the same crash, reported twice from fr-FR: Resources$NotFoundException: Plural resource ID #0x7f0f0000 quantity=14 item=other values-fr translated agenda_range_days with only "one" and "many". French "many" matches millions only, so every count from 2 upwards resolves to "other" — which the translation doesn't define. Android falls back to "other" for any quantity form it can't find, but nothing falls back for "other" itself and there is no fall back to the base locale either, so getQuantityString throws. Any French user with a custom agenda range of 2+ days crashed the moment that label composed: the settings summary, the range picker, and the live preview while typing a day count. Nothing caught it. The lint config downgrades MissingQuantity on the grounds that a missing form "falls back to other at runtime" — true of every form except "other", which is the one that was missing here. And check_translations.py treated <plurals> as an opaque key via RESOURCE_TAGS, never looking inside at quantities. So enforce the one invariant Android actually requires: a translated <plurals> must carry an "other" item. Locales may still skip forms their language rarely uses (Arabic "zero", the missing Italian "many" in search_delete_title) because those do fall back. Lint's MissingQuantity can't tell the two cases apart, which is why this lives in the script rather than in a lint severity, and the stale lint comment is corrected to say so. The broken French string itself is owned by Weblate and is fixed there.
This commit is contained in:
+10
-6
@@ -113,12 +113,16 @@ android {
|
||||
lint {
|
||||
// Community translations are expected to be partial — a missing string
|
||||
// falls back to the English base at runtime — so don't fail the build on
|
||||
// it. Likewise a translated <plurals> may not fill every CLDR quantity
|
||||
// form its locale defines (e.g. Arabic needs "zero"); the missing form
|
||||
// falls back to "other" at runtime, so MissingQuantity is informational
|
||||
// too. Stale/extra keys (ExtraTranslation) stay fatal; scripts/
|
||||
// check_translations.py guards the same invariants with clearer,
|
||||
// translator-facing messages.
|
||||
// it. A translated <plurals> may likewise skip a CLDR quantity form its
|
||||
// locale defines (e.g. Arabic "zero"): Android falls back to "other" for
|
||||
// any form it cannot find, so MissingQuantity is informational too.
|
||||
// What a translation must NOT skip is "other" itself — nothing falls back
|
||||
// for that one, not even the base locale, so it throws
|
||||
// Resources$NotFoundException at runtime (Codeberg #297/#298).
|
||||
// MissingQuantity doesn't tell the two cases apart, so that invariant is
|
||||
// enforced by scripts/check_translations.py instead, with a clearer
|
||||
// translator-facing message. Stale/extra keys (ExtraTranslation) stay
|
||||
// fatal.
|
||||
informational += listOf("MissingTranslation", "MissingQuantity")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user