Package limn.i18n

Class PluralRules

java.lang.Object
limn.i18n.PluralRules

public final class PluralRules extends Object
Which grammatical form a language uses for a count: "1 item", "2 items", and the four other answers some languages give.

Why this exists at all. A sentence with a number in it cannot be translated as one string. Russian needs three forms for integers and picks between them by the last two digits — 21 takes the same form as 1, 22 the same as 2, and 11 neither — and Arabic needs six. Nothing in the JDK answers this: MessageFormat and ChoiceFormat select by numeric range, which cannot express "ends in 1 but not 11", so a catalog cannot carry the rule on its own however it is written. ICU has the data and is not an option here: this toolkit has no runtime dependencies and gains none for one sentence.

So the rules are transcribed by hand — and the transcription is checked against CLDR itself. scripts/i18n/dump-cldr-plurals.mjs reads Node's Intl.PluralRules, which is the CLDR data as ICU carries it, and writes every answer for every integer from 0 to 1000 plus spot checks past a hundred million; PluralRulesTest.theTranscriptionAgreesWithCldr holds this class to that file, which is checked in with the ICU version that produced it. Nothing in the build runs Node. Regenerate the dump when a language is added, which is when a hand-written rule is most likely to be wrong. Transcribed, the rules are still CLDR's data, under the Unicode License v3, whose text the toolkit's jar carries at limn/i18n/internal/CLDR-LICENSE.txt.

The check earned its keep on the day it was written (2026-09-18, ICU 78.3): the first transcription was wrong in five languages at once. Spanish, Italian, French, Portuguese and Brazilian Portuguese have a many form for an exact multiple of a million — "un millón de elementos" — which no rule written from the first few integers would ever suggest.

A language this class does not name falls back to English's PluralRules.Rule.ONE_OTHER, the shape of the largest family, and that fallback stays a guess by construction: an application shipping a language from another family declares its own rule by carrying only the forms it needs (see integerCategories(java.util.Locale)).

Integers only, deliberately. Every count this toolkit speaks is a number of things — rows, items, results — so select(java.util.Locale, long) takes a long. CLDR's fraction categories (Czech's many, which is "2.5 položky") cannot arise, and integerCategories(java.util.Locale) leaves them out rather than making a translator fill a form the language never reaches here.

See Also:
  • Method Details

    • select

      public static PluralRules.Category select(Locale locale, long count)
      The form count takes in locale.
      Parameters:
      locale - the language to ask; only its language subtag is read, as ICU reads the code, so fra is French and heb Hebrew
      count - how many, as a whole number; negative counts are read by their magnitude, because a language inflects for the quantity and not for the sign
      Returns:
      the category, never null
    • integerCategories

      public static Set<PluralRules.Category> integerCategories(Locale locale)
      Every form locale can reach with a whole number, which is exactly the set of keys a catalog for that language owes a PluralString.

      Read by ShippedTranslationsTest, which is why it is public: a catalog with a form its language never reaches is as wrong as one missing a form it does — the first invites a translator to invent a distinction, the second drops a number into the wrong sentence.

      Parameters:
      locale - the language to ask
      Returns:
      its categories, in declaration order, never empty