Humanise numbers - Intl-first exact formatting and strict parsing for numbers, currencies, percentages, units, bytes and durations.
Display prices, measurements, file sizes, and durations in your users' locale. Parse their formatted input back into exact values, and keep table or chart labels on a consistent scale.
This library wraps and extends the native javascript localisation framework Intl and provides processing of numbers to the following formats: number strings, ordinals, percentages, bytes, durations, currencies, and units.
If you only need one-way number, currency, or unit rendering, Intl.NumberFormat may be sufficient. However, this library adds strict inverse parsing, exact scaling, shared series presentation, and a consistent API across domains.
Interactive demo · API reference
pnpm add @neutrium/formatter
This library is ESM-only and requires Node.js 24+ with full ICU data, or a modern browser with Intl support. TypeScript 5.4+ is supported. See compatibility requirements for the tested environments and polyfill ordering.
import { formatter } from "@neutrium/formatter";
const str = formatter.format("1234.5", { kind: "currency", currency: "USD" });
console.log(str); // "$1,234.50"
The shared instance defaults to the en-US locale. Create an independent instance for another default locale:
import { createFormatter } from "@neutrium/formatter";
const german = createFormatter({ locale: "de-DE" });
const str = german.format("1234.5", { kind: "number" });
console.log(str); // "1.234,5"
Below is an introduction for the various types of formatting options available in @neutrium/formatter. See the formatting guide, interactive demo and API reference for further guidance and examples of using this library.
Numeric specifications use kind: "number" for general localized numbers. Inputs may be numbers, decimal strings, bigint values, or compatible decimal objects; use strings or @neutrium/decimal when the digits must remain exact:
import { formatter } from "@neutrium/formatter";
const str = formatter.format("9007199254740993.25", {
kind: "number",
maximumFractionDigits: 2,
});
console.log(str); // "9,007,199,254,740,993.25"
Ordinal formatting applies locale-specific rules to numbers that describe an order, such as a position in a list:
const str = formatter.format(23, { kind: "ordinal" });
console.log(str); // "23rd"
You can provide a custom pattern for a locale, using {number} where the formatted number belongs:
formatter.format(42, {
kind: "ordinal",
ordinalPatterns: { other: "No. {number}" },
});
// "No. 42"
import { formatter } from "@neutrium/formatter";
const str = formatter.format("1234.5", { kind: "currency", currency: "USD" });
console.log(str); // "$1,234.50"
Use compact notation for abbreviated magnitudes. The locale selects the label and plural form, while compactExponent can fix a shared scale:
let str = formatter.format(1536, {
kind: "number",
notation: "compact",
maximumFractionDigits: 1,
});
console.log(str); // "1.5K"
str = formatter.format(1_250_000, {
kind: "number",
notation: "compact",
compactExponent: 6,
maximumFractionDigits: 1,
});
console.log(str); // "1.3M"
The percentage formatter applies the configured power-of-ten scale before rendering the percent sign:
const str = formatter.format(0.125, {
kind: "percentage",
maximumFractionDigits: 1,
});
console.log(str) // "12.5%"
Every duration specification requires an explicit presentation:
import { formatter } from "@neutrium/formatter";
import { parser } from "@neutrium/formatter/parse";
let str = formatter.format(3661, { kind: "duration", presentation: "elapsed" });
console.log(str); // "1:01:01"
str = formatter.format(
{ hours: 1, minutes: 1, seconds: 1 },
{ kind: "duration", presentation: "localized", style: "long" },
);
console.log(str) // "1 hour, 1 minute, 1 second" — format-only
Bytes default to IEC base 1024; select base 1000 for SI units:
const str = formatter.format(1536, { kind: "bytes" });
console.log(str); // "1.5 KiB"
You can format an array of values either as formatted values or common length formatted values for rendering in a table:
import { formatter } from "@neutrium/formatter";
formatter.formatSeries([1200, 1500, 900], {
kind: "number",
notation: "compact",
maximumFractionDigits: 1,
});
// ["1.2K", "1.5K", "0.9K"] — one shared scale
formatter.formatColumn([1.2, 12, 123.45], {
kind: "number",
maximumFractionDigits: 2,
});
// [" 1.2 ", " 12 ", "123.45"]
Series use shared compact/byte scales by default. Pass { scale: "individual" } as the third argument for independent scales. Use formatRange(start, end, spec) for localized ranges.
Use formatter.compileSeries(values, spec) to select a scale once and reuse it for later batches or scalar formatting; pass its .spec to parser.compile() for strict parsing. The returned compiled formatter exposes the selected scale in its frozen .spec.
See series and columns for scale and parsing caveats, and ranges for supported domains.
Compile a specification to improve performance when repeatedly rendering the same specification:
import { formatter, type CurrencyFormatSpec } from "@neutrium/formatter";
const moneySpec = {
kind: "currency",
currency: "USD",
maximumFractionDigits: 2,
} satisfies CurrencyFormatSpec;
const money = formatter.compile(moneySpec);
["12.25", "20"].map(money.format); // ["$12.25", "$20.00"]
Compilation validates the options and freezes a copy, so later edits to moneySpec do not change money. Its methods work as callbacks. See compiled formatters for pairing one with a parser.
Add a custom codec when your application has a value domain or rendering rules that the built-in formats do not cover. Register formatting codecs with createFormatter; parsing codecs use the separate createParser entry point:
import { createFormatter } from "@neutrium/formatter";
import type { FormatCodec, FormatSpecBase } from "@neutrium/formatter/extensions";
interface Point { x: number; y: number }
interface PointSpec extends FormatSpecBase { kind: "point"; separator?: string }
const pointCodec = {
kind: "point",
format(point, spec)
{
return [{ type: "literal", value: `${point.x}${spec.separator ?? ","}${point.y}` }];
},
} satisfies FormatCodec<"point", Point, PointSpec>;
const custom = createFormatter({ codecs: [pointCodec] });
custom.format({ x: 10, y: 20 }, { kind: "point" });
Custom codecs can define their own specification and value types while keeping the built-in codecs available. See the custom codecs guide for complete formatting and parsing examples, validation, compiled codecs, and bundle-size guidance.
This package also provides formatted number parsing capability using an optional import:
import { parser, createParser } from "@neutrium/formatter/parse";
const money = parser.compile({ kind: "currency", currency: "USD" });
money.parse("$12.25"); // "12.25"
const german = createParser({ locale: "de-DE" });
german.parse("1.234,5", { kind: "number" }); // "1234.5"
You can pass a compiled formatter's .spec to parser.compile() to preserve its locale and selected scale.
Use decimal strings or bigint when a JavaScript number cannot represent a value exactly. Objects with toValue(): string, including @neutrium/decimal instances, are also accepted.
See the parsing guide for input validation, matching locales, and parsing scaled values.
supports() vs resolve(), runtime capabilities, and deployment requirements.This project is licensed under the MIT License, see the LICENSE file for details.
You are free to:
Under the following conditions:
This plugin is provided "as is", without warranty of any kind. Use at your own risk.