Skip to main content

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

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pluralio-1.1.0.tar.gz (44.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pluralio-1.1.0-py3-none-any.whl (24.2 kB view details)

Uploaded Python 3

File details

Details for the file pluralio-1.1.0.tar.gz.

File metadata

  • Download URL: pluralio-1.1.0.tar.gz
  • Upload date:
  • Size: 44.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pluralio-1.1.0.tar.gz
Algorithm Hash digest
SHA256 5a3230bd3baf2913be4f5f3a99635457bedfb9e68cc659729e7f6211ed1175c9
MD5 ba54c782dd81d689190f11a681b3b720
BLAKE2b-256 a25bc0e24bba8988173c517c0554b0552243b99b91d59aa59f331366fa207aa4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pluralio-1.1.0.tar.gz:

Publisher: release.yml on MathiasPaulenko/pluralio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pluralio-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: pluralio-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 24.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pluralio-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7ec5a00bf98f3bb5c714a8526b2948fe842702b2fc60022e294b310f70d35b4d
MD5 ee8a058e95b5fa457371f30a4d49bc7e
BLAKE2b-256 94500560c7fb6c497b145e0a4e276d28b1d2b3fb86ee8d55e206d5c8512dfc53

See more details on using hashes here.

Provenance

The following attestation bundles were made for pluralio-1.1.0-py3-none-any.whl:

Publisher: release.yml on MathiasPaulenko/pluralio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page