Hey there, I'm Daniel and I'm a software engineer working in Stockholm, Sweden.
A while back I was working on a settings page and had to add a section letting users know when their subscription would expire. This was pretty straightforward so I added:
const days = 2 // fetched from db
const message = `Your subscription will end in ${days} ${days > 1 ? "days" : "day"}`
Nice and simple. But super annoying. I've always been triggered by apps that just hard code something like 2 day(s)
.
So I got thinking and wanted to make an npm package that could easily find the plural for any English noun. This was the start of my first open source project tinyplural
.
kwaimind / tinyplural
✍ A tiny pluralizer for English nouns
To do this I used TSDX which helps scaffold TypeScript libs and started to research how plurals work in English for nouns.
A noun is a word that refers to a thing (book), a person (Betty Crocker), an animal (cat), a place (Omaha), a quality (softness), an idea (justice), or an action (yodeling). It's usually a single word, but not always: cake, shoes, school bus, and time and a half are all nouns.
A word denoting more than one, or (in languages with dual number) more than two.
Grammerly had a great article that broke down English nouns into some core grammar rules that I could build functions around.
First version
In my first version, I made a function for each of these rules. This was inefficient but easy to test and understand. Most of the rules of English are based on the last few letters of the word, so my solution here was to use some RegEx to test if a string ended with specific letters and then return a noun with the plural. Once this worked I refactored everything into 4 core functions that could do all the work based on some find and replace keys or a callback.
The function works by passing in a singular noun (i.e. not the plural version) and a number
tinyplural("day", 2);
English, like many languages, has a few nouns that don't follow any rules known as /irregular nouns/. Some follow a different pattern and others don't change. i.e., 1 fish, 2 fish, 1 child, 2 children. For these, I added an array that we check and if there is a match, we return the same string or the irregular version.
Focusing on speed
Since this is to be used as a 3rd party lib, I wanted to make sure things were fast and simple and so I added some performance optimisations to help.
First up, this package is only checking for plurals, so we escape early if there is only 1 of a noun.
tinyplural("day", 1) // early escape, return original input
tinyplural("day", 2) // run the lookup
To manage all of the rules, I made an array of functions with their options. As soon as we have a match, we break and return the result.
Finally, I added a cache map to memorize inputs and prevent recalculating the same result. If the function is called with the same arguments, we return the cached version and skip any further calculations:
// first time
tinyplural("day", 2) // run the lookup
// second time
tinyplural("day", 2) // check the cache map, return the previous result
TSDX
TSDX is a great package for npm packages like this and even bundles with size-limit
to check your gzipped final bundle size. Keeping a tiny package was really important to me so there are 0 dependencies and size-limit
made me realise that simplifying all of my checking functions into 1 or 2 core functions that took options would be a better strategy and help reduce the code size.
Future ideas
I’m pretty happy with the outcome so far and have a few ideas I want to try and continue building on:
- Working with other latin-based languages based on a given local
- Working on a better release pipeline with Github actions + npm
- Testing in a production app
Top comments (0)