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

pure-humanize

v0.2.0

Published

Zero-dependency, cross-runtime micro-library for human-readable data formatting via native Intl APIs

Readme

npm version CI

pure-humanize

Zero-dependency, cross-runtime TypeScript micro-library for formatting data into human-readable strings using native Intl APIs.

size dependencies license typescript

Features

  • Intl-native — delegates all locale logic to the runtime's built-in Intl APIs; no bundled locale data
  • Zero dependencies — nothing to audit, nothing to update
  • Tree-shakeable — ESM-first with sideEffects: false; import only what you use
  • Cross-runtime — works in Node.js 18+, Deno, Bun, and all modern browsers
  • Tiny — entire bundle is under 2 KB; individual modules are a few hundred bytes each
  • Fully typed — every function and options object ships with TypeScript types

Install

npm install pure-humanize

Quick Start

import { timeAgo, bytes, number, currency } from 'pure-humanize';

timeAgo(Date.now() - 3_600_000);          // "1 hour ago"
bytes(1_572_864);                          // "1.5 MiB"
number(1_234_567);                         // "1.2M"
currency(4999.99, 'USD');                  // "$4,999.99"

API Reference

timeAgo

function timeAgo(date: Date | number | string, options?: TimeAgoOptions): string

Formats a date into a human-readable relative time string using Intl.RelativeTimeFormat.

timeAgo(Date.now() - 3_600_000)                          // "1 hour ago"
timeAgo(Date.now() - 86_400_000)                         // "yesterday"
timeAgo(Date.now() + 300_000)                            // "in 5 minutes"
timeAgo('2020-01-01', { locale: 'de', style: 'short' })  // "vor 5 J."

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | locale | string \| string[] | runtime default | BCP 47 locale(s) | | style | 'long' \| 'short' \| 'narrow' | 'long' | Output verbosity | | numeric | 'always' \| 'auto' | 'auto' | 'auto' allows "yesterday"; 'always' forces "1 day ago" | | now | Date \| number | Date.now() | Reference point for "now" |


bytes

function bytes(value: number, options?: BytesOptions): string

Formats a byte count into a human-readable size string using Intl.NumberFormat.

bytes(1024)                                         // "1 KiB"
bytes(1000, { binary: false })                      // "1 kB"
bytes(1536, { binary: true, maximumFractionDigits: 2 }) // "1.5 KiB"
bytes(-2048)                                        // "-2 KiB"

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | locale | string \| string[] | runtime default | BCP 47 locale(s) | | binary | boolean | true | Use binary units (KiB, MiB, base 1024) vs SI units (kB, MB, base 1000) | | maximumFractionDigits | number | 1 | Maximum decimal places | | minimumFractionDigits | number | 0 | Minimum decimal places | | unitSeparator | string | ' ' | String between number and unit |


number

function number(value: number, options?: NumberOptions): string

Formats a number using compact notation via Intl.NumberFormat.

number(1234)                               // "1.2K"
number(1234567)                            // "1.2M"
number(999)                                // "999"
number(1500, { compactDisplay: 'long' })   // "1.5 thousand"

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | locale | string \| string[] | runtime default | BCP 47 locale(s) | | compactDisplay | 'short' \| 'long' | 'short' | 'short' → "1.2K", 'long' → "1.2 thousand" | | maximumFractionDigits | number | 1 | Maximum decimal places | | minimumFractionDigits | number | 0 | Minimum decimal places | | maximumSignificantDigits | number | — | When set, overrides fraction digit options |


currency

function currency(value: number, currencyCode: string, options?: CurrencyOptions): string

Formats a number as a currency string using Intl.NumberFormat.

currency(1234.5, CURRENCY_CODES.USD)                                   // "$1,234.50"
currency(1_000_000, CURRENCY_CODES.USD, { compact: true })             // "$1M"
currency(1500, CURRENCY_CODES.EUR, { currencyDisplay: 'code' })        // "EUR 1,500.00"
currency(1000, CURRENCY_CODES.JPY)                                     // "¥1,000"

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | locale | string \| string[] | runtime default | BCP 47 locale(s) | | currencyDisplay | 'symbol' \| 'narrowSymbol' \| 'code' \| 'name' | 'symbol' | How the currency label appears | | compact | boolean | false | Use compact notation (e.g. $1M) | | maximumFractionDigits | number | currency default | Maximum decimal places | | minimumFractionDigits | number | currency default | Minimum decimal places | | trailingZeroDisplay | 'auto' \| 'stripIfInteger' | — | Strip trailing zeros on whole numbers |


list

function list(items: string[], options?: ListOptions): string

Formats an array of strings into a human-readable list using Intl.ListFormat.

list(['Alice', 'Bob', 'Charlie'])                            // "Alice, Bob, and Charlie"
list(['Alice', 'Bob'], { type: LIST_TYPE.DISJUNCTION })             // "Alice or Bob"
list(['5 kg', '10 km'], { type: LIST_TYPE.UNIT, style: LIST_STYLE.NARROW })  // "5 kg 10 km"
list([])                                                     // ""

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | locale | string \| string[] | runtime default | BCP 47 locale(s) | | type | 'conjunction' \| 'disjunction' \| 'unit' | 'conjunction' | 'conjunction' → "and", 'disjunction' → "or", 'unit' → bare list | | style | 'long' \| 'short' \| 'narrow' | 'long' | Output verbosity |


plural

function plural(count: number, forms: PluralForms, options?: PluralOptions): string

Selects the correct plural form for a count using Intl.PluralRules. The # placeholder in templates is replaced by the locale-formatted count.

plural(1, { one: '# item', other: '# items' })   // "1 item"
plural(5, { one: '# item', other: '# items' })   // "5 items"
plural(0, { zero: 'no items', other: '# items' }) // "no items"

PluralForms — at minimum, other is required. Provide additional keys to handle CLDR plural categories:

| Key | Description | |-----|-------------| | other | Required fallback (e.g. "# items") | | one | Singular form (e.g. "# item") | | zero | Zero form (e.g. "no items") | | two | Dual form (used in some locales) | | few | Paucal form (used in Slavic locales) | | many | Used in some locales (e.g. Welsh) |

PluralOptions:

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | locale | string \| string[] | runtime default | BCP 47 locale(s) | | type | 'cardinal' \| 'ordinal' | 'cardinal' | Plural rule type |


ordinal

function ordinal(n: number, options?: OrdinalOptions): string

Formats a number as an ordinal string using Intl.PluralRules with type: 'ordinal'.

Note (v0.1): Suffix strings (st, nd, rd, th) are English-only. The locale option affects PluralRules category selection but not the suffix characters.

ordinal(1)   // "1st"
ordinal(2)   // "2nd"
ordinal(3)   // "3rd"
ordinal(11)  // "11th"
ordinal(21)  // "21st"

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | locale | string \| string[] | runtime default | BCP 47 locale(s) |


truncate

function truncate(str: string, length: number, options?: TruncateOptions): string

Truncates a string to a maximum character length, inserting an ellipsis indicator.

truncate('Hello, World!', 8)                              // "Hello..."
truncate('Hello, World!', 8, { position: TRUNCATE_POSITION.MIDDLE })     // "Hel...d!"
truncate('Hello, World!', 8, { position: TRUNCATE_POSITION.START })      // "...orld!"
truncate('Hello World', 8, { wordBoundary: true })        // "Hello..."

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | ellipsis | string | '...' | Truncation indicator | | position | 'end' \| 'middle' \| 'start' | 'end' | Where to cut the string | | wordBoundary | boolean | false | Avoid mid-word cuts (only applies when position is 'end') |


Constants

All option string-literal values, time/byte units, currency codes, and a curated set of locale identifiers are exported as named constants with matching TypeScript types. They are opt-in via dedicated subpaths so the main bundle stays minimal.

import { currency, timeAgo, truncate } from 'pure-humanize';
import { CURRENCY_CODES }     from 'pure-humanize/constants/currencies';
import { LOCALES }            from 'pure-humanize/constants/locales';
import {
  CURRENCY_DISPLAY,
  RELATIVE_TIME_STYLE,
  TRUNCATE_POSITION,
} from 'pure-humanize/constants/styles';

currency(1500, CURRENCY_CODES.EUR, {
  locale: LOCALES['de-DE'],
  currencyDisplay: CURRENCY_DISPLAY.CODE,
}); // "1.500,00 EUR"

timeAgo(Date.now() - 3_600_000, { style: RELATIVE_TIME_STYLE.SHORT });

truncate('Hello, World!', 8, { position: TRUNCATE_POSITION.START });

Or pull everything from the barrel:

import { CURRENCY_CODES, LOCALES, RELATIVE_TIME_STYLE } from 'pure-humanize/constants';

Subpaths

| Subpath | Exports | |---------|---------| | pure-humanize/constants/currencies | CURRENCY_CODES, CURRENCY_CODE_LIST, type CurrencyCode — full ISO 4217 active list (~170 codes) | | pure-humanize/constants/locales | LOCALES, LOCALE_LIST, type Locale — curated BCP 47 identifiers (en-US, ru-RU, de-DE, …) | | pure-humanize/constants/styles | All option string-literal constants (see table below) | | pure-humanize/constants/units | Time-in-seconds, byte units (binary + SI), ordinal suffixes | | pure-humanize/constants | Barrel — re-exports everything |

Style constants

| Constant | Type | Members | |----------|------|---------| | RELATIVE_TIME_STYLE | RelativeTimeStyle | LONG, SHORT, NARROW | | RELATIVE_TIME_NUMERIC | RelativeTimeNumeric | ALWAYS, AUTO | | LIST_STYLE | ListStyle | LONG, SHORT, NARROW | | LIST_TYPE | ListType | CONJUNCTION, DISJUNCTION, UNIT | | COMPACT_DISPLAY | CompactDisplay | SHORT, LONG | | CURRENCY_DISPLAY | CurrencyDisplay | SYMBOL, NARROW_SYMBOL, CODE, NAME | | TRAILING_ZERO_DISPLAY | TrailingZeroDisplay | AUTO, STRIP_IF_INTEGER | | NOTATION | Notation | STANDARD, COMPACT, SCIENTIFIC, ENGINEERING | | NUMBER_STYLE | NumberStyle | DECIMAL, CURRENCY, PERCENT, UNIT | | PLURAL_TYPE | PluralType | CARDINAL, ORDINAL | | PLURAL_CATEGORY | PluralCategory | ZERO, ONE, TWO, FEW, MANY, OTHER | | TRUNCATE_POSITION | TruncatePosition | END, MIDDLE, START |

Unit constants

| Constant | Description | |----------|-------------| | TIME_UNIT_SECONDS | { MINUTE: 60, HOUR: 3600, DAY: 86400, WEEK: 604800, MONTH: 2592000, YEAR: 31536000 } | | TIME_UNIT / TimeUnit | 'second' \| 'minute' \| 'hour' \| 'day' \| 'week' \| 'month' \| 'year' | | BINARY_BYTE_UNITS / BinaryByteUnit | ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'] | | SI_BYTE_UNITS / SiByteUnit | ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB'] | | BINARY_BYTE_UNITBINARY_EBYTE_UNIT | Individual binary unit literals | | SI_BYTE_UNITSI_EBYTE_UNIT | Individual SI unit literals | | ORDINAL_SUFFIXES_EN | { one: 'st', two: 'nd', few: 'rd', other: 'th' } |

The function signatures still accept any compatible string (locale?: string, currencyCode: string), so existing code that passes raw literals continues to work. Constants are a typed, autocomplete-friendly alternative.


Tree-shaking

Import the whole library or pull in individual modules — both work with any bundler that respects sideEffects: false:

// Full import — bundler tree-shakes unused exports
import { timeAgo, bytes } from 'pure-humanize';

// Deep import — guaranteed single-module bundle, no tree-shaking needed
import { timeAgo } from 'pure-humanize/timeAgo';
import { bytes } from 'pure-humanize/bytes';
import { currency } from 'pure-humanize/currency';

// Constants live in their own subpaths so they never bloat the main bundle
import { CURRENCY_CODES } from 'pure-humanize/constants/currencies';
import { RELATIVE_TIME_STYLE } from 'pure-humanize/constants/styles';

Each subpath export ships as both ESM (.js) and CJS (.cjs) with a co-located .d.ts file.

Runtime Compatibility

| Runtime | Minimum version | |---------|----------------| | Node.js | 18+ | | Deno | 1.x+ | | Bun | 1.x+ | | Chrome | 72+ | | Firefox | 78+ | | Safari | 14.1+ | | Edge | 79+ |

All formatting is delegated to the runtime's Intl implementation. The required APIs are Intl.RelativeTimeFormat, Intl.NumberFormat, Intl.ListFormat, and Intl.PluralRules.

License

MIT