# Plurality
Fast, zero-regex English noun inflection for Elixir. Pluralize, singularize, and detect noun forms with compile-time data and last-byte dispatch. Operates on nouns only; other parts of speech are not supported.
## Installation
Add `plurality` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:plurality, "~> 0.2.1"}
]
end
```
## Usage
```elixir
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
```elixir
Plurality.pluralize("children", check: true) # => "children" (not "childrens")
```
### Case preservation
```elixir
Plurality.pluralize("LEAF") # => "LEAVES"
Plurality.pluralize("Leaf") # => "Leaves"
Plurality.singularize("WOMEN") # => "WOMAN"
```
### Compound nouns
```elixir
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:
```elixir
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:
```elixir
Plurality.singularize("aquariums") # => "aquarium"
Plurality.singularize("aquaria") # => "aquarium"
```
See the [Classical Mode guide](guides/classical-mode.md) 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:
```elixir
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:
```elixir
config :plurality, custom_module: MyApp.Inflection
```
See the [Customization guide](guides/customization.md) for full documentation.
### Ash integration
Optional changes, validations, and calculations for Ash applications.
Compiles away to nothing if Ash isn't loaded.
```elixir
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](guides/ash-integration.md) for full documentation.
## Why Plurality?
Every inflection library gets the easy words right. The difference shows on the
long tail — medical terms, Latin borrowings, irregular compounds, and
uncountable nouns that hand-rolled inflection or simpler libraries silently
mangle.
Plurality exists to close that gap. It is verified against two independent
linguistic corpora — **80,191 noun pairs** tested in both directions — so
words like "fulfilment", "chassis", "criteria", "schema", and "merchandise"
just work. No fallback hacks, no silent failures.
- **100% accuracy** on AGID (32,625 pairs) and NIH SPECIALIST Lexicon (47,566 pairs)
- **~6M ops/sec** single word, ~4.3M words/sec batch — zero regex, all compile-time data
- **Two modes** — modern English by default, `classical: true` for Latin/Greek forms
- **2,325 tests** covering corpus validation, classical mode, business domain, compounds, and edge cases
See [Methodology](guides/methodology.md) for data sources, design decisions,
and corpus compliance. See [Performance](guides/performance.md) for benchmarks.
## 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
- [Classical Mode](guides/classical-mode.md) -- Latin/Greek plural forms
- [Customization](guides/customization.md) -- domain-specific overrides
- [Ash Integration](guides/ash-integration.md) -- changes, validations, calculations
- [Methodology](guides/methodology.md) -- data sources, design decisions, corpus compliance
## Acknowledgements
- [Exflect](https://hex.pm/packages/exflect) by Tyler Wray
- [Inflex](https://hex.pm/packages/inflex) by Miguel Palhas
- [pluralize](https://github.com/plurals/pluralize) by Blake Embrey
- [Rails ActiveSupport::Inflector](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html)
- Damian Conway's 1998 paper [*An Algorithmic Approach to English Pluralization*](https://users.monash.edu/~damian/papers/extabs/Plurals.html)
## License
MIT -- see `LICENSE` file.