Skip to content

MathiasPaulenko/pluralio

Repository files navigation

pluralio

Pluralization and singularization for Python

English · Spanish · Zero dependencies · Type-safe · Extensible

PyPI version Python versions CI Coverage License Code style: ruff Type checker: mypy Downloads


Features

  • Zero dependencies — pure Python standard library, nothing else to install
  • Type-safe — full type hints, py.typed marker included (PEP 561)
  • 100% test coverage — 869 tests, every line is verified
  • Extensible at runtime — add irregulars, rules, uncountables, or entire languages without touching source code
  • Case preservation"Library""Libraries", "BOX""BOXES"
  • Count-awarepluralize("item", count=1)"item"
  • Hyphenated words"mother-in-law""mothers-in-law"
  • Python 3.10+ — tested on 3.10, 3.11, 3.12, and 3.13

Table of contents

Installation

pip install pluralio

Or with uv:

uv add pluralio

Quick start

import pluralio

# ── English (default) ──────────────────────────────────────────
pluralio.pluralize("cat")              # "cats"
pluralio.pluralize("box")              # "boxes"
pluralio.pluralize("child")            # "children"
pluralio.pluralize("city")             # "cities"
pluralio.singularize("cities")         # "city"
pluralio.singularize("mice")           # "mouse"
pluralio.singularize("children")       # "child"

# ── Spanish ────────────────────────────────────────────────────
pluralio.pluralize("gato", lang="es")         # "gatos"
pluralio.pluralize("lápiz", lang="es")        # "lápices"
pluralio.pluralize("examen", lang="es")       # "exámenes"
pluralio.singularize("lápices", lang="es")    # "lápiz"
pluralio.singularize("alemanes", lang="es")   # "alemán"

# ── Count-aware ────────────────────────────────────────────────
pluralio.pluralize("item", count=1)    # "item"  (singular)
pluralio.pluralize("item", count=0)    # "items" (plural)
pluralio.pluralize("item", count=5)    # "items" (plural)

# ── Case preservation ─────────────────────────────────────────
pluralio.pluralize("Library")          # "Libraries"
pluralio.pluralize("LIBRARY")          # "LIBRARIES"
pluralio.singularize("Libraries")      # "Library"

# ── Hyphenated words ──────────────────────────────────────────
pluralio.pluralize("mother-in-law")    # "mothers-in-law"
pluralio.singularize("mothers-in-law") # "mother-in-law"

# ── List supported languages ──────────────────────────────────
pluralio.supported_languages()         # ["en", "es"]

# ── Inspect word form ─────────────────────────────────────────
pluralio.is_plural("cats")             # True
pluralio.is_singular("cat")           # True
pluralio.is_plural("sheep")            # False  (uncountable)
pluralio.is_singular("sheep")          # False  (uncountable)

How it works

Every word goes through a three-step priority chain:

┌─────────────────────────────────────────────────────────────┐
│                    Input word (stripped)                     │
└───────────────────────────┬─────────────────────────────────┘
                            ▼
              ┌─────────────────────────┐
              │  1. Uncountable check   │  ← highest priority
              │     word in set?        │
              └────────────┬────────────┘
                           │ no
                           ▼
              ┌─────────────────────────┐
              │  2. Irregular lookup    │
              │     word in dict?       │
              └────────────┬────────────┘
                           │ no
                           ▼
              ┌─────────────────────────┐
              │  3. Regex rules         │  ← first match wins
              │     (ordered list)      │
              └────────────┬────────────┘
                           │ no match
                           ▼
              ┌─────────────────────────┐
              │  Return word unchanged  │
              └─────────────────────────┘
  • Uncountable words (e.g. "sheep", "information") are returned as-is.
  • Irregular words (e.g. "man""men") are looked up in a dictionary.
  • Regex rules are applied in order — the first matching pattern wins.
  • The casing of the input is always preserved in the output.

API reference

Core functions

Function Description
pluralize(word, lang="en", count=None) Convert a word to its plural form.
singularize(word, lang="en") Convert a word to its singular form.
is_plural(word, lang="en") Check if a word is in plural form.
is_singular(word, lang="en") Check if a word is in singular form.
supported_languages() Return a sorted list of registered language codes.

Extensibility functions

Function Description
add_irregular(singular, plural, lang="en") Add an irregular pair (both directions).
add_plural(singular, plural, lang="en") Add only the singular → plural direction.
add_singular(plural, singular, lang="en") Add only the plural → singular direction.
add_uncountable(word, lang="en") Mark a word as invariable.
add_plural_rule(pattern, replacement, lang="en") Insert a pluralization regex rule (top priority).
add_singular_rule(pattern, replacement, lang="en") Insert a singularization regex rule (top priority).
register_language(lang, *, plural_rules=..., ...) Register a completely new language.

Low-level registry

Function Description
LanguageRules Dataclass holding all rules for a language.
register(rules) Register a LanguageRules instance.
get_rules(lang) Retrieve rules for a language (raises ValueError if not found).

Extending rules

pluralio is designed to be extended at runtime — no need to modify the source code.

Add an irregular word

Registers both pluralize and singularize directions:

pluralio.add_irregular("person", "people")

pluralio.pluralize("person")      # "people"
pluralio.singularize("people")    # "person"

Add only plural or singular direction

When you need just one direction (e.g. Spanish accent restoration):

# Only pluralize direction
pluralio.add_plural("joven", "jóvenes", lang="es")
pluralio.pluralize("joven", lang="es")    # "jóvenes"

# Only singularize direction (accent restoration)
pluralio.add_singular("alemanes", "alemán", lang="es")
pluralio.singularize("alemanes", lang="es")  # "alemán"

Add an uncountable / invariable word

pluralio.add_uncountable("data")

pluralio.pluralize("data")      # "data"
pluralio.singularize("data")    # "data"

Add a regex rule

Custom rules are inserted at the top of the rule list (highest priority — first match wins):

pluralio.add_plural_rule(r"us$", "i")
pluralio.pluralize("cactus")    # "cacti"

pluralio.add_singular_rule(r"i$", "us")
pluralio.singularize("cacti")   # "cactus"

Register a new language

pluralio.register_language(
    "fr",
    plural_rules=[(r"$", "s")],
    singular_rules=[(r"s$", "")],
    irregular_plurals={"cheval": "chevaux"},
    uncountable={"information"},
)

pluralio.pluralize("chat", lang="fr")        # "chats"
pluralio.pluralize("cheval", lang="fr")      # "chevaux"
pluralio.singularize("chevaux", lang="fr")   # "cheval"
pluralio.pluralize("information", lang="fr") # "information"

Comparison

How does pluralio compare to other Python inflection libraries?

Feature pluralio inflect Inflector pluralizer
Spanish support
Singularize
Count-aware (count=1)
Case preservation
Hyphenated words
Runtime extensibility
Add custom languages
Zero dependencies
Type hints (py.typed)
Python 3.10+
Test coverage 100% ~95% ~80% ~70%

Supported languages

Language Code Regex rules Irregulars Uncountables Status
English en 6 131 76 ✅ Complete
Spanish es 9 61 + 34 extra singles 62 ✅ Complete
Portuguese pt 🔜 Planned
French fr 🔜 Planned
Italian it 🔜 Planned

Roadmap

Version Goal Status
1.0.0 English + Spanish, core engine, extensibility API ✅ Released
1.1.0 Portuguese (pt) 🔜 Planned
1.2.0 French (fr) 🔜 Planned
1.3.0 Italian (it) 🔜 Planned

Contributing

Contributions are welcome! Whether it's a bug report, a new language, or a feature suggestion — please read our guides first:

Quick start for contributors

git clone https://github.com/MathiasPaulenko/pluralio.git
cd pluralio
pip install -e ".[dev]"

# Run checks
ruff check pluralio/ tests/
mypy pluralio/
pytest

Security

If you discover a security vulnerability, please see our Security Policy for responsible disclosure instructions. Do not open a public issue.

License

MIT — Copyright (c) 2025 Mathias Paulenko

About

Pluralization and singularization for Python — ES/EN, zero deps, type-safe

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages