Formatter
formatter.service.ts is a function module: every export is a standalone use* helper with no singleton and no setup. All helpers are pure, stateless and SSR-safe — they depend only on Intl and JSON, so they behave the same in the browser, Node, Bun and edge runtimes.
Three rules shape the whole module:
- Format at the edge — helpers return display strings. Keep raw numbers, dates and objects in your state and call the formatter when you render.
- Locales are explicit — the
Localetype is a closed union:"en" | "es" | "fr" | "de" | "it" | "pt" | "ja" | "zh". Other BCP 47 tags work at runtime throughIntl, but only these eight typecheck. - No token mini-language — the library never parses format patterns. Numbers and currency delegate to
Intl.NumberFormat; dates delegate toIntl.DateTimeFormatthrough Temporal; text uses the built-in case methods.
The unit-conversion helpers that share this source file (useToCelsius(), useToKilometers(), useToKilos() and the rest) are documented on the Converter page.
Quick example
import {
useFormatNumber,
useFormatCurrency,
useFormat,
useCapitalize,
useUpperCase,
useLowerCase,
useJsonStringify,
useJsonParse,
} from "katanakit-js";
useFormatNumber(1234.5); // "1,234.50"
useFormatNumber(1234.5, "de"); // "1.234,50"
useFormatCurrency({ amount: 100 }); // "$100.00"
useFormatCurrency({ amount: 100, taxes: 21, locale: "es" }); // "121,00 US$"
useFormat("2024-06-15", "en"); // "6/15/2024"
useCapitalize("hello world"); // "Hello world"
useUpperCase("straße", "de"); // "STRASSE"
useLowerCase("HELLO"); // "hello"
useJsonStringify({ a: 1 }); // '{\n "a": 1\n}'
useJsonParse<{ a: number }>('{"a":1}'); // { a: 1 }Numbers
useFormatNumber(value, locale?, digits?)
Formats a number with locale-aware grouping and a fixed number of fraction digits. digits is applied as both the minimum and the maximum, so the output is always padded or rounded to that precision:
| Call | Result | Notes |
|---|---|---|
useFormatNumber(1234.5) | "1,234.50" | en defaults, 2 digits |
useFormatNumber(1234.5, "de") | "1.234,50" | de swaps separators |
useFormatNumber(42, "en", 0) | "42" | 0 disables decimals |
useFormatNumber(0.5, "en", 4) | "0.5000" | pads to exactly 4 digits |
useFormatNumber(1.005, "en", 2) | "1.01" | rounding is delegated to Intl |
Parameters
value— the number to format.locale— one of the supportedLocaletags. Defaults to"en".digits— fraction digits, applied to both min and max. Defaults to2.
import { useFormatNumber, type Locale } from "katanakit-js";
export function formatKpi(value: number, locale: Locale) {
return useFormatNumber(value, locale, 1);
}Edge cases
- Non-finite values are formatted literally instead of throwing:
NaN→"NaN",Infinity→"∞",-Infinity→"-∞". - A
digitsvalue outside the range accepted by the runtime (0–100 in current engines) throwsRangeError. - A structurally valid but unknown locale falls back to the runtime default; a malformed tag throws
RangeError. Stick to theLocaleunion when the output must be stable.
Real use case — dashboard metrics: store the raw number returned by the API and format it per user locale at render time, so no rounding is baked into your data layer.
Currency
useFormatCurrency(options)
Formats an amount as currency, optionally applying a tax rate. Everything is configured through a single options object:
| Option | Type | Default | Description |
|---|---|---|---|
amount | number | — (required) | Base amount, before taxes. |
currency | Currency | "USD" | One of the supported ISO codes. |
taxes | number | 0 | Tax rate added to the amount. |
locale | Locale | "en" | Controls separators, symbol and spacing. |
Supported currencies: EUR, USD, GBP, JPY, CAD, MXN, CHF, AUD, BRL, CNY, ARS, COP, CLP.
Tax handling is the subtle part. The implementation computes rate = taxes > 1 ? taxes / 100 : taxes and then amount * (1 + rate), which means:
taxes: 0(default) — no tax.taxes: 21— a percentage: +21%.taxes: 0.21— a decimal fraction: +21%.taxes: 1— the fraction branch, so +100%, not +1%.
Use explicit fractions only for rates at or below 1% (0.005 → 0.5%). The source does not validate the sign: negative values follow the fraction branch and reduce the total.
import { useFormatCurrency } from "katanakit-js";
useFormatCurrency({ amount: 100, currency: "USD" }); // "$100.00"
useFormatCurrency({ amount: 100, taxes: 21 }); // "$121.00"
useFormatCurrency({ amount: 100, taxes: 0.21, locale: "es" }); // "121,00 US$"
useFormatCurrency({ amount: 100, taxes: 0.21, currency: "EUR", locale: "es" }); // "121,00 €"Edge cases
- Fraction digits come from the currency, not from a parameter:
USDrenders two decimals,JPYrenders none ("¥1,235"). There is nodigitsoption. - Only the 13 codes above typecheck. At runtime the string reaches
Intlunchanged, so an unknown-but-well-formed code still renders (the code itself is shown), while a malformed code throwsRangeError. amount: 0and negative amounts format normally ("-$10.00").
Real use case — checkout summaries: keep prices tax-free in the database and apply the tax rate at render time, passing the buyer's locale so the symbol and separators match their region.
import { useFormatCurrency, type Currency, type Locale } from "katanakit-js";
export function orderTotal(
subtotal: number,
taxPercent: number,
currency: Currency,
locale: Locale,
) {
return useFormatCurrency({ amount: subtotal, taxes: taxPercent, currency, locale });
}Dates
useFormat(dateInput, locale?, options?)
Formats a date-like value into a localized string. This helper ships with the Dates service and is re-exported from the package root; it is documented here because it is the formatting entry point. The rest of the Temporal-based date toolkit lives on that page.
Accepted inputs
| Input | Parsed as |
|---|---|
number | Epoch milliseconds → zoned date-time in the runtime time zone. |
Date | Epoch milliseconds → zoned date-time in the runtime time zone. |
Temporal.Instant | Zoned date-time in the runtime time zone. |
ISO date string ("2024-06-15") | Temporal.PlainDate. |
| ISO date-time string without offset | Temporal.PlainDate first — see edge cases. |
Temporal.PlainDate, PlainDateTime, ZonedDateTime | Used as-is. |
Options are plain Intl.DateTimeFormatOptions (default {}), forwarded to Temporal's toLocaleString and then to Intl.DateTimeFormat. The library implements no custom tokens, so the accepted keys are the standard ones: dateStyle, timeStyle, year, month, day, weekday, hour, minute, second, fractionalSecondDigits, timeZone, timeZoneName, and so on.
import { useFormat } from "katanakit-js";
useFormat("2024-06-15"); // "6/15/2024"
useFormat("2024-06-15", "es", { dateStyle: "full" }); // "sábado, 15 de junio de 2024"
useFormat("2024-06-15", "de", { dateStyle: "long" }); // "15. Juni 2024"
useFormat(
new Date(2024, 5, 15, 14, 30),
"en",
{ dateStyle: "medium", timeStyle: "short" },
); // "Jun 15, 2024, 2:30 PM"Edge cases
- Date-time strings without an offset are parsed as
PlainDatefirst, which drops the time silently.useFormat("2024-06-15T14:30:00", "en", { dateStyle: "medium" })returns"Jun 15, 2024", and requestingtimeStylealone throwsInvalid date input. When the time matters, pass aDate, epoch milliseconds, or aTemporal.PlainDateTime. - Strings carrying a UTC designator (
Z) or a numeric offset (+02:00) are rejected, because neitherPlainDatenorPlainDateTimeaccepts offsets. - Invalid input is logged through
useLoggerand then thrown asnew Error("Invalid date input: ..."). Wrap the call withuseAttempt()at I/O boundaries. number,DateandTemporal.Instantinputs are converted to the runtime's time zone, so a server in UTC and a browser in UTC+2 can render different hours for the same instant. PasstimeZoneinoptionsor aZonedDateTimefor deterministic output.- With the default
{}options, the output depends on the runtime's ICU defaults. Always pass an explicit style for user-facing copy.
Real use case — localized timestamps in feeds and comments: store ISO strings in the API, then format them per reader locale and style at render time.
Text
Text helpers transform and normalize strings. All case helpers trim whitespace before returning.
useCapitalize(text, locale?)
Upper-cases only the first character and leaves the rest untouched. Empty or whitespace-only input returns "", so it is safe to call on uncontrolled data.
import { useCapitalize } from "katanakit-js";
useCapitalize("hello world"); // "Hello world"
useCapitalize(" hello "); // "Hello"
useCapitalize("áÉl"); // "ÁÉl"Real use case — rendering names from a database that stores them in lowercase: capitalize for display without mutating the stored value.
useUpperCase(text, locale?)
Locale-aware upper case via toLocaleUpperCase, then trim().
import { useUpperCase } from "katanakit-js";
useUpperCase("hello"); // "HELLO"
useUpperCase("straße", "de"); // "STRASSE"
useUpperCase("i", "tr"); // "İ" — Turkish dotted capital IEdge case — do not use it for case-insensitive comparisons: equal-looking strings can differ under locale rules. Use localeCompare (or Intl.Collator) for that.
useLowerCase(text, locale?)
Mirror of useUpperCase() through toLocaleLowerCase, then trim().
import { useLowerCase } from "katanakit-js";
useLowerCase("HELLO"); // "hello"
useLowerCase("I", "tr"); // "ı" — Turkish dotless lower caseReal use case — normalizing user input (emails, tags, search terms) before persisting or comparing, with the locale the input was typed in.
useJsonStringify(data)
Pretty-prints a value as JSON with a 3-space indent — note the unusual indent, which comes from JSON.stringify(data, null, 3).
import { useJsonStringify } from "katanakit-js";
useJsonStringify({ a: 1, nested: { b: 2 } });
// {
// "a": 1,
// "nested": {
// "b": 2
// }
// }Edge cases — circular references and BigInt values throw TypeError (same as JSON.stringify); top-level values with no JSON representation (undefined, functions, symbols) return undefined at runtime even though the declared return type is string.
Real use case — a developer-facing payload preview or a "copy debug state" button that needs readable JSON.
useJsonParse<T>(json)
A thin, typed wrapper around JSON.parse — useful to avoid as casts while keeping the familiar throw-on-invalid behavior. It performs no runtime validation: the generic T is an assertion, not a guarantee. Invalid JSON throws SyntaxError.
import { useJsonParse } from "katanakit-js";
const config = useJsonParse<{ theme: "light" | "dark" }>('{"theme":"dark"}');
config.theme; // "dark"Edge case — for untrusted input (storage, network, user files), prefer useTryJsonParse(), which returns a Safe Result instead of throwing, and validate the parsed shape with a schema before using it.
Related
- Converter — temperature, distance, height and weight conversions from the same source file.
- Dates — the full Temporal toolkit behind
useFormat(). - Generator —
useSlugify()and other string generators. - Errors & Results —
useTryJsonParse()and Safe Results. - API Reference — every export, generated from source.