UNPKG

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
"use strict"; /** * 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 }); }