TypeScript-first pluralization and singularization engine — drop-in replacement for pluralize.
| pluralize | @agentine/inflekt | |
|---|---|---|
| TypeScript types included | ❌ | ✅ |
| ESM support | ❌ | ✅ |
| CJS support | ✅ | ✅ |
| Zero dependencies | ✅ | ✅ |
| Actively maintained | ❌ (last release 2019) | ✅ |
"cookie" → "cookies" |
❌ ("cooky") |
✅ |
"deceased" → "deceased" |
❌ ("deceaseds") |
✅ |
| Uncountable: feedback, species, hertz | ❌ | ✅ |
| Compound/hyphenated words | ❌ | ✅ |
npm install @agentine/inflektRequires Node.js >=20.
import { plural, singular, inflect, isPlural, isSingular } from '@agentine/inflekt';
// Pluralize
plural('person'); // 'people'
plural('cookie'); // 'cookies'
plural('cactus'); // 'cacti'
plural('analysis'); // 'analyses'
plural('matrix'); // 'matrices'
// Singularize
singular('people'); // 'person'
singular('cookies'); // 'cookie'
singular('analyses'); // 'analysis'
singular('matrices'); // 'matrix'
singular('cacti'); // 'cactus'
// Count-based inflection
inflect('cat', 1); // 'cat'
inflect('cat', 3); // 'cats'
inflect('cat', 3, true); // '3 cats'
inflect('cat', 0, true); // '0 cats'
// Detection
isPlural('cats'); // true
isPlural('sheep'); // true (uncountable)
isSingular('cat'); // true
isSingular('fish'); // true (uncountable)inflekt preserves the original casing pattern of the input word.
plural('cat'); // 'cats'
plural('Cat'); // 'Cats'
plural('CAT'); // 'CATS'
plural('Person'); // 'People'
plural('PERSON'); // 'PEOPLE'Hyphenated words are supported — the last component is inflected.
plural('mother-in-law'); // 'mother-in-laws'
plural('test-case'); // 'test-cases'Returns the plural form of word. Preserves original casing. Returns uncountable words unchanged.
plural('leaf'); // 'leaves'
plural('datum'); // 'data'
plural('feedback'); // 'feedback' (uncountable)Returns the singular form of word. Preserves original casing. Returns uncountable words unchanged.
singular('leaves'); // 'leaf'
singular('data'); // 'datum'
singular('sheep'); // 'sheep' (uncountable)Returns the singular form when count === 1, otherwise the plural form. If inclusive is true, the count is prepended with a space.
inflect('cat', 1); // 'cat'
inflect('cat', 2); // 'cats'
inflect('cat', 1, true); // '1 cat'
inflect('cat', 5, true); // '5 cats'Returns true if the word appears to be in plural form, or is uncountable.
isPlural('cats'); // true
isPlural('cat'); // false
isPlural('sheep'); // true (uncountable)Returns true if the word appears to be in singular form, or is uncountable.
isSingular('cat'); // true
isSingular('cats'); // false
isSingular('fish'); // true (uncountable)Registers a custom pluralization rule. Rules added later take precedence over earlier ones. replacement follows the same capture-group syntax as String.prototype.replace.
addPluralRule(/gex$/i, 'gices');
plural('regex'); // 'regices'Registers a custom singularization rule. Rules added later take precedence.
addSingularRule(/gices$/i, 'gex');
singular('regices'); // 'regex'Registers a custom irregular word pair.
addIrregularRule('pokemon', 'pokemon'); // treat as uncountable-like irregular
addIrregularRule('octopus', 'octopi');Registers a word or pattern as uncountable — plural and singular return it unchanged.
addUncountableRule('pokemon');
plural('pokemon'); // 'pokemon'
singular('pokemon'); // 'pokemon'
addUncountableRule(/craft$/i);
plural('hovercraft'); // 'hovercraft'
plural('aircraft'); // 'aircraft'Rules are applied in priority order (higher-priority rules override lower ones). The engine checks, in sequence:
- Empty/whitespace — returned as-is.
- Compound words — hyphenated inputs split on
-; the last segment is inflected recursively. - Uncountables — words in the uncountable set or matching an uncountable regex are returned unchanged.
- Irregulars — exact matches in the irregular map (e.g.
person→people) are returned with case preserved. - Regex rules — applied in reverse registration order (last registered = highest priority).
| Pattern | Replacement | Example |
|---|---|---|
$ |
s |
cat → cats |
s$ |
s |
bus → bus (no-op) |
(bu|mis|gas)s$ |
$1ses |
bus → buses |
([ti])um$ |
$1a |
datum → data |
([ti])a$ |
$1a |
data → data (no-op) |
sis$ |
ses |
analysis → analyses |
(?:([^f])fe|...)f$ |
$1$2ves |
knife → knives |
(hive)s?$ |
$1s |
hive → hives |
([^aeiouy]|qu)y$ |
$1ies |
baby → babies |
(x|ch|ss|sh)$ |
$1es |
box → boxes |
([ml])ouse$ |
$1ice |
mouse → mice |
([ml])ice$ |
$1ice |
mice → mice (no-op) |
^(ox)$ |
$1en |
ox → oxen |
(quiz)$ |
$1zes |
quiz → quizzes |
(database)s?$ |
$1s |
database → databases |
([^aeiou])ies$ |
$1ies |
babies → babies (no-op) |
(ch|sh|x|ss)es$ |
$1es |
boxes → boxes (no-op) |
(alias|status|...)$ |
$1es |
status → statuses |
(buffal|tomat|...)o$ |
$1oes |
tomato → tomatoes |
(matr|append)ix$|(vert|ind)ex$ |
$1$2ices |
matrix → matrices |
(octop|vir|...)us$ |
$1i |
cactus → cacti |
(octop|vir|...)i$ |
$1i |
cacti → cacti (no-op) |
(ax|test)is$ |
$1es |
axis → axes |
| Pattern | Replacement | Example |
|---|---|---|
s$ |
`` | cats → cat |
ss$ |
ss |
kiss → kiss (no-op) |
(n)ews$ |
$1ews |
news → news (uncountable, no-op) |
([ti])a$ |
$1um |
data → datum |
([^f])ves$ |
$1fe |
knives → knife |
(hive)s$ |
$1 |
hives → hive |
(tive)s$ |
$1 |
natives → native |
([^aeiouy]|qu)ies$ |
$1y |
babies → baby |
ies$ |
y |
series (handled by irregulars) |
(s)eries$ |
$1eries |
series → series (no-op) |
(m)ovies$ |
$1ovie |
movies → movie |
(x|ch|ss|sh)es$ |
$1 |
boxes → box |
([ml])ice$ |
$1ouse |
mice → mouse |
(bus)es$ |
$1 |
buses → bus |
(o)es$ |
$1 |
tomatoes → tomato |
(shoe)s$ |
$1 |
shoes → shoe |
(octop|vir|...)i$ |
$1us |
cacti → cactus |
(alias|status|...)es$ |
$1 |
statuses → status |
^(ox)en |
$1 |
oxen → ox |
(quiz)zes$ |
$1 |
quizzes → quiz |
([lr])ves$ |
$1f |
halves → half |
(cris|ax|test)es$ |
$1is |
axes → axis |
((a)naly|...)ses$ |
$1sis |
analyses → analysis |
(matr|suff|append)ices$ |
$1ix |
matrices → matrix |
(vert|ind)ices$ |
$1ex |
vertices → vertex |
(database)s$ |
$1 |
databases → database |
These word pairs bypass the regex rules entirely. Both directions are registered (singular ↔ plural).
| Singular | Plural |
|---|---|
| person | people |
| man | men |
| woman | women |
| child | children |
| tooth | teeth |
| foot | feet |
| goose | geese |
| ox | oxen |
| mouse | mice |
| quiz | quizzes |
| cookie | cookies |
| movie | movies |
| rookie | rookies |
| smoothie | smoothies |
| hero | heroes |
| potato | potatoes |
| tomato | tomatoes |
| volcano | volcanoes |
| tornado | tornadoes |
| torpedo | torpedoes |
| domino | dominoes |
| mosquito | mosquitoes |
| echo | echoes |
| veto | vetoes |
| dingo | dingoes |
| leaf | leaves |
| life | lives |
| genus | genera |
| opus | opera |
| oasis | oases |
| cactus (via rule) | cacti |
| I | we |
| me | us |
| he / she | they |
| is | are |
| was | were |
| has | have |
| this | these |
| that | those |
And many more — see src/irregulars.ts for the complete list.
These words have the same singular and plural form:
adulthood, advice, agenda, aircraft, alcohol, ammo, analytics, anime, athletics, bison, blood, bream, buffalo, butter, carp, cash, chassis, chess, clothing, cod, commerce, cooperation, corps, debris, deceased, deer, diabetes, digestion, elk, electricity, emoji, equipment, evidence, evolution, faith, feedback, firmware, fish, flora, flounder, fun, furniture, gallows, garbage, gold, golf, graffiti, grass, grouse, hardware, headquarters, health, herpes, hertz, highs, homework, honesty, ice, information, jeans, justice, kudos, labour/labor, legislation, leisure, linguistics, livestock, lows, luggage, machinery, mackerel, mail, mathematics, media, metadata, money, moose, mud, music, news, nutrition, offspring, plankton, pliers, police, pollution, premises, rain, racism, research, rice, salmon, scissors, series, sewage, shambles, sheep, shrimp, software, spam, species, staff, swine, tennis, thanks, traffic, transportation, trousers, trout, tuna, vermicelli, weather, wheat, whitebait, wholesale, wildlife, willpower, you
inflekt deliberately fixes all known incorrect outputs from pluralize:
| Word | pluralize | inflekt |
|---|---|---|
"cookie" |
"cooky" |
"cookies" |
"deceased" |
"deceaseds" |
"deceased" |
"feedback" |
"feedbacks" |
"feedback" |
"species" |
"speciess" |
"species" |
"hertz" |
"hertzs" |
"hertz" |
"scissors" |
"scissorss" |
"scissors" |
"series" |
"seriess" |
"series" |
"emoji" |
"emojis" |
"emoji" |
"agenda" |
varies | "agenda" |
"media" |
"medias" |
"media" |
Additional edge cases handled:
- Empty string — returned as-is:
plural("") === "" - Whitespace-only string — returned as-is:
plural(" ") === " " - Already-plural input to
plural— returns unchanged:plural("cats") === "cats" - Already-singular input to
singular— returns unchanged:singular("cat") === "cat" - Uncountable words —
isPluralandisSingularboth returntrue
@agentine/inflekt is a drop-in replacement. Update your import:
- const pluralize = require('pluralize');
+ import { plural, singular, inflect, isPlural, isSingular, addPluralRule, addSingularRule, addIrregularRule, addUncountableRule } from '@agentine/inflekt';Function mapping:
| pluralize | inflekt |
|---|---|
pluralize(word) |
plural(word) |
pluralize.singular(word) |
singular(word) |
pluralize(word, count) |
inflect(word, count) |
pluralize(word, count, true) |
inflect(word, count, true) |
pluralize.isPlural(word) |
isPlural(word) |
pluralize.isSingular(word) |
isSingular(word) |
pluralize.addPluralRule(r, s) |
addPluralRule(r, s) |
pluralize.addSingularRule(r, s) |
addSingularRule(r, s) |
pluralize.addIrregularRule(s, p) |
addIrregularRule(s, p) |
pluralize.addUncountableRule(w) |
addUncountableRule(w) |
- inflekt ships with TypeScript types — no separate
@types/pluralizepackage needed. - inflekt exports named functions rather than a default object. Update any usage of
pluralize(word)toplural(word). - inflekt produces correct output for
cookie,deceased,feedback,species,hertz, and all other words that pluralize gets wrong.
MIT