Plurality

Fast, zero-regex English inflection for Elixir. Pluralize, singularize, and detect noun forms with compile-time data and last-byte dispatch.

Installation

Add plurality to your list of dependencies in mix.exs:

def deps do
  [
    {:plurality, "~> 0.2.0"}
  ]
end

Usage

Plurality.pluralize("leaf")                    # => "leaves"
Plurality.singularize("leaves")                # => "leaf"
Plurality.plural?("leaves")                    # => true
Plurality.singular?("leaf")                    # => true
Plurality.inflect("leaf", 2)                   # => "leaves"
Plurality.inflect("leaf", 1)                   # => "leaf"

Safe pluralization

Plurality.pluralize("children", check: true)   # => "children" (not "childrens")

Case preservation

Plurality.pluralize("LEAF")                    # => "LEAVES"
Plurality.pluralize("Leaf")                    # => "Leaves"
Plurality.singularize("WOMEN")                 # => "WOMAN"

Compound nouns

Plurality.pluralize("status code")             # => "status codes"
Plurality.pluralize("field mouse")             # => "field mice"
Plurality.singularize("ice creams")            # => "ice cream"

Classical mode

Pass classical: true to get Latin/Greek plural forms instead of modern English:

Plurality.pluralize("aquarium")                  # => "aquariums"  (modern default)
Plurality.pluralize("aquarium", classical: true)  # => "aquaria"    (classical)
Plurality.pluralize("formula", classical: true)   # => "formulae"
Plurality.pluralize("trauma", classical: true)    # => "traumata"

Singularization handles both forms automatically, no flag needed:

Plurality.singularize("aquariums")  # => "aquarium"
Plurality.singularize("aquaria")    # => "aquarium"

See the Classical Mode guide for full details on which forms are affected, app-wide config, and how default decisions were made.

Domain customization

Override built-in data with your own irregulars and uncountables:

defmodule MyApp.Inflection do
  use Plurality.Custom,
    irregulars: [{"regex", "regexen"}],
    uncountables: ["kubernetes"]
end

MyApp.Inflection.pluralize("regex")       # => "regexen"
MyApp.Inflection.pluralize("kubernetes")  # => "kubernetes"
MyApp.Inflection.pluralize("leaf")        # => "leaves"  (falls through)

Or delegate globally so all Plurality.* calls use your overrides:

config :plurality, custom_module: MyApp.Inflection

See the Customization guide for full documentation.

Ash integration

Optional changes, validations, and calculations for Ash applications. Compiles away to nothing if Ash isn't loaded.

change {Plurality.Ash.Changes.Pluralize, attribute: :table_name, from: :name}
validate {Plurality.Ash.Validations.PluralForm, attribute: :table_name}
calculate :name_plural, :string, {Plurality.Ash.Calculations.Pluralize, attribute: :name}

See the Ash Integration guide for full documentation.

Why Plurality?

Accuracy

Handles tricky business and technical English that other libraries miss:

Word Plurality
merchandise merchandise (uncountable)
schema schemas (modern English)
appendix appendices
chassis chassis (uncountable)
taxes tax (singularize)

Performance

Zero regex at runtime. Suffix rules use last-byte dispatch via BEAM select_val jump tables.

Approach ips vs Regex
Regex (typical) 3.6K 1x
Last-byte dispatch173K48x

Architecture

Three-tier resolution (Conway 1998, same as Rails and pluralize.js):

1. Uncountables (MapSet)  =>  word unchanged     (sheep, software, news)
2. Irregulars (Map)       =>  direct lookup       (child => children)
3. Suffix rules           =>  last-byte dispatch  (category => categories)

All data compiled into module attributes at build time. Zero runtime file I/O.

See the Methodology guide for detailed documentation of data sources, modern vs. classical decisions, corpus compliance, and suffix rule design.

Test suite

44 doctests, 2,325 tests, 0 failures

Validates 80,191 noun pairs from two independent corpora in both directions:

API

Function Description
pluralize/2 Plural form, options: check:, classical:
singularize/1 Singular form
plural?/1 Check if plural
singular?/1 Check if singular
inflect/3 Count-based inflection with options

Guides

Acknowledgements

License

MIT -- see LICENSE file.