npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rom98m/pluralize

v1.0.0

Published

Pluralization framework

Readme

Pluralization framework

This framework was designed to align the way of pluralization in Web apps. It's powered by Intl.PluralRules; check the documentation.
The main principle is:

  1. GIVEN a Term, a Number and the Locale.
  2. DEFINE the PluralTag by the Locale and Number.
  3. IF the Term appears in ExceptionsDictionary ⇒ use it.
  4. OTHERWISE apply Rule(Term).

It was created to straighten the process.

Installation

npm install --save @rom98m/pluralize

Usage

Preparation:

import { Plural } from "@rom98m/pluralize"
const en = new Plural("en")

Add default pluralization rule for English cardinal terms:

en.registerRule((cat, term) => {
  if (cat === "one") return term
  if (/(s|x|z|sh|ch)$/.test(term)) return term + "es"
  if (/y$/.test(term)) return term.replace(/y$/, "i") + "es"
  return term + "s"
})

Add some exception terms:

en
  .registerException("child", { other: "children" })
  .registerException("person", { other: "people" })

🚀 Ready to pluralize:

en.pluralize(1, "box")       // "box"
en.pluralize(5, "box")       // "boxes"
en.pluralize(5, "city")      // "cities"
en.pluralize(5, "item")      // "items"
en.pluralize(1, "person")    // "person"
en.pluralize(5, "person")    // "people"

API

new Plural()

The constructor uses same params as Intl.PluralRules.

/**
 * @param {string} locale
 * @param {object} [options={}]
 * @param {"cardinal" | "ordinal"} [options.type="cardinal"]
 */
constructor(
  locale: string,
  { type = "cardinal" }: { type?: PluralRulesType } = {}
)

Instance props

Following props mimic semantically appropriate ones of the Intl.PluralRules options:

public readonly locale: string
public readonly type: PluralRulesType

// "zero" | "one" | "two" | "few" | "many" | "other"
public readonly categories: Intl.LDMLPluralRule[]

.registerRule()

Define the language-default way of pluralizing words.
Mind that "way of pluralizing" is a function which might have intricated logic.

/**
 * @param rule The rule to pluralize regular words.
 *             E.g., pure man's English rule:
 *             `(cat, word) => cat === "one" ? word : (word + "s")`
 * @returns {Plural} Instance of the object (therefore, chainable).
 */
registerRule(rule: (pluralCategory: Intl.LDMLPluralRule, term: string) => string): Plural

.registerException()

Some termas don't go along well with pluralization rules, e.g., "person""people".
Those words should be registred separately.

/**
 * @param {string} term The term that uses non-standard pluralization.
 *                      E.g., "person" – "people".
 * @param {PluralForms} pluralForms Plural forms of the term.
 *                                  The keys of the object are the {@link categories}.
 *                                  E.g., `{ one: "person", other: "people" }`
 * @returns {Plural} Instance of the object (therefore, chainable).
*/
registerException(term: string, pluralForms: PluralForms): Plural

⚠️ Mind that all locale-specific plural catagories should be covered; the "one" might be omitted; the term as-given is used in that case:

en.registerException("person", { /* one: "person", */ other: "people" }) // ✅

// Following will warn as there's no "few" category
// for English/cardinal:
en.registerException("child", { few: "children" }) // ⚠️

.pluralize()

Pluralizes given term as per given number.

/**
 * @param {number} number
 * @param {string} term
 * @returns {string} Pluralized term.
 */
pluralize(number: number, term: string): string

⚠️ The method warns if rule was not defined beforehand or if exception category is missing. The term as-given is returned in such cases.

Design rationalization: missing rules/exceptions should occur pretty rare; better to return something (even wrong) rather than fail/throw in this case.

Etc

All methods are chainable:

const en = new Plural(en)
  .registerRule(...)
  .registerException(...)
  .registerException(...)

The framework works well in browsers and in NodeJS (as per Intp.PluralRules support).

Caveats

  1. Yes, it requires that amount of preparation.
    Luckily, there's not too many exception terms used in the app; it makes sense to register only those which are used.
  2. When cardinal/ordinal pluralization is needed, 2 separate instances should be created (as the rules are different):
    const enCardinal = new Plural("en")
    const enOrdinal = new Plural("en", { type: "ordinal" })
  3. Obviously, each language/locale should instantiate its own new Plural("…").
  4. …and should define its own rule and register exceptions 🤷‍♂️

Credits

Roman Melnyk, https://melnyk.site
Use at own risk.