|
| 1 | +<div align="center"> |
| 2 | + |
| 3 | +[![typescript-image]][typescript-url] |
| 4 | +[![npm-image]][npm-url] |
| 5 | +[![npm-download-image]][npm-download-url] |
| 6 | +[![license-image]][license-url] |
| 7 | + |
| 8 | +</div> |
| 9 | + |
| 10 | +`@boringnode/pluralize` is a TypeScript port of the pluralization features from [Doctrine Inflector](https://github.com/doctrine/inflector). |
| 11 | + |
| 12 | +Built-in support for: |
| 13 | +- **English** (`en`, `english`) |
| 14 | +- **French** (`fr`, `french`) |
| 15 | + |
| 16 | +> [!NOTE] |
| 17 | +> Changes to inflection rules (adding, removing, or modifying rules) will not be considered as breaking changes. |
| 18 | +
|
| 19 | +## Installation |
| 20 | + |
| 21 | +```sh |
| 22 | +npm install @boringnode/pluralize |
| 23 | +``` |
| 24 | + |
| 25 | +## Usage |
| 26 | + |
| 27 | +### Simple Functions |
| 28 | + |
| 29 | +The easiest way to use the library is with the simple functions. |
| 30 | + |
| 31 | +```ts |
| 32 | +import { pluralize, singularize } from '@boringnode/pluralize' |
| 33 | + |
| 34 | +pluralize('word') // 'words' |
| 35 | +pluralize('person') // 'people' |
| 36 | +pluralize('child') // 'children' |
| 37 | + |
| 38 | +singularize('words') // 'word' |
| 39 | +singularize('people') // 'person' |
| 40 | +singularize('children') // 'child' |
| 41 | +``` |
| 42 | + |
| 43 | +### Using a Different Locale |
| 44 | + |
| 45 | +You can create an `Inflector` instance with a different locale. |
| 46 | + |
| 47 | +```ts |
| 48 | +import { Inflector } from '@boringnode/pluralize' |
| 49 | + |
| 50 | +const inflector = new Inflector('fr') |
| 51 | + |
| 52 | +inflector.pluralize('cheval') // 'chevaux' |
| 53 | +inflector.pluralize('bijou') // 'bijoux' |
| 54 | +inflector.singularize('chevaux') // 'cheval' |
| 55 | +``` |
| 56 | + |
| 57 | +### Custom Rules with Inflector Class |
| 58 | + |
| 59 | +You can create an `Inflector` instance to add custom rules. |
| 60 | + |
| 61 | +```ts |
| 62 | +import { Inflector } from '@boringnode/pluralize' |
| 63 | + |
| 64 | +const inflector = new Inflector() |
| 65 | + .addIrregular('gex', 'gexes') |
| 66 | + .addUninflected('pokemon') |
| 67 | + .addPluralRule(/(.*)gon$/i, '$1gons') |
| 68 | + .addSingularRule(/(.*)gons$/i, '$1gon') |
| 69 | + |
| 70 | +inflector.pluralize('gex') // 'gexes' |
| 71 | +inflector.pluralize('pokemon') // 'pokemon' |
| 72 | +inflector.singularize('dragons') // 'dragon' |
| 73 | +``` |
| 74 | + |
| 75 | +### Creating a Custom Language Ruleset |
| 76 | + |
| 77 | +You can create and register custom language rulesets for other languages. |
| 78 | + |
| 79 | +```ts |
| 80 | +import { Inflector, type LanguageRuleset } from '@boringnode/pluralize' |
| 81 | +import { |
| 82 | + pattern, |
| 83 | + Patterns, |
| 84 | + Substitutions, |
| 85 | + Transformations, |
| 86 | +} from '@boringnode/pluralize/builder' |
| 87 | + |
| 88 | +const SpanishRuleset: LanguageRuleset = { |
| 89 | + getSingularRuleset: () => ({ |
| 90 | + regular: new Transformations([ |
| 91 | + { pattern: pattern('es$'), replacement: '' }, |
| 92 | + { pattern: pattern('s$'), replacement: '' }, |
| 93 | + ]), |
| 94 | + uninflected: new Patterns([pattern('lunes'), pattern('martes')]), |
| 95 | + irregular: new Substitutions([{ from: 'hombres', to: 'hombre' }]), |
| 96 | + }), |
| 97 | + getPluralRuleset: () => ({ |
| 98 | + regular: new Transformations([ |
| 99 | + { pattern: pattern('[aeiou]$'), replacement: '$&s' }, |
| 100 | + { pattern: pattern('$'), replacement: 'es' }, |
| 101 | + ]), |
| 102 | + uninflected: new Patterns([pattern('lunes'), pattern('martes')]), |
| 103 | + irregular: new Substitutions([{ from: 'hombre', to: 'hombres' }]), |
| 104 | + }), |
| 105 | +} |
| 106 | + |
| 107 | +// Register the ruleset |
| 108 | +Inflector.register('es', SpanishRuleset) |
| 109 | + |
| 110 | +// Use it |
| 111 | +const inflector = new Inflector('es') |
| 112 | +inflector.pluralize('gato') // 'gatos' |
| 113 | +``` |
| 114 | + |
| 115 | +## API |
| 116 | + |
| 117 | +### Functions |
| 118 | + |
| 119 | +- `pluralize(word: string): string` - Returns the plural form of a word |
| 120 | +- `singularize(word: string): string` - Returns the singular form of a word |
| 121 | + |
| 122 | +### Inflector Class |
| 123 | + |
| 124 | +| Method | Description | |
| 125 | +| --------------------------------------- | ----------------------------------------------------------- | |
| 126 | +| `new Inflector(locale?: string)` | Creates a new inflector instance (defaults to `'en'`) | |
| 127 | +| `Inflector.register(locale, ruleset)` | Registers a custom language ruleset | |
| 128 | +| `pluralize(word)` | Returns the plural form of a word | |
| 129 | +| `singularize(word)` | Returns the singular form of a word | |
| 130 | +| `addIrregular(singular, plural)` | Adds an irregular word mapping | |
| 131 | +| `addUninflected(word)` | Adds a word that doesn't change between singular and plural | |
| 132 | +| `addPluralRule(pattern, replacement)` | Adds a custom pluralization rule | |
| 133 | +| `addSingularRule(pattern, replacement)` | Adds a custom singularization rule | |
| 134 | + |
| 135 | +### Builder Exports (`@boringnode/pluralize/builder`) |
| 136 | + |
| 137 | +For creating custom language rulesets: |
| 138 | + |
| 139 | +| Export | Description | |
| 140 | +| ----------------- | -------------------------------------------------------- | |
| 141 | +| `pattern` | Helper function to create a case-insensitive regex | |
| 142 | +| `Patterns` | A collection of patterns for uninflected words | |
| 143 | +| `Transformation` | Interface: `{ pattern: RegExp, replacement: string }` | |
| 144 | +| `Transformations` | A collection of transformations | |
| 145 | +| `Substitution` | Interface: `{ from: string, to: string }` | |
| 146 | +| `Substitutions` | A collection of substitutions | |
| 147 | +| `Ruleset` | Interface: `{ regular, uninflected, irregular }` | |
| 148 | + |
| 149 | +[npm-image]: https://img.shields.io/npm/v/@boringnode/pluralize.svg?style=for-the-badge&logo=npm |
| 150 | +[npm-url]: https://www.npmjs.com/package/@boringnode/pluralize |
| 151 | +[npm-download-image]: https://img.shields.io/npm/dm/@boringnode/pluralize?style=for-the-badge |
| 152 | +[npm-download-url]: https://www.npmjs.com/package/@boringnode/pluralize |
| 153 | +[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript |
| 154 | +[typescript-url]: https://www.typescriptlang.org |
| 155 | +[license-image]: https://img.shields.io/npm/l/@boringnode/pluralize?color=blueviolet&style=for-the-badge |
| 156 | +[license-url]: LICENSE.md |
0 commit comments