Skip to content

[gen_l10n] Support for explicit 0 and 1 rules for number formatting #84291

Closed

Description

@aleksakrstic

Use case

Currently ARB files support only following plural forms: =0, =1, =2, ..., few, many, other.
And gen_l10n generator ignores zero, one & two plural forms.

However, according to ICU standard here and here these numeric representation of plural forms (=0, =1, =2, ...) represent cases when the number is exactly that.

In order to properly cover plural forms among different languages according to ICU standard, ARB files should support zero, one & two plural forms as well.
These zero, one, two forms are meant to be applied in more cases than just those exact numbers.
Check here.

Proposal

Add support for plural forms like this one:
"pluralString": "{howMany, plural, zero{...} one{...} two{...} few{...} many{...} other {...}}"

which should generate the following Dart code:

String pluralString(int howMany) {
    return intl.Intl.pluralLogic(
      howMany,
      locale: localeName,
      zero: '...',
      one: '...',
      two: '...',
      few: '...',
      many: '...',
      other: '...',
    );
  }

Activity

  1. tiloc commented on Jun 10, 2021

    @tiloc

    I recently ran into the same issue, would absolutely second that request. This would also make gen_l10n compatible with third-party translation tools, such as poeditor.com.

  2. added
    in triagePresently being triaged by the triage team
    a: internationalizationSupporting other languages or locales. (aka i18n)
    frameworkflutter/packages/flutter repository. See also f: labels.
    c: proposalA detailed proposal for a change to Flutter
    c: new featureNothing broken; request for a new capability
    and removed
    in triagePresently being triaged by the triage team
    on Jun 10, 2021
  3. HansMuller commented on Jun 10, 2021

    @HansMuller
    Contributor

    CC @shihaohong - sounds like a reasonable request

  4. shihaohong commented on Jun 16, 2021

    @shihaohong
    Contributor

    I'm a little confused here -- wouldn't it be fine if =0 and 'zero' treated the same way? In the tool, we basically just take =0 and then use it in zero. We use =1 for 'one' and =2 for 'two' as well. See https://github.com/flutter/flutter/blob/master/packages/flutter_tools/lib/src/localizations/gen_l10n.dart#L189.

    I say this because I think the numerical cases would still be handled correctly by the 'other' value. I might be missing something though, so some clarification would be helpful!

  5. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Jun 16, 2021
  6. tiloc commented on Jun 16, 2021

    @tiloc

    @shihaohong for me it is mostly about the tooling. Several tools I have come across only know the "zero", "one", "two" which seem to be more commonly used than the "=0", "=1", "=2" which Flutter has chosen to use. It is annoying to do search & replace on the ARB file from a translation tool before feeding it into gen_l10n.
    Also as the original poster pointed out, "=1" and "one" are semantically not the same. "=1" means "exactly one", where for instance "one" in Ukrainian means "any number ending with the digit 1". So the mapping in gen_l10n is mapping two semantically different concepts onto each other. The same with "two", which in Arabic means "any number beginning with the digit 2".

  7. shihaohong commented on Jun 17, 2021

    @shihaohong
    Contributor

    Interesting -- thanks for the additional information. I'll see how we can make this change. The only issue is that it would likely be a breaking change, so we'll need to add an optional parameter to provide the fix and then phase it out.

  8. shihaohong commented on Jun 21, 2021

    @shihaohong
    Contributor

    Several tools I have come across only know the "zero", "one", "two" which seem to be more commonly used than the "=0", "=1", "=2" which Flutter has chosen to use. It is annoying to do search & replace on the ARB file from a translation tool before feeding it into gen_l10n.

    I took another look at this and what we could do is basically have the tool accept either the numeric representation (=1, =2) or the full English word ('one', 'two') and treat them the same.

    Also as the original poster pointed out, "=1" and "one" are semantically not the same. "=1" means "exactly one", where for instance "one" in Ukrainian means "any number ending with the digit 1". So the mapping in gen_l10n is mapping two semantically different concepts onto each other. The same with "two", which in Arabic means "any number beginning with the digit 2".

    Could you share some documentation that describes this difference? I don't seem to find a description of this in the ICU standard. Even so, this would be an intl package issue since we use that under the hood and we would not be able to do much unless the intl package itself supported this behavior.

  9. changed the title [-]Add support for missing "zero", "one" & "two" plural forms to gen_l10n generator[/-] [+][gen_l10n] Support for missing "zero", "one" & "two" as well as =0, =1, =2[/+] on Jun 21, 2021
  10. tiloc commented on Jun 21, 2021

    @tiloc

    Here is the documentation, it is the "explicit 0 and 1 rules": https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#a-nameexplicit01rules-hrefexplicit01rulesexplicit-0-and-1-rulesa

    The override is mentioned here: The explicit “0” and “1” cases have precedence over the “zero” and “one” cases. For example, if for a particular element CLDR data includes values for both the “1” and “one” cases, then the “1” value is used for numeric values of exactly 1, while the “one” value is used for any other formatted numeric values matching the “one” plural rule for the language.
    "=2" doesn't really seem to be a thing.

  11. shihaohong commented on Jun 21, 2021

    @shihaohong
    Contributor

    I filed an issue to dart-lang/intl: dart-lang/i18n#21.

  12. aDrIaNo34500 commented on Jun 21, 2021

    @aDrIaNo34500
  13. aDrIaNo34500 commented on Jun 21, 2021

    @aDrIaNo34500
  14. changed the title [-][gen_l10n] Support for missing "zero", "one" & "two" as well as =0, =1, =2[/-] [+][gen_l10n] Support for explicit 0 and 1 rules for number formatting[/+] on Jun 21, 2021
  15. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Jul 14, 2021
  16. aleksakrstic commented on Feb 24, 2022

    @aleksakrstic
    Author

    Hi @shihaohong
    Is there any update on this? It seems many devs face such issue.

    Maybe just to start accepting ARB files with zero & =0, one & =1, two & =2 and treat them the same way, instead of accepting only =0, =1 & =2?
    At least for starters?
    It seems that would solve all the issues around this, without any noticable cons.

  17. added
    P3Issues that are less important to the Flutter project
    on Apr 20, 2022
  18. lonedfx commented on Jan 31, 2026

    @lonedfx

    This should be prioritized higher than P3, as it affects ANY nontrivial localized application.

    Essentially, this issue is making the =0 category entirely useless unless one doesn't care about cross-language consistency. E.g, if I want my English string to be "No items found" for the zero case, all I can get for the French case is "0 element trouvé".

    Example:
    ARB file: =0{No items} =1{1 item} other{{count} items}

    Expected behavior:

    • English count=0: "No items" ✓
    • French count=0: "Aucun élément" ✓

    Actual behavior:

    • English count=0: "No items" ✓
    • French count=0: "0 élément" ✗ (=0 case is ignored)

    One might argue that it sorta "works" ("0 element" is accurate) but:

    1. it is grammatically awkward is many cases
    2. it is misleading since an existing zero case in the ICU for a French-like languages looks like the case is properly handled but isn't (it is effectively dead code)
    3. it makes it impossible to write a "No items" case in French ("Aucun element") and similar languages.
    4. It is is inconsistent with other languages, which will display "No items" while French will show "0 item"
    5. it forces the use of two strings in order to fix this inconsistency (a dedicated zero-case string plus an ICU string for other non-zero cases) and a wrapper function that selects the proper string on a case by case basis -- this goes completely counter to the purpose of using integrated ICU syntax
    6. it can very easily cause subtle bugs when LLMs are used for translation (which is increasingly common). LLMs will almost universally write =1{1 element} rather than =1{{count} element} because the pattern looks more natural. This causes 0 items to display as "1 element" in French, a critical bug that will remain undetected until specifically QA'd for French with a zero-count case. As AI-assisted translation becomes standard practice, this will affect more and more apps.

    Also, I'm speaking about French here because that's what I'm knowledgeable about, but surely this exact same issue surfaces in other languages with even less trivial grammatical vs semantic mappings.

    The person making the translation knows the language best and it should be clear which case they are covering. The =0 case should be matched first before CLDR categories are consulted. ICU specs do recognize this:

    "When formatting, the input number is first matched against the explicitValue clauses. If there is no exact-number match, then a keyword is selected by calling the PluralRules..."

    I understand this is going to be a breaking change, but leaving this bug in makes it is a bit of a nightmare to maintain proper localization.

    Surely there is a way to keep the old behavior while a) fixing the behavior for new projects going forward and b) letting legacy projects opt into the new behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projecta: internationalizationSupporting other languages or locales. (aka i18n)c: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Flutterframeworkflutter/packages/flutter repository. See also f: labels.team-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions