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

@jsenv/humanize

v1.8.1

Published

Generate messages meant to read by humans

Downloads

7,004

Readme

humanize npm package

@jsenv/humanize helps to generate messages meant to be read by humans

It is written in ES6 and compatible with browsers and Node.js.

API

humanize(jsValue)

Turns a JavaScript value into a string meant to be read by a human.

import { humanize } from "@jsenv/humanize";

console.log(
  humanize({
    boolean: true,
    number: 10,
    string: "hello world",
  }),
);
{
  "boolean": true,
  "number": 10,
  "string": "hello world"
}

Comparison with JSON.stringify

Table comparing JSON.stringify and humanize() to demonstrates how inspect focuses on readability and accuracy.

| value | JSON.stringify | humanize | | --------- | -------------- | ----------- | | 123456789 | "123456789" | 123_456_789 | | Infinity | "null" | Infinity | | -0 | "0" | -0 | | '"' | '"\""' | '"' |

humanizeDuration(ms, options)

import { humanizeDuration } from "@jsenv/humanize";

humanizeDuration(61_421); // "1 minute and 1 second"

format* — text for the reader of an app

Everything above writes English for a developer reading a terminal. The format* family writes for the person using the app, in their language, through Intl.

| what you are writing | reach for | | --------------------------------------------- | -------------------------------------------------- | | a message in a terminal, a log, a test report | humanize, humanizeDuration, humanizeFileSize | | a date, a delay or a number someone will read | format* |

It lives in this package, which has no frontend of its own, so that a server and a browser word the same instant identically: these are the functions behind @jsenv/navi's <Time> components, so a notification row written by a backend and the card it points at read the same date the same way — and a REST service does not install a frontend framework to print a month name. Nothing here touches the DOM.

import {
  formatDatetime,
  formatDay,
  formatDuration,
  formatHourDuration,
  formatMinuteDuration,
  formatMonth,
  formatNumber,
  formatSecondDuration,
  formatTimeOfDay,
  formatTimeRange,
  formatTimeRelative,
} from "@jsenv/humanize";

const start = new Date("2026-05-11T14:05:00");
const end = new Date("2026-05-11T16:00:00");
const lang = "fr";

// a date
formatDay(start, { lang }); // "lundi 11 mai"
formatDay(start, { lang, format: "numeric" }); // "11/05/2026"
formatMonth(start, { lang }); // "mai 2026"
formatDatetime(start, { lang }); // "lun. 11 mai à 14:05"

// a time of day, a span
formatTimeOfDay(start, { lang, format: "compact" }); // "14h05"
formatTimeRange(start, end, { lang, format: "compact" }); // "14h05–16h00"

// a duration, from the unit you happen to hold
formatMinuteDuration(90, { lang, format: "compact" }); // "1h30"
formatHourDuration(2.5, { lang }); // "2 heures et 30 minutes"
formatSecondDuration(90, { lang }); // "1 minute et 30 secondes"
formatDuration("PT1H30M", { lang }); // "1 heure et 30 minutes"

// a moment, relative to now (second argument is how long it lasts)
formatTimeRelative(start, 30 * 60_000, { lang });
// "dans 1 heure et 30 minutes", "En cours" or "il y a 2 heures"

// a number
formatNumber(1_234_567.5, { lang }); // "1 234 567,5"

Each takes more than lang — how a part is spelled, precision, padding, time zone, whether the year is written. Those options live in the function's own JSDoc, which the published bundle keeps: hover the import in an editor rather than looking for a page listing them.

Two more, for a date field with nothing in it yet: formatDatePlaceholder() writes "jj/mm/aaaa", and formatMonthPlaceholder(), formatWeekPlaceholder() and formatDatetimePlaceholder() do the same for their own shape.

Which language

With no lang, a call uses the runtime language source: by default the runtime's own locale, exactly what Intl would pick on its own. A frontend replaces that source once, with a live one:

import { setRuntimeLangSource } from "@jsenv/humanize";

setRuntimeLangSource(() => languagesSignal.value);

@jsenv/navi already does this, pointing it at the user's language preference; because the source is read on every call, a component formatting a date re-renders when that preference changes.

On a server the default is the process locale, which is nobody's in particular — pass lang explicitly whenever the text is for someone else, and timeZone too when the instant must be worded in the reader's zone rather than the machine's.

Changing a word

The words around the numbers — "time.ongoing", the compact unit symbols, the placeholder tokens — live in humanizeI18n, and every one of them can be replaced or translated into a language that is not shipped:

import { humanizeI18n } from "@jsenv/humanize";

humanizeI18n.add("time.ongoing", { fr: "En cours…" });
humanizeI18n.addLangKeys("ja", { "time.midnight": "真夜中" });

src/i18n/humanize_i18n.js is the exhaustive list of keys and defaults, and is meant to be read. It is the same registry @jsenv/navi exposes as naviI18n, so one call reaches both.

createI18n() (an app's own text registry) and interpolateText() (one sentence with values in it) are exported here too; @jsenv/navi's docs/i18n.md explains when to use which, and why a library's keys are opaque while an app's are not.