View on GitHub

CrI18n: Crystal Internationalization

Quick Start | Developing With | Parameters & Aliases | Pluralization | Formatters

Plural Labels

There are times when you need to have different labels based on if there is one thing vs multiple things. This follows the mnemonic tags defined by the CLDR, specifically zero, one, two, few, many, and other. The architecture does not limit to only those tags for custom plural rule implementations (see below).

Defining Plural Labels

All plural labels must have the other tag, this is what cr-i18n uses to detect if a label is plural or not, which impacts how label parity is determined. Take the below label files as an example for the rest of this page:

labels/en/us.yml:

food:
  cookie:
    one: cookie
    other: cookies

labels/en/uk.yml:

food:
  cookie:
    one: biscuit
    other: biscuits

Plural labels are picked based on a special count parameter, and plural rules that map specific numbers to their plural mnemonic tag (see below). cr-i18n comes with plural rules for many languages and locales out of the box, and also provides a way to define your own.

Using Plural Labels

# This will register all plural rules
CrI18n::Pluralization.auto_register_rules

label(food.cookie, "en-us", count: 1) # => "cookie"
label(food.cookie, "en-us", count: 42) # => "cookies"
label(food.cookie, "en-uk", count: 1) # => "biscuit"
label(food.cookie, "en-uk", count: 2) # => "biscuits"

Defining a Plural Rule

Plural rules must extend the Cri18n::Pluralization::PluralRule, define a LOCALES constant as a string array containing all languages and locales it supports if using the auto_register_rules method, and implement the abstract apply(count : Float | Int) : String method that returns the desired plural tag:

class MyPluralRule < CrI18n::Pluralization::PluralRule
  # This constant is optional, but will throw an error if `auto_register_rules` is run and it doesn't exist.
  # There's already a plural rule for these locales, so this is only an example
  LOCALES = ["es-MX", "es"]

  def apply(count : Float | Int) : String
    count == 1 ? "one" : "other"
  end
end

# Register the rule for the `label` macro to pick up on it
CrI18n::Pluralization.register_locale("es-MX", MyPluralRule.new)

Technically, only the “other” tag is required with no other enforcement on the tags, and while not supported in any way, you can use it to perform weird hacks:

fruits:
  apple: Apple
  orange: Orange
  banana: Banana
  other: Not a fruit
@[Flags]
enum Fruit
  Apple
  Orange
  Banana
end

class MyFruitRule < CrI18n::Pluralization::PluralRule
  LOCALES = ["fruit_picker"]

  def apply(count : Float | Int) : String
    Fruit.from_value(count.as(Int)).to_s.downcase
  end
end

label("fruits", count: Fruit::Apple.value)

This would be a weird, but effective way to localize your enum names across locales.