{"_id":"@agentine/inflekt","name":"@agentine/inflekt","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@agentine/inflekt","version":"0.1.0","description":"TypeScript-first pluralization and singularization engine — drop-in replacement for pluralize","repository":{"type":"git","url":"git+https://github.com/agentine/inflekt.git"},"license":"MIT","type":"module","engines":{"node":">=20"},"exports":{".":{"import":"./dist/esm/index.js","require":"./dist/cjs/index.js","types":"./dist/esm/index.d.ts"}},"main":"./dist/cjs/index.js","module":"./dist/esm/index.js","types":"./dist/esm/index.d.ts","scripts":{"build":"npm run build:esm && npm run build:cjs","build:esm":"tsc -p tsconfig.json","build:cjs":"tsc -p tsconfig.cjs.json","clean":"rm -rf dist","test":"vitest run"},"devDependencies":{"@types/node":"^25.5.0","typescript":"^5.4.0","vitest":"^4.1.0"},"_id":"@agentine/inflekt@0.1.0","gitHead":"a514146ad2da55d8c55c9207743e28c372664c04","bugs":{"url":"https://github.com/agentine/inflekt/issues"},"homepage":"https://github.com/agentine/inflekt#readme","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-TLSj82HR0qFrcD2y+iStYBAL0euEb8Cr+iQuAIGnRMHnZTN3fm7+E3zz9ZeGcLAQ5WE1VfUt/863rrFfob+S4w==","shasum":"a314a77ee6ef21719b2d1125b5a7edd6ac64573a","tarball":"https://registry.npmjs.org/@agentine/inflekt/-/inflekt-0.1.0.tgz","fileCount":51,"unpackedSize":84710,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@agentine%2finflekt@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCtjW+tgcBFoXRXBXYue+hx7vfn1/2bAJ6m2PDOrA8H6wIgLqbaZDlOEEASgCX8JFexqcZvmfC6fgOtQ73zZ6XVK2c="}]},"_npmUser":{"name":"mtingers","email":"matthingersoll@gmail.com"},"directories":{},"maintainers":[{"name":"mtingers","email":"matthingersoll@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/inflekt_0.1.0_1773634071362_0.8362751990454989"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-16T04:07:51.283Z","0.1.0":"2026-03-16T04:07:51.510Z","modified":"2026-03-16T04:07:51.903Z"},"maintainers":[{"name":"mtingers","email":"matthingersoll@gmail.com"}],"description":"TypeScript-first pluralization and singularization engine — drop-in replacement for pluralize","homepage":"https://github.com/agentine/inflekt#readme","repository":{"type":"git","url":"git+https://github.com/agentine/inflekt.git"},"bugs":{"url":"https://github.com/agentine/inflekt/issues"},"license":"MIT","readme":"# @agentine/inflekt\n\n[![npm version](https://img.shields.io/npm/v/@agentine/inflekt.svg)](https://www.npmjs.com/package/@agentine/inflekt)\n[![npm downloads](https://img.shields.io/npm/dm/@agentine/inflekt.svg)](https://www.npmjs.com/package/@agentine/inflekt)\n[![CI](https://github.com/agentine/inflekt/actions/workflows/ci.yml/badge.svg)](https://github.com/agentine/inflekt/actions)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nTypeScript-first pluralization and singularization engine — drop-in replacement for [pluralize](https://github.com/blakeembrey/pluralize).\n\n## Why inflekt?\n\n| | pluralize | @agentine/inflekt |\n|---|---|---|\n| TypeScript types included | ❌ | ✅ |\n| ESM support | ❌ | ✅ |\n| CJS support | ✅ | ✅ |\n| Zero dependencies | ✅ | ✅ |\n| Actively maintained | ❌ (last release 2019) | ✅ |\n| `\"cookie\"` → `\"cookies\"` | ❌ (`\"cooky\"`) | ✅ |\n| `\"deceased\"` → `\"deceased\"` | ❌ (`\"deceaseds\"`) | ✅ |\n| Uncountable: feedback, species, hertz | ❌ | ✅ |\n| Compound/hyphenated words | ❌ | ✅ |\n\n## Installation\n\n```bash\nnpm install @agentine/inflekt\n```\n\nRequires Node.js >=20.\n\n## Usage\n\n```typescript\nimport { plural, singular, inflect, isPlural, isSingular } from '@agentine/inflekt';\n\n// Pluralize\nplural('person');        // 'people'\nplural('cookie');        // 'cookies'\nplural('cactus');        // 'cacti'\nplural('analysis');      // 'analyses'\nplural('matrix');        // 'matrices'\n\n// Singularize\nsingular('people');      // 'person'\nsingular('cookies');     // 'cookie'\nsingular('analyses');    // 'analysis'\nsingular('matrices');    // 'matrix'\nsingular('cacti');       // 'cactus'\n\n// Count-based inflection\ninflect('cat', 1);       // 'cat'\ninflect('cat', 3);       // 'cats'\ninflect('cat', 3, true); // '3 cats'\ninflect('cat', 0, true); // '0 cats'\n\n// Detection\nisPlural('cats');        // true\nisPlural('sheep');       // true  (uncountable)\nisSingular('cat');       // true\nisSingular('fish');      // true  (uncountable)\n```\n\n### Case preservation\n\ninflekt preserves the original casing pattern of the input word.\n\n```typescript\nplural('cat');     // 'cats'\nplural('Cat');     // 'Cats'\nplural('CAT');     // 'CATS'\nplural('Person');  // 'People'\nplural('PERSON');  // 'PEOPLE'\n```\n\n### Compound words\n\nHyphenated words are supported — the last component is inflected.\n\n```typescript\nplural('mother-in-law');   // 'mother-in-laws'\nplural('test-case');       // 'test-cases'\n```\n\n## API\n\n### `plural(word: string): string`\n\nReturns the plural form of `word`. Preserves original casing. Returns uncountable words unchanged.\n\n```typescript\nplural('leaf');      // 'leaves'\nplural('datum');     // 'data'\nplural('feedback');  // 'feedback'  (uncountable)\n```\n\n### `singular(word: string): string`\n\nReturns the singular form of `word`. Preserves original casing. Returns uncountable words unchanged.\n\n```typescript\nsingular('leaves');   // 'leaf'\nsingular('data');     // 'datum'\nsingular('sheep');    // 'sheep'  (uncountable)\n```\n\n### `inflect(word: string, count: number, inclusive?: boolean): string`\n\nReturns the singular form when `count === 1`, otherwise the plural form. If `inclusive` is `true`, the count is prepended with a space.\n\n```typescript\ninflect('cat', 1);        // 'cat'\ninflect('cat', 2);        // 'cats'\ninflect('cat', 1, true);  // '1 cat'\ninflect('cat', 5, true);  // '5 cats'\n```\n\n### `isPlural(word: string): boolean`\n\nReturns `true` if the word appears to be in plural form, or is uncountable.\n\n```typescript\nisPlural('cats');     // true\nisPlural('cat');      // false\nisPlural('sheep');    // true  (uncountable)\n```\n\n### `isSingular(word: string): boolean`\n\nReturns `true` if the word appears to be in singular form, or is uncountable.\n\n```typescript\nisSingular('cat');    // true\nisSingular('cats');   // false\nisSingular('fish');   // true  (uncountable)\n```\n\n### `addPluralRule(rule: string | RegExp, replacement: string): void`\n\nRegisters a custom pluralization rule. Rules added later take precedence over earlier ones. `replacement` follows the same capture-group syntax as `String.prototype.replace`.\n\n```typescript\naddPluralRule(/gex$/i, 'gices');\nplural('regex');  // 'regices'\n```\n\n### `addSingularRule(rule: string | RegExp, replacement: string): void`\n\nRegisters a custom singularization rule. Rules added later take precedence.\n\n```typescript\naddSingularRule(/gices$/i, 'gex');\nsingular('regices');  // 'regex'\n```\n\n### `addIrregularRule(singular: string, plural: string): void`\n\nRegisters a custom irregular word pair.\n\n```typescript\naddIrregularRule('pokemon', 'pokemon');  // treat as uncountable-like irregular\naddIrregularRule('octopus', 'octopi');\n```\n\n### `addUncountableRule(word: string | RegExp): void`\n\nRegisters a word or pattern as uncountable — `plural` and `singular` return it unchanged.\n\n```typescript\naddUncountableRule('pokemon');\nplural('pokemon');     // 'pokemon'\nsingular('pokemon');   // 'pokemon'\n\naddUncountableRule(/craft$/i);\nplural('hovercraft');  // 'hovercraft'\nplural('aircraft');    // 'aircraft'\n```\n\n## Pluralization rules overview\n\nRules are applied in priority order (higher-priority rules override lower ones). The engine checks, in sequence:\n\n1. **Empty/whitespace** — returned as-is.\n2. **Compound words** — hyphenated inputs split on `-`; the last segment is inflected recursively.\n3. **Uncountables** — words in the uncountable set or matching an uncountable regex are returned unchanged.\n4. **Irregulars** — exact matches in the irregular map (e.g. `person` → `people`) are returned with case preserved.\n5. **Regex rules** — applied in reverse registration order (last registered = highest priority).\n\n### Pluralization regex rules (ordered, last = highest priority)\n\n| Pattern | Replacement | Example |\n|---|---|---|\n| `$` | `s` | cat → cats |\n| `s$` | `s` | bus → bus (no-op) |\n| `(bu\\|mis\\|gas)s$` | `$1ses` | bus → buses |\n| `([ti])um$` | `$1a` | datum → data |\n| `([ti])a$` | `$1a` | data → data (no-op) |\n| `sis$` | `ses` | analysis → analyses |\n| `(?:([^f])fe\\|...)f$` | `$1$2ves` | knife → knives |\n| `(hive)s?$` | `$1s` | hive → hives |\n| `([^aeiouy]\\|qu)y$` | `$1ies` | baby → babies |\n| `(x\\|ch\\|ss\\|sh)$` | `$1es` | box → boxes |\n| `([ml])ouse$` | `$1ice` | mouse → mice |\n| `([ml])ice$` | `$1ice` | mice → mice (no-op) |\n| `^(ox)$` | `$1en` | ox → oxen |\n| `(quiz)$` | `$1zes` | quiz → quizzes |\n| `(database)s?$` | `$1s` | database → databases |\n| `([^aeiou])ies$` | `$1ies` | babies → babies (no-op) |\n| `(ch\\|sh\\|x\\|ss)es$` | `$1es` | boxes → boxes (no-op) |\n| `(alias\\|status\\|...)$` | `$1es` | status → statuses |\n| `(buffal\\|tomat\\|...)o$` | `$1oes` | tomato → tomatoes |\n| `(matr\\|append)ix$\\|(vert\\|ind)ex$` | `$1$2ices` | matrix → matrices |\n| `(octop\\|vir\\|...)us$` | `$1i` | cactus → cacti |\n| `(octop\\|vir\\|...)i$` | `$1i` | cacti → cacti (no-op) |\n| `(ax\\|test)is$` | `$1es` | axis → axes |\n\n### Singularization regex rules (ordered, last = highest priority)\n\n| Pattern | Replacement | Example |\n|---|---|---|\n| `s$` | `` | cats → cat |\n| `ss$` | `ss` | kiss → kiss (no-op) |\n| `(n)ews$` | `$1ews` | news → news (uncountable, no-op) |\n| `([ti])a$` | `$1um` | data → datum |\n| `([^f])ves$` | `$1fe` | knives → knife |\n| `(hive)s$` | `$1` | hives → hive |\n| `(tive)s$` | `$1` | natives → native |\n| `([^aeiouy]\\|qu)ies$` | `$1y` | babies → baby |\n| `ies$` | `y` | series (handled by irregulars) |\n| `(s)eries$` | `$1eries` | series → series (no-op) |\n| `(m)ovies$` | `$1ovie` | movies → movie |\n| `(x\\|ch\\|ss\\|sh)es$` | `$1` | boxes → box |\n| `([ml])ice$` | `$1ouse` | mice → mouse |\n| `(bus)es$` | `$1` | buses → bus |\n| `(o)es$` | `$1` | tomatoes → tomato |\n| `(shoe)s$` | `$1` | shoes → shoe |\n| `(octop\\|vir\\|...)i$` | `$1us` | cacti → cactus |\n| `(alias\\|status\\|...)es$` | `$1` | statuses → status |\n| `^(ox)en` | `$1` | oxen → ox |\n| `(quiz)zes$` | `$1` | quizzes → quiz |\n| `([lr])ves$` | `$1f` | halves → half |\n| `(cris\\|ax\\|test)es$` | `$1is` | axes → axis |\n| `((a)naly\\|...)ses$` | `$1sis` | analyses → analysis |\n| `(matr\\|suff\\|append)ices$` | `$1ix` | matrices → matrix |\n| `(vert\\|ind)ices$` | `$1ex` | vertices → vertex |\n| `(database)s$` | `$1` | databases → database |\n\n## Irregular words\n\nThese word pairs bypass the regex rules entirely. Both directions are registered (singular ↔ plural).\n\n| Singular | Plural |\n|---|---|\n| person | people |\n| man | men |\n| woman | women |\n| child | children |\n| tooth | teeth |\n| foot | feet |\n| goose | geese |\n| ox | oxen |\n| mouse | mice |\n| quiz | quizzes |\n| cookie | cookies |\n| movie | movies |\n| rookie | rookies |\n| smoothie | smoothies |\n| hero | heroes |\n| potato | potatoes |\n| tomato | tomatoes |\n| volcano | volcanoes |\n| tornado | tornadoes |\n| torpedo | torpedoes |\n| domino | dominoes |\n| mosquito | mosquitoes |\n| echo | echoes |\n| veto | vetoes |\n| dingo | dingoes |\n| leaf | leaves |\n| life | lives |\n| genus | genera |\n| opus | opera |\n| oasis | oases |\n| cactus (via rule) | cacti |\n| I | we |\n| me | us |\n| he / she | they |\n| is | are |\n| was | were |\n| has | have |\n| this | these |\n| that | those |\n\n_And many more — see [`src/irregulars.ts`](src/irregulars.ts) for the complete list._\n\n## Uncountable words\n\nThese words have the same singular and plural form:\n\n`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`\n\n## Known edge cases and fixes\n\ninflekt deliberately fixes all known incorrect outputs from `pluralize`:\n\n| Word | pluralize | inflekt |\n|---|---|---|\n| `\"cookie\"` | `\"cooky\"` | `\"cookies\"` |\n| `\"deceased\"` | `\"deceaseds\"` | `\"deceased\"` |\n| `\"feedback\"` | `\"feedbacks\"` | `\"feedback\"` |\n| `\"species\"` | `\"speciess\"` | `\"species\"` |\n| `\"hertz\"` | `\"hertzs\"` | `\"hertz\"` |\n| `\"scissors\"` | `\"scissorss\"` | `\"scissors\"` |\n| `\"series\"` | `\"seriess\"` | `\"series\"` |\n| `\"emoji\"` | `\"emojis\"` | `\"emoji\"` |\n| `\"agenda\"` | varies | `\"agenda\"` |\n| `\"media\"` | `\"medias\"` | `\"media\"` |\n\nAdditional edge cases handled:\n\n- **Empty string** — returned as-is: `plural(\"\") === \"\"`\n- **Whitespace-only string** — returned as-is: `plural(\"  \") === \"  \"`\n- **Already-plural input to `plural`** — returns unchanged: `plural(\"cats\") === \"cats\"`\n- **Already-singular input to `singular`** — returns unchanged: `singular(\"cat\") === \"cat\"`\n- **Uncountable words** — `isPlural` and `isSingular` both return `true`\n\n## Migration from pluralize\n\n`@agentine/inflekt` is a drop-in replacement. Update your import:\n\n```diff\n- const pluralize = require('pluralize');\n+ import { plural, singular, inflect, isPlural, isSingular, addPluralRule, addSingularRule, addIrregularRule, addUncountableRule } from '@agentine/inflekt';\n```\n\nFunction mapping:\n\n| pluralize | inflekt |\n|---|---|\n| `pluralize(word)` | `plural(word)` |\n| `pluralize.singular(word)` | `singular(word)` |\n| `pluralize(word, count)` | `inflect(word, count)` |\n| `pluralize(word, count, true)` | `inflect(word, count, true)` |\n| `pluralize.isPlural(word)` | `isPlural(word)` |\n| `pluralize.isSingular(word)` | `isSingular(word)` |\n| `pluralize.addPluralRule(r, s)` | `addPluralRule(r, s)` |\n| `pluralize.addSingularRule(r, s)` | `addSingularRule(r, s)` |\n| `pluralize.addIrregularRule(s, p)` | `addIrregularRule(s, p)` |\n| `pluralize.addUncountableRule(w)` | `addUncountableRule(w)` |\n\n### Behaviour differences\n\n- inflekt ships with TypeScript types — no separate `@types/pluralize` package needed.\n- inflekt exports **named functions** rather than a default object. Update any usage of `pluralize(word)` to `plural(word)`.\n- inflekt produces correct output for `cookie`, `deceased`, `feedback`, `species`, `hertz`, and all other words that pluralize gets wrong.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-34cdf1a491d4c8dcac395a9d686a32c7"}