to-words
Version:
Convert numbers to words in 132 locales with currency, ordinal, and BigInt support (TypeScript, ESM/CJS/UMD).
213 lines (212 loc) • 8.76 kB
JavaScript
;
/**
* ToWords - Full-featured class with all bundled locales.
*
* This class extends ToWordsCore and adds locale lookup by code string.
* It imports all locales, so use this when you need dynamic locale switching
* or don't care about bundle size.
*
* For tree-shaken single-locale imports, use per-locale entry points instead:
*
* @example
* // Full package (all locales ~55KB gzipped)
* import { ToWords } from 'to-words';
* const tw = new ToWords({ localeCode: 'en-IN' });
*
* // Single locale (~3-4KB gzipped) - SAME API!
* import { ToWords } from 'to-words/en-IN';
* const tw = new ToWords();
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ToWords = exports.LOCALES = exports.DefaultToWordsOptions = exports.DefaultConverterOptions = void 0;
exports.setLocaleDetector = setLocaleDetector;
exports.detectLocale = detectLocale;
exports.toWords = toWords;
exports.toOrdinal = toOrdinal;
exports.toCurrency = toCurrency;
const ToWordsCore_js_1 = require("./ToWordsCore.js");
Object.defineProperty(exports, "DefaultConverterOptions", { enumerable: true, get: function () { return ToWordsCore_js_1.DefaultConverterOptions; } });
Object.defineProperty(exports, "DefaultToWordsOptions", { enumerable: true, get: function () { return ToWordsCore_js_1.DefaultToWordsOptions; } });
const index_js_1 = __importDefault(require("./locales/index.js"));
exports.LOCALES = index_js_1.default;
// Module-level instance cache for the functional helpers (toWords / toOrdinal / toCurrency).
// Each locale gets one cached instance — repeated calls at the same locale are zero-overhead.
const instanceCache = new Map();
// ---------------------------------------------------------------------------
// Locale detection
// ---------------------------------------------------------------------------
/**
* Reads the raw locale string from the runtime environment.
* Checks `navigator.language` first (browser), then falls back to
* `Intl.DateTimeFormat().resolvedOptions().locale` (Node.js, Deno, Bun, CF Workers).
* Returns an empty string when neither source is available.
*
* This is a private helper — callers should use `detectLocale()` or `setLocaleDetector()`.
*/
function readRawLocale() {
// Browser — access through globalThis so it works in all environments
try {
const nav = globalThis.navigator;
if (nav?.language) {
return nav.language;
}
}
catch {
// noop — browser globals unavailable
}
// Node.js / Deno / Bun / CF Workers
try {
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
if (locale) {
return locale;
}
}
catch {
// noop — Intl unavailable
}
return '';
}
/**
* Module-level override for locale detection.
* When set, replaces the default `navigator.language` / Intl detection entirely.
* Pass `null` to restore the built-in detection.
*
* Useful for server environments (e.g. derive locale from `Accept-Language` header)
* or in tests where you want a fixed locale without mocking globals.
*
* @example
* // Server: resolve locale from request header before handling each request
* setLocaleDetector(() => req.headers['accept-language']?.split(',')[0] ?? 'en-US');
*
* // Test: pin to a specific locale
* setLocaleDetector(() => 'fr-FR');
* // … run tests …
* setLocaleDetector(null); // restore
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
let _localeDetector = null;
function setLocaleDetector(fn) {
_localeDetector = fn;
}
class ToWords extends ToWordsCore_js_1.ToWordsCore {
/**
* Get the locale class, either from setLocale() or by looking up the localeCode.
* This overrides ToWordsCore to add LOCALES lookup.
*/
getLocaleClass() {
// First check if a locale was set directly via setLocale()
if (this.localeClass) {
return this.localeClass;
}
// Fall back to looking up by localeCode in LOCALES
if (!(this.options.localeCode in index_js_1.default)) {
throw new Error(`Unknown Locale "${this.options.localeCode}"`);
}
return index_js_1.default[this.options.localeCode];
}
}
exports.ToWords = ToWords;
/**
* Returns a cached `ToWords` instance for the given locale.
* When `localeCode` is omitted, `detectLocale()` is called once here —
* the single place where auto-detection happens for all functional helpers.
*/
function getCachedInstance(localeCode) {
const code = localeCode ?? detectLocale();
let inst = instanceCache.get(code);
if (!inst) {
inst = new ToWords({ localeCode: code });
instanceCache.set(code, inst);
}
return inst;
}
/**
* Detect the current locale from the environment and match it against the supported
* locale list. This is the single entry point for all auto-detection.
*
* Detection order:
* 1. Custom detector (if registered via `setLocaleDetector()`).
* 2. `navigator.language` — browser / React Native.
* 3. `Intl.DateTimeFormat().resolvedOptions().locale` — Node.js, Deno, Bun, CF Workers.
*
* Once a raw locale string is obtained it is normalised and matched:
* 1. Exact match (e.g. `fr-FR`).
* 2. Strip BCP 47 script tag (e.g. `zh-Hant-TW` → `zh-TW`).
* 3. Language-prefix fallback (e.g. `sw-ZZ` → first `sw-*` locale in the list).
*
* Returns `fallback` (default `'en-IN'`) when nothing matches.
*
* @param fallback Locale code to return when detection yields no match.
*/
function detectLocale(fallback = ToWordsCore_js_1.DefaultToWordsOptions.localeCode) {
const candidate = _localeDetector ? _localeDetector() : readRawLocale();
if (!candidate) {
return fallback;
}
const parts = candidate.split('-');
// 1. Exact match
if (candidate in index_js_1.default) {
return candidate;
}
// 2. Normalise: strip script tag, upper-case region (e.g. zh-Hant-TW → zh-TW)
if (parts.length >= 2) {
const normalized = `${parts[0]}-${parts[parts.length - 1].toUpperCase()}`;
if (normalized in index_js_1.default) {
return normalized;
}
}
// 3. Language-prefix fallback (e.g. sw-ZZ → sw-KE)
const lang = parts[0].toLowerCase();
const match = Object.keys(index_js_1.default).find((code) => code.toLowerCase().startsWith(`${lang}-`));
if (match) {
return match;
}
return fallback;
}
/**
* Convert a number to words.
* Uses the full bundle (all locales). For tree-shaken single-locale usage import from `to-words/<locale>`.
* Internally caches one `ToWords` instance per locale — no performance penalty on repeated calls.
* When `localeCode` is omitted, the runtime locale is auto-detected via `detectLocale()`.
*
* @example
* import { toWords } from 'to-words';
* toWords(12345, { localeCode: 'en-US' }); // "Twelve Thousand Three Hundred Forty Five"
* toWords(12345); // uses auto-detected runtime locale, falls back to 'en-IN'
*/
function toWords(number, options) {
const { localeCode, ...converterOptions } = options ?? {};
return getCachedInstance(localeCode).convert(number, converterOptions);
}
/**
* Convert a number to ordinal words.
* Uses the full bundle (all locales). For tree-shaken single-locale usage import from `to-words/<locale>`.
* When `localeCode` is omitted, the runtime locale is auto-detected via `detectLocale()`.
*
* @example
* import { toOrdinal } from 'to-words';
* toOrdinal(21, { localeCode: 'en-US' }); // "Twenty First"
* toOrdinal(21); // uses auto-detected runtime locale, falls back to 'en-IN'
*/
function toOrdinal(number, options) {
const { localeCode, ...ordinalOptions } = options ?? {};
return getCachedInstance(localeCode).toOrdinal(number, ordinalOptions);
}
/**
* Convert a number to currency words.
* Uses the full bundle (all locales). For tree-shaken single-locale usage import from `to-words/<locale>`.
* Shorthand for `toWords(number, { currency: true, ...options })`.
* When `localeCode` is omitted, the runtime locale is auto-detected via `detectLocale()`.
*
* @example
* import { toCurrency } from 'to-words';
* toCurrency(1234.56, { localeCode: 'en-US' }); // "One Thousand Two Hundred Thirty Four Dollars And Fifty Six Cents Only"
* toCurrency(1234.56); // uses auto-detected runtime locale, falls back to 'en-IN'
*/
function toCurrency(number, options) {
const { localeCode, ...converterOptions } = options ?? {};
return getCachedInstance(localeCode).convert(number, { ...converterOptions, currency: true });
}