@wikytam/helpers 1.0.1 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +51 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +21 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +49 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -295,6 +295,20 @@ function normalizeDate(value) {
|
|
|
295
295
|
throw new Error(`Invalid data type for date: ${typeof value}`);
|
|
296
296
|
}
|
|
297
297
|
/**
|
|
298
|
+
* Parse a date string safely, treating timezone-naive ISO strings as UTC.
|
|
299
|
+
* Strings like "2024-03-15T14:30:00" (no Z, no +offset) get "Z" appended
|
|
300
|
+
* to ensure consistent UTC interpretation across environments.
|
|
301
|
+
* Returns an Invalid Date (NaN) for null, undefined, or empty values
|
|
302
|
+
* instead of throwing - suitable for display pipelines.
|
|
303
|
+
*/
|
|
304
|
+
function parseDate(date) {
|
|
305
|
+
if (!date) return /* @__PURE__ */ new Date(NaN);
|
|
306
|
+
if (date instanceof Date) return date;
|
|
307
|
+
let str = date;
|
|
308
|
+
if (typeof str === "string" && !str.endsWith("Z") && !str.includes("+") && str.includes("T")) str += "Z";
|
|
309
|
+
return new Date(str);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
298
312
|
* Normalize an input value into a number.
|
|
299
313
|
* Accepts: number, numeric string (with optional comma grouping), boolean.
|
|
300
314
|
*/
|
|
@@ -1142,15 +1156,52 @@ const formatter = new Proxy({}, { get(_target, prop, receiver) {
|
|
|
1142
1156
|
function configureFormatter(options) {
|
|
1143
1157
|
instance = new Formatter(options);
|
|
1144
1158
|
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Format a date value safely for display using the global formatter.
|
|
1161
|
+
* Returns `fallback` (default: "\u2014") for null, undefined, empty, or invalid dates.
|
|
1162
|
+
* Handles timezone-naive ISO strings by treating them as UTC.
|
|
1163
|
+
*/
|
|
1164
|
+
function formatDate(date, fallback = "—") {
|
|
1165
|
+
if (!date) return fallback;
|
|
1166
|
+
try {
|
|
1167
|
+
const d = parseDate(date);
|
|
1168
|
+
if (Number.isNaN(d.getTime())) return fallback;
|
|
1169
|
+
return formatter.asDate(d);
|
|
1170
|
+
} catch {
|
|
1171
|
+
return fallback;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Format a value as a percentage for display using the global formatter.
|
|
1176
|
+
* - If the string already contains "%", returns it as-is.
|
|
1177
|
+
* - Values between 0 and 1 (exclusive) are treated as ratios (multiplied by 100).
|
|
1178
|
+
* - Returns `fallback` (default: "\u2014") for null/undefined/unparseable values.
|
|
1179
|
+
*/
|
|
1180
|
+
function formatPercent(value, fallback = "—") {
|
|
1181
|
+
if (value === null || value === void 0) return fallback;
|
|
1182
|
+
if (typeof value === "string") {
|
|
1183
|
+
if (value.trim() === "") return fallback;
|
|
1184
|
+
if (value.includes("%")) return value;
|
|
1185
|
+
const num = Number(value);
|
|
1186
|
+
if (Number.isNaN(num)) return value || fallback;
|
|
1187
|
+
const pct = num > 0 && num <= 1 ? num * 100 : num;
|
|
1188
|
+
return `${formatter.asDecimal(pct, 2)}%`;
|
|
1189
|
+
}
|
|
1190
|
+
const pct = value > 0 && value <= 1 ? value * 100 : value;
|
|
1191
|
+
return `${formatter.asDecimal(pct, 2)}%`;
|
|
1192
|
+
}
|
|
1145
1193
|
//#endregion
|
|
1146
1194
|
exports.Formatter = Formatter;
|
|
1147
1195
|
exports.configureFormatter = configureFormatter;
|
|
1148
1196
|
exports.escapeHtml = escapeHtml;
|
|
1197
|
+
exports.formatDate = formatDate;
|
|
1198
|
+
exports.formatPercent = formatPercent;
|
|
1149
1199
|
exports.formatter = formatter;
|
|
1150
1200
|
exports.getNumberShortConfig = getNumberShortConfig;
|
|
1151
1201
|
exports.getSpellout = getSpellout;
|
|
1152
1202
|
exports.normalizeDate = normalizeDate;
|
|
1153
1203
|
exports.normalizeNumber = normalizeNumber;
|
|
1204
|
+
exports.parseDate = parseDate;
|
|
1154
1205
|
exports.registerNumberShort = registerNumberShort;
|
|
1155
1206
|
exports.registerSpellout = registerSpellout;
|
|
1156
1207
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["ones","digitWords","convert"],"sources":["../src/locales/en.ts","../src/locales/vi.ts","../src/locales/index.ts","../src/utils.ts","../src/formatter.ts","../src/global.ts"],"sourcesContent":["import type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\nconst ones = [\n\t\"\",\n\t\"one\",\n\t\"two\",\n\t\"three\",\n\t\"four\",\n\t\"five\",\n\t\"six\",\n\t\"seven\",\n\t\"eight\",\n\t\"nine\",\n\t\"ten\",\n\t\"eleven\",\n\t\"twelve\",\n\t\"thirteen\",\n\t\"fourteen\",\n\t\"fifteen\",\n\t\"sixteen\",\n\t\"seventeen\",\n\t\"eighteen\",\n\t\"nineteen\",\n]\n\nconst tens = [\n\t\"\",\n\t\"\",\n\t\"twenty\",\n\t\"thirty\",\n\t\"forty\",\n\t\"fifty\",\n\t\"sixty\",\n\t\"seventy\",\n\t\"eighty\",\n\t\"ninety\",\n]\n\nconst digitWords: Record<string, string> = {\n\t\"0\": \"zero\",\n\t\"1\": \"one\",\n\t\"2\": \"two\",\n\t\"3\": \"three\",\n\t\"4\": \"four\",\n\t\"5\": \"five\",\n\t\"6\": \"six\",\n\t\"7\": \"seven\",\n\t\"8\": \"eight\",\n\t\"9\": \"nine\",\n}\n\nfunction convert(num: number): string {\n\tif (num === 0) return \"\"\n\tif (num < 20) return ones[num] ?? \"\"\n\tif (num < 100) {\n\t\tconst t = tens[Math.floor(num / 10)] ?? \"\"\n\t\tconst o = ones[num % 10]\n\t\treturn o ? `${t}-${o}` : t\n\t}\n\tif (num < 1000) {\n\t\tconst h = ones[Math.floor(num / 100)] ?? \"\"\n\t\tconst remainder = num % 100\n\t\treturn remainder ? `${h} hundred ${convert(remainder)}` : `${h} hundred`\n\t}\n\tif (num < 1_000_000) {\n\t\tconst th = convert(Math.floor(num / 1000))\n\t\tconst remainder = num % 1000\n\t\treturn remainder ? `${th} thousand ${convert(remainder)}` : `${th} thousand`\n\t}\n\tif (num < 1_000_000_000) {\n\t\tconst m = convert(Math.floor(num / 1_000_000))\n\t\tconst remainder = num % 1_000_000\n\t\treturn remainder ? `${m} million ${convert(remainder)}` : `${m} million`\n\t}\n\tconst b = convert(Math.floor(num / 1_000_000_000))\n\tconst remainder = num % 1_000_000_000\n\treturn remainder ? `${b} billion ${convert(remainder)}` : `${b} billion`\n}\n\nexport const enSpellout: LocaleSpellout = {\n\tzeroWord: \"zero\",\n\tpointWord: \"point\",\n\tnegativePrefix: \"minus\",\n\n\tintegerToWords(n: number): string {\n\t\tif (n === 0) return \"zero\"\n\t\treturn convert(n).trim()\n\t},\n\n\tdigitToWord(digit: string): string {\n\t\treturn digitWords[digit] ?? digit\n\t},\n}\n\nexport const enNumberShort: NumberShortConfig = {\n\tthresholds: [\n\t\t{ value: 1_000_000_000_000, suffix: \" Trillion\" },\n\t\t{ value: 1_000_000_000, suffix: \" Billion\" },\n\t\t{ value: 1_000_000, suffix: \" Million\" },\n\t\t{ value: 1_000, suffix: \"K\" },\n\t],\n}\n","import type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\nconst ones = [\n\t\"\",\n\t\"m\\u1ed9t\",\n\t\"hai\",\n\t\"ba\",\n\t\"b\\u1ed1n\",\n\t\"n\\u0103m\",\n\t\"s\\u00e1u\",\n\t\"b\\u1ea3y\",\n\t\"t\\u00e1m\",\n\t\"ch\\u00edn\",\n]\n\nconst onesInTens = [\n\t\"\",\n\t\"m\\u1ed1t\",\n\t\"hai\",\n\t\"ba\",\n\t\"b\\u1ed1n\",\n\t\"l\\u0103m\", // 5 in tens position uses \"lam\" not \"nam\"\n\t\"s\\u00e1u\",\n\t\"b\\u1ea3y\",\n\t\"t\\u00e1m\",\n\t\"ch\\u00edn\",\n]\n\nconst digitWords: Record<string, string> = {\n\t\"0\": \"kh\\u00f4ng\",\n\t\"1\": \"m\\u1ed9t\",\n\t\"2\": \"hai\",\n\t\"3\": \"ba\",\n\t\"4\": \"b\\u1ed1n\",\n\t\"5\": \"n\\u0103m\",\n\t\"6\": \"s\\u00e1u\",\n\t\"7\": \"b\\u1ea3y\",\n\t\"8\": \"t\\u00e1m\",\n\t\"9\": \"ch\\u00edn\",\n}\n\n/**\n * Vietnamese number spellout following standard rules:\n * - 5 in ones position of tens => \"lam\" (not \"nam\")\n * - 1 in ones position of tens (>=20) => \"mot\" with special handling\n * - 0 in ones position of tens => \"muoi\" only (no trailing)\n * - Tens starting with 1 => \"muoi\", otherwise => \"muoi\" with prefix\n */\nfunction readTens(t: number, u: number): string {\n\tlet result = \"\"\n\n\tif (t === 1) {\n\t\tresult = \"m\\u01b0\\u1eddi\"\n\t} else {\n\t\tresult = `${ones[t]} m\\u01b0\\u01a1i`\n\t}\n\n\tif (u === 0) return result\n\tif (u === 1 && t > 1) return `${result} m\\u1ed1t`\n\tif (u === 5 && t > 0) return `${result} l\\u0103m`\n\treturn `${result} ${onesInTens[u] ?? \"\"}`\n}\n\nfunction readHundreds(h: number, t: number, u: number): string {\n\tconst result = `${ones[h]} tr\\u0103m`\n\tif (t === 0 && u === 0) return result\n\tif (t === 0) return `${result} linh ${ones[u]}`\n\treturn `${result} ${readTens(t, u)}`\n}\n\nfunction readBlock(num: number): string {\n\tif (num === 0) return \"\"\n\n\tconst h = Math.floor(num / 100)\n\tconst t = Math.floor((num % 100) / 10)\n\tconst u = num % 10\n\n\tif (h > 0) return readHundreds(h, t, u)\n\tif (t > 0) return readTens(t, u)\n\treturn ones[u] ?? \"\"\n}\n\nfunction convert(num: number): string {\n\tif (num === 0) return \"kh\\u00f4ng\"\n\n\tconst units = [\n\t\t{ value: 1_000_000_000, label: \"t\\u1ef7\" },\n\t\t{ value: 1_000_000, label: \"tri\\u1ec7u\" },\n\t\t{ value: 1_000, label: \"ngh\\u00ecn\" },\n\t\t{ value: 1, label: \"\" },\n\t]\n\n\tconst parts: string[] = []\n\tlet remaining = num\n\n\tfor (const unit of units) {\n\t\tif (remaining >= unit.value) {\n\t\t\tconst block = Math.floor(remaining / unit.value)\n\t\t\tremaining %= unit.value\n\n\t\t\tconst blockStr = readBlock(block)\n\t\t\tif (blockStr) {\n\t\t\t\tparts.push(unit.label ? `${blockStr} ${unit.label}` : blockStr)\n\t\t\t}\n\n\t\t\t// Handle leading zeros in next block (e.g. 1001 -> \"mot nghin khong tram linh mot\")\n\t\t\tif (remaining > 0 && remaining < unit.value / 10) {\n\t\t\t\t// Needs \"khong tram\" prefix if next block < 100\n\t\t\t\tif (remaining < 100 && unit.value >= 1000) {\n\t\t\t\t\tparts.push(\"kh\\u00f4ng tr\\u0103m\")\n\t\t\t\t\tif (remaining < 10) {\n\t\t\t\t\t\tparts.push(`linh ${ones[remaining]}`)\n\t\t\t\t\t\tremaining = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\" \").trim()\n}\n\nexport const viSpellout: LocaleSpellout = {\n\tzeroWord: \"kh\\u00f4ng\",\n\tpointWord: \"ph\\u1ea9y\",\n\tnegativePrefix: \"\\u00e2m\",\n\n\tintegerToWords(n: number): string {\n\t\tif (n === 0) return \"kh\\u00f4ng\"\n\t\treturn convert(n)\n\t},\n\n\tdigitToWord(digit: string): string {\n\t\treturn digitWords[digit] ?? digit\n\t},\n}\n\nexport const viNumberShort: NumberShortConfig = {\n\tthresholds: [\n\t\t{ value: 1_000_000_000_000, suffix: \" Ngh\\u00ecn T\\u1ef7\" },\n\t\t{ value: 1_000_000_000, suffix: \" T\\u1ef7\" },\n\t\t{ value: 1_000_000, suffix: \" Tri\\u1ec7u\" },\n\t\t{ value: 1_000, suffix: \" Ng\\u00e0n\" },\n\t],\n}\n","import { enNumberShort, enSpellout } from \"./en.js\"\nimport type {\n\tLocaleRegistry,\n\tLocaleSpellout,\n\tNumberShortConfig,\n\tNumberShortRegistry,\n} from \"./types.js\"\nimport { viNumberShort, viSpellout } from \"./vi.js\"\n\nexport type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\n/** Built-in spellout locale registry. */\nconst spelloutRegistry: LocaleRegistry = {\n\ten: enSpellout,\n\tvi: viSpellout,\n}\n\n/** Built-in number-short locale registry. */\nconst numberShortRegistry: NumberShortRegistry = {\n\ten: enNumberShort,\n\tvi: viNumberShort,\n}\n\n/** Get the spellout provider for a locale, falling back to English. */\nexport function getSpellout(locale: string): LocaleSpellout {\n\tconst lang = locale.split(\"-\")[0]\n\treturn spelloutRegistry[lang] ?? enSpellout\n}\n\n/** Get the number-short config for a locale, falling back to English. */\nexport function getNumberShortConfig(locale: string): NumberShortConfig {\n\tconst lang = locale.split(\"-\")[0]\n\treturn numberShortRegistry[lang] ?? enNumberShort\n}\n\n/** Register a custom spellout locale at runtime. */\nexport function registerSpellout(lang: string, impl: LocaleSpellout): void {\n\tspelloutRegistry[lang] = impl\n}\n\n/** Register a custom number-short config at runtime. */\nexport function registerNumberShort(\n\tlang: string,\n\tconfig: NumberShortConfig,\n): void {\n\tnumberShortRegistry[lang] = config\n}\n","import type { DateFormatPreset } from \"./types.js\"\n\n/**\n * Escape the 5 HTML-special characters, equivalent to PHP's htmlspecialchars().\n * No external dependency - pure string replacement.\n */\nexport function escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\")\n}\n\n/**\n * Normalize an input value into a Date object.\n * Accepts: Date, number (UNIX seconds or milliseconds), string (ISO 8601).\n */\nexport function normalizeDate(value: unknown): Date {\n\tif (value instanceof Date) return value\n\n\tif (typeof value === \"number\") {\n\t\t// Values below 1e12 are treated as seconds, otherwise milliseconds\n\t\treturn new Date(value < 1e12 ? value * 1000 : value)\n\t}\n\n\tif (typeof value === \"string\") {\n\t\tconst parsed = new Date(value)\n\t\tif (Number.isNaN(parsed.getTime())) {\n\t\t\tthrow new Error(`Cannot parse date value: \"${value}\"`)\n\t\t}\n\t\treturn parsed\n\t}\n\n\tthrow new Error(`Invalid data type for date: ${typeof value}`)\n}\n\n/**\n * Normalize an input value into a number.\n * Accepts: number, numeric string (with optional comma grouping), boolean.\n */\nexport function normalizeNumber(value: unknown): number {\n\tif (typeof value === \"number\") return value\n\n\tif (typeof value === \"string\") {\n\t\tconst trimmed = value.trim()\n\t\t// Strip common thousand separators before parsing\n\t\tconst cleaned = trimmed.replace(/,/g, \"\")\n\t\tconst num = Number(cleaned)\n\t\tif (Number.isNaN(num)) {\n\t\t\tthrow new Error(`Cannot parse numeric value: \"${value}\"`)\n\t\t}\n\t\treturn num\n\t}\n\n\tif (typeof value === \"boolean\") return value ? 1 : 0\n\n\tthrow new Error(`Invalid data type for number: ${typeof value}`)\n}\n\n/**\n * Convert a preset name (short/medium/long/full) to Intl.DateTimeFormatOptions.\n */\nexport function presetToDateOptions(\n\tpreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tconst dateOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { year: \"2-digit\", month: \"numeric\", day: \"numeric\" },\n\t\tmedium: { year: \"numeric\", month: \"short\", day: \"numeric\" },\n\t\tlong: { year: \"numeric\", month: \"long\", day: \"numeric\" },\n\t\tfull: { year: \"numeric\", month: \"long\", day: \"numeric\", weekday: \"long\" },\n\t}\n\n\tconst timeOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { hour: \"numeric\", minute: \"numeric\" },\n\t\tmedium: { hour: \"numeric\", minute: \"numeric\", second: \"numeric\" },\n\t\tlong: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"short\",\n\t\t},\n\t\tfull: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"long\",\n\t\t},\n\t}\n\n\tswitch (type) {\n\t\tcase \"date\":\n\t\t\treturn dateOptions[preset] ?? dateOptions.medium\n\t\tcase \"time\":\n\t\t\treturn timeOptions[preset] ?? timeOptions.medium\n\t\tcase \"datetime\":\n\t\t\treturn {\n\t\t\t\t...(dateOptions[preset] ?? dateOptions.medium),\n\t\t\t\t...(timeOptions[preset] ?? timeOptions.medium),\n\t\t\t}\n\t}\n}\n\n/**\n * Resolve a format value: string preset -> Intl options, object -> use directly.\n */\nexport function resolveDateFormat(\n\tformat: string | Intl.DateTimeFormatOptions | undefined,\n\tdefaultPreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tif (!format) return presetToDateOptions(defaultPreset, type)\n\tif (typeof format === \"object\") return format\n\treturn presetToDateOptions(format as DateFormatPreset, type)\n}\n\n/**\n * Replace locale-default separators with custom ones in a formatted string.\n * Uses temporary placeholders to avoid replacement collisions.\n */\nexport function applyCustomSeparators(\n\tformatted: string,\n\tlocale: string,\n\tcustomDecimal?: string | null,\n\tcustomThousand?: string | null,\n): string {\n\tif (customDecimal == null && customThousand == null) return formatted\n\n\t// Detect locale-default separators\n\tconst parts = new Intl.NumberFormat(locale).formatToParts(1234567.89)\n\tconst localeDecimal = parts.find((p) => p.type === \"decimal\")?.value ?? \".\"\n\tconst localeGroup = parts.find((p) => p.type === \"group\")?.value ?? \",\"\n\n\tlet result = formatted\n\n\t// Temporary placeholders to prevent collision during replacement\n\tconst PLACEHOLDER_DEC = \"\\x01\"\n\tconst PLACEHOLDER_GRP = \"\\x02\"\n\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(localeDecimal, PLACEHOLDER_DEC)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(localeGroup, PLACEHOLDER_GRP)\n\t}\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_DEC, customDecimal)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_GRP, customThousand)\n\t}\n\n\treturn result\n}\n","import { getNumberShortConfig, getSpellout } from \"./locales/index.js\"\nimport type {\n\tEmailOptions,\n\tFormatterOptions,\n\tFormatWidth,\n\tGpsDistanceOptions,\n\tHtmlSanitizeConfig,\n\tImageOptions,\n\tMaskOptions,\n\tMeasureUnitConfig,\n\tNumberShortOptions,\n\tOrdinalSuffixMap,\n\tParagraphOptions,\n\tUnitSystem,\n\tUrlOptions,\n} from \"./types.js\"\nimport {\n\tapplyCustomSeparators,\n\tescapeHtml,\n\tnormalizeDate,\n\tnormalizeNumber,\n\tresolveDateFormat,\n} from \"./utils.js\"\n\n/** Returns true for null or undefined only. */\nfunction isNullish(value: unknown): value is null | undefined {\n\treturn value === null || value === undefined\n}\n\n/** Returns true for null, undefined, or empty/whitespace-only strings. */\nfunction isBlank(value: unknown): value is null | undefined {\n\tif (value === null || value === undefined) return true\n\tif (typeof value === \"string\" && value.trim() === \"\") return true\n\treturn false\n}\n\n/**\n * TypeScript port of yii\\i18n\\Formatter.\n *\n * Uses only built-in Intl APIs - zero external dependencies.\n * Supports: strings, HTML, numbers, currency, dates, times,\n * file sizes, measurement units, and more.\n */\nexport class Formatter {\n\tpublic locale: string\n\tpublic timeZone: string\n\tpublic defaultTimeZone: string\n\tpublic dateFormat: string | Intl.DateTimeFormatOptions\n\tpublic timeFormat: string | Intl.DateTimeFormatOptions\n\tpublic datetimeFormat: string | Intl.DateTimeFormatOptions\n\tpublic booleanFormat: [string, string]\n\tpublic nullDisplay: string\n\tpublic currencyCode: string\n\tpublic decimalSeparator: string | null\n\tpublic thousandSeparator: string | null\n\tpublic currencyDecimalSeparator: string | null\n\tpublic sizeFormatBase: 1024 | 1000\n\tpublic systemOfUnits: UnitSystem\n\tpublic defaultDecimalDigits: number | null\n\n\tconstructor(options: FormatterOptions = {}) {\n\t\tthis.locale = options.locale ?? \"en-US\"\n\t\tthis.timeZone = options.timeZone ?? \"UTC\"\n\t\tthis.defaultTimeZone = options.defaultTimeZone ?? \"UTC\"\n\t\tthis.dateFormat = options.dateFormat ?? \"medium\"\n\t\tthis.timeFormat = options.timeFormat ?? \"medium\"\n\t\tthis.datetimeFormat = options.datetimeFormat ?? \"medium\"\n\t\tthis.booleanFormat = options.booleanFormat ?? [\"No\", \"Yes\"]\n\t\tthis.nullDisplay = options.nullDisplay ?? \"(not set)\"\n\t\tthis.currencyCode = options.currencyCode ?? \"USD\"\n\t\tthis.decimalSeparator = options.decimalSeparator ?? null\n\t\tthis.thousandSeparator = options.thousandSeparator ?? null\n\t\tthis.currencyDecimalSeparator = options.currencyDecimalSeparator ?? null\n\t\tthis.sizeFormatBase = options.sizeFormatBase ?? 1024\n\t\tthis.systemOfUnits = options.systemOfUnits ?? \"metric\"\n\t\tthis.defaultDecimalDigits = options.defaultDecimalDigits ?? null\n\t}\n\n\t// ─── Generic dispatch ─────────────────────────────────────────────\n\n\t/**\n\t * Format a value by type name, like Yii2's `$formatter->format($value, 'date')`.\n\t * Supports both string and tuple `[formatName, ...params]` signatures.\n\t */\n\tpublic format(value: unknown, type: string | [string, ...unknown[]]): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\n\t\tconst formatName = Array.isArray(type) ? type[0] : type\n\t\tconst params = Array.isArray(type) ? type.slice(1) : []\n\t\tconst methodName = `as${formatName.charAt(0).toUpperCase()}${formatName.slice(1)}`\n\n\t\tconst method = (this as Record<string, unknown>)[methodName]\n\t\tif (typeof method === \"function\") {\n\t\t\treturn (method as (...args: unknown[]) => string).call(\n\t\t\t\tthis,\n\t\t\t\tvalue,\n\t\t\t\t...params,\n\t\t\t)\n\t\t}\n\n\t\tthrow new Error(`Unknown format type: ${formatName}`)\n\t}\n\n\t// ─── String & HTML ────────────────────────────────────────────────\n\n\t/** Returns the value as-is without any formatting. */\n\tpublic asRaw(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn String(value)\n\t}\n\n\t/** Formats the value as HTML-encoded plain text. */\n\tpublic asText(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn escapeHtml(String(value))\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text with newlines converted to `<br />`.\n\t * Handles all line-ending variants: `\\r\\n` (Windows), `\\r` (old Mac), `\\n` (Unix).\n\t * Consecutive newlines produce multiple `<br />` tags.\n\t */\n\tpublic asNtext(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst escaped = escapeHtml(String(value))\n\t\treturn escaped.replace(/\\r\\n/g, \"<br />\").replace(/[\\r\\n]/g, \"<br />\")\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text paragraphs (split by double newlines).\n\t * Supports configurable wrapper tag and inline line-break conversion.\n\t */\n\tpublic asParagraphs(value: unknown, options?: ParagraphOptions): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst tag = options?.tag ?? \"p\"\n\t\tconst lineBreaks = options?.lineBreaks ?? false\n\t\tconst text = String(value)\n\t\t// Normalize line endings before splitting\n\t\tconst normalized = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\")\n\t\tconst paragraphs = normalized.split(/\\n\\s*\\n/)\n\t\treturn paragraphs\n\t\t\t.map((p) => {\n\t\t\t\tlet content = escapeHtml(p.trim())\n\t\t\t\tif (lineBreaks) {\n\t\t\t\t\tcontent = content.replace(/\\n/g, \"<br />\")\n\t\t\t\t}\n\t\t\t\treturn `<${tag}>${content}</${tag}>`\n\t\t\t})\n\t\t\t.filter((p) => p !== `<${tag}></${tag}>`)\n\t\t\t.join(\"\\n\")\n\t}\n\n\t/**\n\t * Returns the value as HTML text.\n\t * When a sanitize config is provided, only allowed tags and attributes are kept.\n\t * Without config, the value is returned as-is (caller is responsible for safety).\n\t */\n\tpublic asHtml(value: unknown, sanitize?: HtmlSanitizeConfig): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst html = String(value)\n\t\tif (!sanitize) return html\n\t\treturn Formatter.sanitizeHtml(html, sanitize)\n\t}\n\n\t/**\n\t * Allowlist-based HTML sanitizer. Strips tags and attributes not in the config.\n\t * Handles self-closing tags, nested tags, and attribute filtering.\n\t */\n\tprivate static sanitizeHtml(\n\t\thtml: string,\n\t\tconfig: HtmlSanitizeConfig,\n\t): string {\n\t\tconst allowedTags = new Set(\n\t\t\t(config.allowedTags ?? []).map((t) => t.toLowerCase()),\n\t\t)\n\t\tconst allowedAttrs = config.allowedAttributes ?? {}\n\n\t\t// Match opening tags, closing tags, and self-closing tags\n\t\treturn html.replace(\n\t\t\t/<\\/?([a-zA-Z][a-zA-Z0-9]*)\\b([^>]*?)\\s*\\/?>/g,\n\t\t\t(match, tagName: string, attrsStr: string) => {\n\t\t\t\tconst tag = tagName.toLowerCase()\n\t\t\t\tif (!allowedTags.has(tag)) return \"\"\n\n\t\t\t\tconst isClosing = match.startsWith(\"</\")\n\t\t\t\tif (isClosing) return `</${tag}>`\n\n\t\t\t\tconst isSelfClosing = match.endsWith(\"/>\")\n\t\t\t\tconst tagAllowedAttrs = new Set(\n\t\t\t\t\t(allowedAttrs[tag] ?? []).map((a) => a.toLowerCase()),\n\t\t\t\t)\n\n\t\t\t\t// Parse and filter attributes\n\t\t\t\tconst filteredAttrs: string[] = []\n\t\t\t\tconst attrRegex =\n\t\t\t\t\t/([a-zA-Z_:][\\w:.-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|(\\S+)))?/g\n\t\t\t\tlet attrMatch: RegExpExecArray | null = null\n\t\t\t\twhile (true) {\n\t\t\t\t\tattrMatch = attrRegex.exec(attrsStr)\n\t\t\t\t\tif (!attrMatch) break\n\t\t\t\t\tconst attrName = attrMatch[1].toLowerCase()\n\t\t\t\t\tif (tagAllowedAttrs.has(attrName)) {\n\t\t\t\t\t\tconst attrValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4]\n\t\t\t\t\t\tif (attrValue !== undefined) {\n\t\t\t\t\t\t\tfilteredAttrs.push(`${attrName}=\"${escapeHtml(attrValue)}\"`)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilteredAttrs.push(attrName)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst attrsOut =\n\t\t\t\t\tfilteredAttrs.length > 0 ? ` ${filteredAttrs.join(\" \")}` : \"\"\n\t\t\t\treturn isSelfClosing ? `<${tag}${attrsOut} />` : `<${tag}${attrsOut}>`\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Formats the value as a mailto link.\n\t * Supports custom display text, subject, and body parameters.\n\t * Validates email format - returns escaped plain text for invalid emails.\n\t */\n\tpublic asEmail(value: unknown, options?: EmailOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst email = String(value)\n\n\t\tif (!Formatter.isValidEmail(email)) {\n\t\t\treturn escapeHtml(email)\n\t\t}\n\n\t\tconst params: string[] = []\n\t\tif (options?.subject)\n\t\t\tparams.push(`subject=${encodeURIComponent(options.subject)}`)\n\t\tif (options?.body) params.push(`body=${encodeURIComponent(options.body)}`)\n\t\tconst query = params.length > 0 ? `?${params.join(\"&\")}` : \"\"\n\t\tconst displayText = escapeHtml(options?.text ?? email)\n\n\t\treturn `<a href=\"mailto:${escapeHtml(email)}${query}\">${displayText}</a>`\n\t}\n\n\t/** Basic email format validation (covers most common patterns). */\n\tprivate static isValidEmail(email: string): boolean {\n\t\treturn /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)\n\t}\n\n\t/**\n\t * Formats the value as a hyperlink.\n\t * Detects http, https, ftp, ftps, and mailto schemes.\n\t * Prepends `http://` when no recognized scheme is present.\n\t */\n\tpublic asUrl(value: unknown, options?: UrlOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst url = String(value)\n\t\tconst href = /^(https?|ftps?|mailto):/i.test(url) ? url : `http://${url}`\n\t\tconst target = options?.target ?? \"_blank\"\n\t\tconst displayText = escapeHtml(options?.text ?? url)\n\n\t\tconst attrs: string[] = [\n\t\t\t`href=\"${escapeHtml(href)}\"`,\n\t\t\t`target=\"${escapeHtml(target)}\"`,\n\t\t]\n\t\tif (options?.rel) attrs.push(`rel=\"${escapeHtml(options.rel)}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\n\t\treturn `<a ${attrs.join(\" \")}>${displayText}</a>`\n\t}\n\n\t/**\n\t * Formats the value as an image tag.\n\t * Supports width, height, CSS class, and loading strategy attributes.\n\t */\n\tpublic asImage(value: unknown, options?: ImageOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst src = String(value)\n\t\tconst alt = options?.alt ?? \"\"\n\n\t\tconst attrs: string[] = [\n\t\t\t`src=\"${escapeHtml(src)}\"`,\n\t\t\t`alt=\"${escapeHtml(alt)}\"`,\n\t\t]\n\t\tif (options?.width != null)\n\t\t\tattrs.push(`width=\"${escapeHtml(String(options.width))}\"`)\n\t\tif (options?.height != null)\n\t\t\tattrs.push(`height=\"${escapeHtml(String(options.height))}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\t\tif (options?.loading) attrs.push(`loading=\"${escapeHtml(options.loading)}\"`)\n\n\t\treturn `<img ${attrs.join(\" \")} />`\n\t}\n\n\t/** Formats the value as a boolean using the configured booleanFormat labels. */\n\tpublic asBoolean(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn value ? this.booleanFormat[1] : this.booleanFormat[0]\n\t}\n\n\t// ─── Number & Currency ────────────────────────────────────────────\n\n\t/** Formats the value as an integer by removing decimal digits without rounding. */\n\tpublic asInteger(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst intVal = Math.trunc(num)\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tmaximumFractionDigits: 0,\n\t\t\tminimumFractionDigits: 0,\n\t\t}).format(intVal)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a decimal number. */\n\tpublic asDecimal(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a percent number with \"%\" sign. */\n\tpublic asPercent(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 0\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"percent\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a currency number using ISO 4217 codes. */\n\tpublic asCurrency(value: unknown, currency?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst code = currency ?? this.currencyCode\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"currency\",\n\t\t\tcurrency: code,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.currencyDecimalSeparator ?? this.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a scientific number (e-notation). */\n\tpublic asScientific(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\tnotation: \"scientific\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\t}\n\n\t/**\n\t * Formats the value as a number spellout (e.g. 42 -> \"forty-two\").\n\t * Supports multiple locales via the locales/ registry.\n\t */\n\tpublic asSpellout(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\n\t\tconst spellout = getSpellout(this.locale)\n\n\t\tif (num === 0) return spellout.zeroWord\n\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\t\tconst intPart = Math.trunc(absNum)\n\t\tconst decPart = absNum - intPart\n\n\t\tlet result = spellout.integerToWords(intPart)\n\n\t\tif (decPart > 0) {\n\t\t\tconst decStr = String(absNum).split(\".\")[1] ?? \"\"\n\t\t\tconst decDigits = decStr.split(\"\").map((d) => spellout.digitToWord(d))\n\t\t\tresult += ` ${spellout.pointWord} ${decDigits.join(\" \")}`\n\t\t}\n\n\t\treturn isNegative ? `${spellout.negativePrefix} ${result}` : result\n\t}\n\n\t/**\n\t * Formats the value as an ordinal number (e.g. 1 -> \"1st\", 2 -> \"2nd\").\n\t * Supports multiple locales via built-in suffix maps and custom overrides\n\t * through `Formatter.registerOrdinalSuffixes()`.\n\t */\n\tpublic asOrdinal(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = Math.trunc(normalizeNumber(value))\n\n\t\ttry {\n\t\t\tconst pr = new Intl.PluralRules(this.locale, { type: \"ordinal\" })\n\t\t\tconst rule = pr.select(num)\n\n\t\t\tconst lang = this.locale.split(\"-\")[0]\n\t\t\tconst enSuffixes = Formatter.ordinalSuffixes.en ?? { other: \"th\" }\n\t\t\tconst langSuffixes = Formatter.ordinalSuffixes[lang] ?? enSuffixes\n\t\t\tconst suffix = langSuffixes[rule] ?? langSuffixes.other ?? \"\"\n\n\t\t\treturn `${new Intl.NumberFormat(this.locale).format(num)}${suffix}`\n\t\t} catch {\n\t\t\treturn `${num}${this.getOrdinalSuffixEn(num)}`\n\t\t}\n\t}\n\n\t/** Built-in ordinal suffix registry. Extensible at runtime. */\n\tprivate static ordinalSuffixes: Record<string, OrdinalSuffixMap> = {\n\t\ten: { one: \"st\", two: \"nd\", few: \"rd\", other: \"th\" },\n\t\tvi: { other: \"\" },\n\t\tfr: { one: \"er\", other: \"e\" },\n\t\tde: { other: \".\" },\n\t\tes: { other: \".\" },\n\t\tpt: { other: \".\" },\n\t\tit: { other: \".\" },\n\t\tja: { other: \"\" },\n\t\tko: { other: \"\" },\n\t\tzh: { other: \"\" },\n\t}\n\n\t/**\n\t * Register ordinal suffixes for a language at runtime.\n\t * Keys are Intl.PluralRules ordinal categories: \"one\", \"two\", \"few\", \"other\".\n\t */\n\tpublic static registerOrdinalSuffixes(\n\t\tlang: string,\n\t\tsuffixes: OrdinalSuffixMap,\n\t): void {\n\t\tFormatter.ordinalSuffixes[lang] = suffixes\n\t}\n\n\t// ─── Date & Time ──────────────────────────────────────────────────\n\n\t/** Formats the value as a date. */\n\tpublic asDate(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.dateFormat,\n\t\t\t\"medium\",\n\t\t\t\"date\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a time. */\n\tpublic asTime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.timeFormat,\n\t\t\t\"medium\",\n\t\t\t\"time\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a datetime. */\n\tpublic asDatetime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.datetimeFormat,\n\t\t\t\"medium\",\n\t\t\t\"datetime\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Returns the value as a UNIX timestamp (seconds since epoch). */\n\tpublic asTimestamp(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\treturn String(Math.floor(date.getTime() / 1000))\n\t}\n\n\t/**\n\t * Formats the value as the time interval between a date and now in human readable form.\n\t * Uses Intl.RelativeTimeFormat (built-in in Node.js / browsers).\n\t */\n\tpublic asRelativeTime(value: unknown, referenceTime?: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\n\t\tconst date = normalizeDate(value)\n\t\tconst ref = referenceTime ? normalizeDate(referenceTime) : new Date()\n\t\tconst diffMs = date.getTime() - ref.getTime()\n\t\tconst diffSec = Math.round(diffMs / 1000)\n\n\t\tconst rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: \"auto\" })\n\n\t\tconst absSec = Math.abs(diffSec)\n\t\tif (absSec < 60) return rtf.format(diffSec, \"second\")\n\t\tif (absSec < 3600) return rtf.format(Math.round(diffSec / 60), \"minute\")\n\t\tif (absSec < 86400) return rtf.format(Math.round(diffSec / 3600), \"hour\")\n\t\tif (absSec < 2592000) return rtf.format(Math.round(diffSec / 86400), \"day\")\n\t\tif (absSec < 31536000)\n\t\t\treturn rtf.format(Math.round(diffSec / 2592000), \"month\")\n\t\treturn rtf.format(Math.round(diffSec / 31536000), \"year\")\n\t}\n\n\t/**\n\t * Represents the value as duration in human readable format.\n\t * Example: 5400 -> \"1 hour, 30 minutes\"\n\t */\n\tpublic asDuration(value: unknown, implode?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet seconds = Math.abs(normalizeNumber(value))\n\t\tconst separator = implode ?? \", \"\n\n\t\tif (seconds === 0) return this.getDurationLabel(\"second\", 0)\n\n\t\tconst units: Array<{ unit: string; divisor: number }> = [\n\t\t\t{ unit: \"year\", divisor: 31536000 },\n\t\t\t{ unit: \"month\", divisor: 2592000 },\n\t\t\t{ unit: \"day\", divisor: 86400 },\n\t\t\t{ unit: \"hour\", divisor: 3600 },\n\t\t\t{ unit: \"minute\", divisor: 60 },\n\t\t\t{ unit: \"second\", divisor: 1 },\n\t\t]\n\n\t\tconst parts: string[] = []\n\t\tfor (const { unit, divisor } of units) {\n\t\t\tif (seconds >= divisor) {\n\t\t\t\tconst count = Math.floor(seconds / divisor)\n\t\t\t\tseconds %= divisor\n\t\t\t\tparts.push(this.getDurationLabel(unit, count))\n\t\t\t}\n\t\t}\n\n\t\treturn parts.join(separator)\n\t}\n\n\t// ─── Size & Measurement ──────────────────────────────────────────\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kilobytes\"). */\n\tpublic asSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"long\")\n\t}\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kB\"). */\n\tpublic asShortSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"short\")\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 meters\"). */\n\tpublic asLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 m\"). */\n\tpublic asShortLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"short\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kilograms\"). */\n\tpublic asWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kg\"). */\n\tpublic asShortWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"short\", decimals)\n\t}\n\n\t// ─── Utility Methods ─────────────────────────────────────────────\n\n\t/**\n\t * Abbreviate a large number with locale-aware suffixes.\n\t * e.g. 1500000 -> \"1.5 Million\" (en) or \"1,5 Trieu\" (vi).\n\t */\n\tpublic asNumberShort(value: unknown, options?: NumberShortOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst absNum = Math.abs(num)\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst fallback = options?.fallback ?? \"currency\"\n\t\tconst addSpace = options?.spaceBefore ?? false\n\t\tconst config = getNumberShortConfig(this.locale)\n\n\t\tfor (const { value: threshold, suffix } of config.thresholds) {\n\t\t\tif (absNum >= threshold) {\n\t\t\t\tconst short =\n\t\t\t\t\tMath.round((num / threshold) * 10 ** decimals) / 10 ** decimals\n\t\t\t\tconst sep = addSpace && !suffix.startsWith(\" \") ? \" \" : \"\"\n\t\t\t\treturn `${this.asDecimal(short, decimals)}${sep}${suffix}`\n\t\t\t}\n\t\t}\n\n\t\tswitch (fallback) {\n\t\t\tcase \"decimal\":\n\t\t\t\treturn this.asDecimal(num, decimals)\n\t\t\tcase \"integer\":\n\t\t\t\treturn this.asInteger(num)\n\t\t\tdefault:\n\t\t\t\treturn this.asCurrency(num)\n\t\t}\n\t}\n\n\t/**\n\t * Format the GPS (great-circle) distance between two coordinates.\n\t * Returns a human-readable string with unit suffix.\n\t */\n\tpublic asGpsDistance(\n\t\tlatFrom: number,\n\t\tlonFrom: number,\n\t\tlatTo: number,\n\t\tlonTo: number,\n\t\toptions?: GpsDistanceOptions,\n\t): string {\n\t\tconst earthRadius = options?.earthRadius ?? 6_371_000\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst rawUnit = options?.unit ?? \"auto\"\n\t\tconst meters = Formatter.gpsDistance(\n\t\t\tlatFrom,\n\t\t\tlonFrom,\n\t\t\tlatTo,\n\t\t\tlonTo,\n\t\t\tearthRadius,\n\t\t)\n\n\t\tlet value: number\n\t\tlet unit: string\n\t\tif (rawUnit === \"mi\") {\n\t\t\tvalue = meters / 1609.344\n\t\t\tunit = \"mi\"\n\t\t} else if (rawUnit === \"km\") {\n\t\t\tvalue = meters / 1000\n\t\t\tunit = \"km\"\n\t\t} else if (rawUnit === \"m\") {\n\t\t\tvalue = meters\n\t\t\tunit = \"m\"\n\t\t} else {\n\t\t\t// auto: use km if >= 1000m, otherwise m\n\t\t\tif (meters >= 1000) {\n\t\t\t\tvalue = meters / 1000\n\t\t\t\tunit = \"km\"\n\t\t\t} else {\n\t\t\t\tvalue = meters\n\t\t\t\tunit = \"m\"\n\t\t\t}\n\t\t}\n\n\t\treturn `${this.asDecimal(value, decimals)} ${unit}`\n\t}\n\n\t/** Haversine formula: returns distance in meters between two GPS coordinates. */\n\tprivate static gpsDistance(\n\t\tlatitudeFrom: number,\n\t\tlongitudeFrom: number,\n\t\tlatitudeTo: number,\n\t\tlongitudeTo: number,\n\t\tearthRadius = 6_371_000,\n\t): number {\n\t\tconst toRad = (deg: number) => (deg * Math.PI) / 180\n\n\t\tconst latFrom = toRad(latitudeFrom)\n\t\tconst latTo = toRad(latitudeTo)\n\t\tconst deltaLat = toRad(latitudeTo - latitudeFrom)\n\t\tconst deltaLon = toRad(longitudeTo - longitudeFrom)\n\n\t\tconst a =\n\t\t\tMath.sin(deltaLat / 2) ** 2 +\n\t\t\tMath.cos(latFrom) * Math.cos(latTo) * Math.sin(deltaLon / 2) ** 2\n\t\tconst c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))\n\n\t\treturn earthRadius * c\n\t}\n\n\t/**\n\t * Mask a string value, showing only the first and last N characters.\n\t * Instance method with options support.\n\t */\n\tpublic asMaskedValue(value: unknown, options?: MaskOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst str = String(value)\n\t\treturn Formatter.getMaskedValue(\n\t\t\tstr,\n\t\t\toptions?.startVisible ?? 4,\n\t\t\toptions?.endVisible ?? 3,\n\t\t\toptions?.maskChar ?? \"X\",\n\t\t)\n\t}\n\n\t/** Core masking logic used by asMaskedValue(). */\n\tprivate static getMaskedValue(\n\t\tvalue: string,\n\t\tstartVisible = 4,\n\t\tendVisible = 3,\n\t\tmaskChar = \"X\",\n\t): string {\n\t\tif (!value) return \"\"\n\t\tconst len = value.length\n\n\t\tif (len <= startVisible + endVisible) return value\n\n\t\tconst start = value.slice(0, startVisible)\n\t\tconst end = value.slice(len - endVisible)\n\t\tconst masked = maskChar.repeat(len - startVisible - endVisible)\n\n\t\treturn `${start}${masked}${end}`\n\t}\n\n\t// ─── Private helpers ──────────────────────────────────────────────\n\n\t/**\n\t * Format bytes into the most appropriate size unit.\n\t * Supports both base-1024 (binary) and base-1000 (decimal).\n\t */\n\tprivate formatBytes(\n\t\tvalue: unknown,\n\t\tdecimals: number | undefined,\n\t\twidth: FormatWidth,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet bytes = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\t\tconst base = this.sizeFormatBase\n\n\t\tconst isNegative = bytes < 0\n\t\tbytes = Math.abs(bytes)\n\n\t\ttype SizeUnit = {\n\t\t\tthreshold: number\n\t\t\tlong: string\n\t\t\tshort: string\n\t\t}\n\n\t\tconst units1024: SizeUnit[] = [\n\t\t\t{ threshold: 1099511627776, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1073741824, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1048576, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1024, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units1000: SizeUnit[] = [\n\t\t\t{ threshold: 1000000000000, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1000000000, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1000000, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1000, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units = base === 1024 ? units1024 : units1000\n\n\t\tfor (const unit of units) {\n\t\t\tif (bytes >= unit.threshold && unit.threshold > 0) {\n\t\t\t\tconst val = bytes / unit.threshold\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? unit.long : unit.short\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\tconst label = width === \"long\" ? \"bytes\" : \"B\"\n\t\treturn `${sign}${Math.round(bytes)} ${label}`\n\t}\n\n\t/**\n\t * Format a measurement value (length or mass).\n\t * Automatically selects the most appropriate unit based on value magnitude.\n\t */\n\tprivate formatMeasure(\n\t\tvalue: unknown,\n\t\ttype: \"length\" | \"mass\",\n\t\twidth: FormatWidth,\n\t\tdecimals?: number,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst configs = this.getMeasureUnits(type)\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\n\t\t// Find the best-fitting unit (largest unit where value >= 1)\n\t\tfor (let i = configs.length - 1; i >= 0; i--) {\n\t\t\tconst config = configs[i]\n\t\t\tif (!config) continue\n\t\t\tif (absNum >= config.factor || i === 0) {\n\t\t\t\tconst val = absNum / config.factor\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? config.longLabel : config.shortLabel\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\treturn String(num)\n\t}\n\n\t/** Get measurement unit configs for the configured system (metric/imperial). */\n\tprivate getMeasureUnits(type: \"length\" | \"mass\"): MeasureUnitConfig[] {\n\t\tif (type === \"length\") {\n\t\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\t\treturn [\n\t\t\t\t\t{ factor: 1, longLabel: \"inches\", shortLabel: \"in\" },\n\t\t\t\t\t{ factor: 12, longLabel: \"feet\", shortLabel: \"ft\" },\n\t\t\t\t\t{ factor: 36, longLabel: \"yards\", shortLabel: \"yd\" },\n\t\t\t\t\t{ factor: 63360, longLabel: \"miles\", shortLabel: \"mi\" },\n\t\t\t\t]\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"millimeters\", shortLabel: \"mm\" },\n\t\t\t\t{ factor: 1000, longLabel: \"meters\", shortLabel: \"m\" },\n\t\t\t\t{ factor: 1000000, longLabel: \"kilometers\", shortLabel: \"km\" },\n\t\t\t]\n\t\t}\n\n\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"grains\", shortLabel: \"gr\" },\n\t\t\t\t{ factor: 437.5, longLabel: \"ounces\", shortLabel: \"oz\" },\n\t\t\t\t{ factor: 7000, longLabel: \"pounds\", shortLabel: \"lb\" },\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t{ factor: 1, longLabel: \"grams\", shortLabel: \"g\" },\n\t\t\t{ factor: 1000, longLabel: \"kilograms\", shortLabel: \"kg\" },\n\t\t\t{ factor: 1000000, longLabel: \"tons\", shortLabel: \"t\" },\n\t\t]\n\t}\n\n\t/** Format the numeric part of a result using locale-aware Intl. */\n\tprivate formatNumberPart(num: number, digits: number): string {\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: 0,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Create a locale-aware duration label using Intl unit formatting. */\n\tprivate getDurationLabel(unit: string, count: number): string {\n\t\ttry {\n\t\t\tconst intlUnit = unit === \"month\" ? \"month\" : unit\n\t\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\t\tstyle: \"unit\",\n\t\t\t\tunit: intlUnit,\n\t\t\t\tunitDisplay: \"long\",\n\t\t\t}).format(count)\n\t\t} catch {\n\t\t\tconst plural = count !== 1 ? \"s\" : \"\"\n\t\t\treturn `${count} ${unit}${plural}`\n\t\t}\n\t}\n\n\t/** English ordinal suffix fallback. */\n\tprivate getOrdinalSuffixEn(n: number): string {\n\t\tconst abs = Math.abs(n)\n\t\tconst mod100 = abs % 100\n\t\tif (mod100 >= 11 && mod100 <= 13) return \"th\"\n\t\tswitch (abs % 10) {\n\t\t\tcase 1:\n\t\t\t\treturn \"st\"\n\t\t\tcase 2:\n\t\t\t\treturn \"nd\"\n\t\t\tcase 3:\n\t\t\t\treturn \"rd\"\n\t\t\tdefault:\n\t\t\t\treturn \"th\"\n\t\t}\n\t}\n}\n","import { Formatter } from \"./formatter.js\"\nimport type { FormatterOptions } from \"./types.js\"\n\n/**\n * Global singleton Formatter instance.\n *\n * Usage: import once at app entry, configure once, then use `formatter` everywhere.\n *\n * ```ts\n * // main.ts (once)\n * import { configureFormatter } from \"@template/helpers\"\n * configureFormatter({ locale: \"vi-VN\", currencyCode: \"VND\" })\n *\n * // any-page.tsx (no setup needed)\n * import { formatter } from \"@template/helpers\"\n * formatter.asCurrency(1234567)\n * ```\n */\nlet instance = new Formatter()\n\n/** The global Formatter singleton. Ready to use after `configureFormatter()`. */\nexport const formatter = new Proxy({} as Formatter, {\n\tget(_target, prop, receiver) {\n\t\treturn Reflect.get(instance, prop, receiver)\n\t},\n})\n\n/**\n * Configure the global formatter once (typically at app bootstrap).\n * Replaces the internal instance - all existing `formatter` references\n * automatically pick up the new config via the proxy.\n */\nexport function configureFormatter(options: FormatterOptions): void {\n\tinstance = new Formatter(options)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["ones","digitWords","convert"],"sources":["../src/locales/en.ts","../src/locales/vi.ts","../src/locales/index.ts","../src/utils.ts","../src/formatter.ts","../src/global.ts"],"sourcesContent":["import type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\nconst ones = [\n\t\"\",\n\t\"one\",\n\t\"two\",\n\t\"three\",\n\t\"four\",\n\t\"five\",\n\t\"six\",\n\t\"seven\",\n\t\"eight\",\n\t\"nine\",\n\t\"ten\",\n\t\"eleven\",\n\t\"twelve\",\n\t\"thirteen\",\n\t\"fourteen\",\n\t\"fifteen\",\n\t\"sixteen\",\n\t\"seventeen\",\n\t\"eighteen\",\n\t\"nineteen\",\n]\n\nconst tens = [\n\t\"\",\n\t\"\",\n\t\"twenty\",\n\t\"thirty\",\n\t\"forty\",\n\t\"fifty\",\n\t\"sixty\",\n\t\"seventy\",\n\t\"eighty\",\n\t\"ninety\",\n]\n\nconst digitWords: Record<string, string> = {\n\t\"0\": \"zero\",\n\t\"1\": \"one\",\n\t\"2\": \"two\",\n\t\"3\": \"three\",\n\t\"4\": \"four\",\n\t\"5\": \"five\",\n\t\"6\": \"six\",\n\t\"7\": \"seven\",\n\t\"8\": \"eight\",\n\t\"9\": \"nine\",\n}\n\nfunction convert(num: number): string {\n\tif (num === 0) return \"\"\n\tif (num < 20) return ones[num] ?? \"\"\n\tif (num < 100) {\n\t\tconst t = tens[Math.floor(num / 10)] ?? \"\"\n\t\tconst o = ones[num % 10]\n\t\treturn o ? `${t}-${o}` : t\n\t}\n\tif (num < 1000) {\n\t\tconst h = ones[Math.floor(num / 100)] ?? \"\"\n\t\tconst remainder = num % 100\n\t\treturn remainder ? `${h} hundred ${convert(remainder)}` : `${h} hundred`\n\t}\n\tif (num < 1_000_000) {\n\t\tconst th = convert(Math.floor(num / 1000))\n\t\tconst remainder = num % 1000\n\t\treturn remainder ? `${th} thousand ${convert(remainder)}` : `${th} thousand`\n\t}\n\tif (num < 1_000_000_000) {\n\t\tconst m = convert(Math.floor(num / 1_000_000))\n\t\tconst remainder = num % 1_000_000\n\t\treturn remainder ? `${m} million ${convert(remainder)}` : `${m} million`\n\t}\n\tconst b = convert(Math.floor(num / 1_000_000_000))\n\tconst remainder = num % 1_000_000_000\n\treturn remainder ? `${b} billion ${convert(remainder)}` : `${b} billion`\n}\n\nexport const enSpellout: LocaleSpellout = {\n\tzeroWord: \"zero\",\n\tpointWord: \"point\",\n\tnegativePrefix: \"minus\",\n\n\tintegerToWords(n: number): string {\n\t\tif (n === 0) return \"zero\"\n\t\treturn convert(n).trim()\n\t},\n\n\tdigitToWord(digit: string): string {\n\t\treturn digitWords[digit] ?? digit\n\t},\n}\n\nexport const enNumberShort: NumberShortConfig = {\n\tthresholds: [\n\t\t{ value: 1_000_000_000_000, suffix: \" Trillion\" },\n\t\t{ value: 1_000_000_000, suffix: \" Billion\" },\n\t\t{ value: 1_000_000, suffix: \" Million\" },\n\t\t{ value: 1_000, suffix: \"K\" },\n\t],\n}\n","import type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\nconst ones = [\n\t\"\",\n\t\"m\\u1ed9t\",\n\t\"hai\",\n\t\"ba\",\n\t\"b\\u1ed1n\",\n\t\"n\\u0103m\",\n\t\"s\\u00e1u\",\n\t\"b\\u1ea3y\",\n\t\"t\\u00e1m\",\n\t\"ch\\u00edn\",\n]\n\nconst onesInTens = [\n\t\"\",\n\t\"m\\u1ed1t\",\n\t\"hai\",\n\t\"ba\",\n\t\"b\\u1ed1n\",\n\t\"l\\u0103m\", // 5 in tens position uses \"lam\" not \"nam\"\n\t\"s\\u00e1u\",\n\t\"b\\u1ea3y\",\n\t\"t\\u00e1m\",\n\t\"ch\\u00edn\",\n]\n\nconst digitWords: Record<string, string> = {\n\t\"0\": \"kh\\u00f4ng\",\n\t\"1\": \"m\\u1ed9t\",\n\t\"2\": \"hai\",\n\t\"3\": \"ba\",\n\t\"4\": \"b\\u1ed1n\",\n\t\"5\": \"n\\u0103m\",\n\t\"6\": \"s\\u00e1u\",\n\t\"7\": \"b\\u1ea3y\",\n\t\"8\": \"t\\u00e1m\",\n\t\"9\": \"ch\\u00edn\",\n}\n\n/**\n * Vietnamese number spellout following standard rules:\n * - 5 in ones position of tens => \"lam\" (not \"nam\")\n * - 1 in ones position of tens (>=20) => \"mot\" with special handling\n * - 0 in ones position of tens => \"muoi\" only (no trailing)\n * - Tens starting with 1 => \"muoi\", otherwise => \"muoi\" with prefix\n */\nfunction readTens(t: number, u: number): string {\n\tlet result = \"\"\n\n\tif (t === 1) {\n\t\tresult = \"m\\u01b0\\u1eddi\"\n\t} else {\n\t\tresult = `${ones[t]} m\\u01b0\\u01a1i`\n\t}\n\n\tif (u === 0) return result\n\tif (u === 1 && t > 1) return `${result} m\\u1ed1t`\n\tif (u === 5 && t > 0) return `${result} l\\u0103m`\n\treturn `${result} ${onesInTens[u] ?? \"\"}`\n}\n\nfunction readHundreds(h: number, t: number, u: number): string {\n\tconst result = `${ones[h]} tr\\u0103m`\n\tif (t === 0 && u === 0) return result\n\tif (t === 0) return `${result} linh ${ones[u]}`\n\treturn `${result} ${readTens(t, u)}`\n}\n\nfunction readBlock(num: number): string {\n\tif (num === 0) return \"\"\n\n\tconst h = Math.floor(num / 100)\n\tconst t = Math.floor((num % 100) / 10)\n\tconst u = num % 10\n\n\tif (h > 0) return readHundreds(h, t, u)\n\tif (t > 0) return readTens(t, u)\n\treturn ones[u] ?? \"\"\n}\n\nfunction convert(num: number): string {\n\tif (num === 0) return \"kh\\u00f4ng\"\n\n\tconst units = [\n\t\t{ value: 1_000_000_000, label: \"t\\u1ef7\" },\n\t\t{ value: 1_000_000, label: \"tri\\u1ec7u\" },\n\t\t{ value: 1_000, label: \"ngh\\u00ecn\" },\n\t\t{ value: 1, label: \"\" },\n\t]\n\n\tconst parts: string[] = []\n\tlet remaining = num\n\n\tfor (const unit of units) {\n\t\tif (remaining >= unit.value) {\n\t\t\tconst block = Math.floor(remaining / unit.value)\n\t\t\tremaining %= unit.value\n\n\t\t\tconst blockStr = readBlock(block)\n\t\t\tif (blockStr) {\n\t\t\t\tparts.push(unit.label ? `${blockStr} ${unit.label}` : blockStr)\n\t\t\t}\n\n\t\t\t// Handle leading zeros in next block (e.g. 1001 -> \"mot nghin khong tram linh mot\")\n\t\t\tif (remaining > 0 && remaining < unit.value / 10) {\n\t\t\t\t// Needs \"khong tram\" prefix if next block < 100\n\t\t\t\tif (remaining < 100 && unit.value >= 1000) {\n\t\t\t\t\tparts.push(\"kh\\u00f4ng tr\\u0103m\")\n\t\t\t\t\tif (remaining < 10) {\n\t\t\t\t\t\tparts.push(`linh ${ones[remaining]}`)\n\t\t\t\t\t\tremaining = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\" \").trim()\n}\n\nexport const viSpellout: LocaleSpellout = {\n\tzeroWord: \"kh\\u00f4ng\",\n\tpointWord: \"ph\\u1ea9y\",\n\tnegativePrefix: \"\\u00e2m\",\n\n\tintegerToWords(n: number): string {\n\t\tif (n === 0) return \"kh\\u00f4ng\"\n\t\treturn convert(n)\n\t},\n\n\tdigitToWord(digit: string): string {\n\t\treturn digitWords[digit] ?? digit\n\t},\n}\n\nexport const viNumberShort: NumberShortConfig = {\n\tthresholds: [\n\t\t{ value: 1_000_000_000_000, suffix: \" Ngh\\u00ecn T\\u1ef7\" },\n\t\t{ value: 1_000_000_000, suffix: \" T\\u1ef7\" },\n\t\t{ value: 1_000_000, suffix: \" Tri\\u1ec7u\" },\n\t\t{ value: 1_000, suffix: \" Ng\\u00e0n\" },\n\t],\n}\n","import { enNumberShort, enSpellout } from \"./en.js\"\nimport type {\n\tLocaleRegistry,\n\tLocaleSpellout,\n\tNumberShortConfig,\n\tNumberShortRegistry,\n} from \"./types.js\"\nimport { viNumberShort, viSpellout } from \"./vi.js\"\n\nexport type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\n/** Built-in spellout locale registry. */\nconst spelloutRegistry: LocaleRegistry = {\n\ten: enSpellout,\n\tvi: viSpellout,\n}\n\n/** Built-in number-short locale registry. */\nconst numberShortRegistry: NumberShortRegistry = {\n\ten: enNumberShort,\n\tvi: viNumberShort,\n}\n\n/** Get the spellout provider for a locale, falling back to English. */\nexport function getSpellout(locale: string): LocaleSpellout {\n\tconst lang = locale.split(\"-\")[0]\n\treturn spelloutRegistry[lang] ?? enSpellout\n}\n\n/** Get the number-short config for a locale, falling back to English. */\nexport function getNumberShortConfig(locale: string): NumberShortConfig {\n\tconst lang = locale.split(\"-\")[0]\n\treturn numberShortRegistry[lang] ?? enNumberShort\n}\n\n/** Register a custom spellout locale at runtime. */\nexport function registerSpellout(lang: string, impl: LocaleSpellout): void {\n\tspelloutRegistry[lang] = impl\n}\n\n/** Register a custom number-short config at runtime. */\nexport function registerNumberShort(\n\tlang: string,\n\tconfig: NumberShortConfig,\n): void {\n\tnumberShortRegistry[lang] = config\n}\n","import type { DateFormatPreset } from \"./types.js\"\n\n/**\n * Escape the 5 HTML-special characters, equivalent to PHP's htmlspecialchars().\n * No external dependency - pure string replacement.\n */\nexport function escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\")\n}\n\n/**\n * Normalize an input value into a Date object.\n * Accepts: Date, number (UNIX seconds or milliseconds), string (ISO 8601).\n */\nexport function normalizeDate(value: unknown): Date {\n\tif (value instanceof Date) return value\n\n\tif (typeof value === \"number\") {\n\t\t// Values below 1e12 are treated as seconds, otherwise milliseconds\n\t\treturn new Date(value < 1e12 ? value * 1000 : value)\n\t}\n\n\tif (typeof value === \"string\") {\n\t\tconst parsed = new Date(value)\n\t\tif (Number.isNaN(parsed.getTime())) {\n\t\t\tthrow new Error(`Cannot parse date value: \"${value}\"`)\n\t\t}\n\t\treturn parsed\n\t}\n\n\tthrow new Error(`Invalid data type for date: ${typeof value}`)\n}\n\n/**\n * Parse a date string safely, treating timezone-naive ISO strings as UTC.\n * Strings like \"2024-03-15T14:30:00\" (no Z, no +offset) get \"Z\" appended\n * to ensure consistent UTC interpretation across environments.\n * Returns an Invalid Date (NaN) for null, undefined, or empty values\n * instead of throwing - suitable for display pipelines.\n */\nexport function parseDate(\n\tdate: string | Date | null | undefined,\n): Date {\n\tif (!date) return new Date(Number.NaN)\n\tif (date instanceof Date) return date\n\tlet str = date\n\tif (typeof str === \"string\" && !str.endsWith(\"Z\") && !str.includes(\"+\") && str.includes(\"T\")) {\n\t\tstr += \"Z\"\n\t}\n\treturn new Date(str)\n}\n\n/**\n * Normalize an input value into a number.\n * Accepts: number, numeric string (with optional comma grouping), boolean.\n */\nexport function normalizeNumber(value: unknown): number {\n\tif (typeof value === \"number\") return value\n\n\tif (typeof value === \"string\") {\n\t\tconst trimmed = value.trim()\n\t\t// Strip common thousand separators before parsing\n\t\tconst cleaned = trimmed.replace(/,/g, \"\")\n\t\tconst num = Number(cleaned)\n\t\tif (Number.isNaN(num)) {\n\t\t\tthrow new Error(`Cannot parse numeric value: \"${value}\"`)\n\t\t}\n\t\treturn num\n\t}\n\n\tif (typeof value === \"boolean\") return value ? 1 : 0\n\n\tthrow new Error(`Invalid data type for number: ${typeof value}`)\n}\n\n/**\n * Convert a preset name (short/medium/long/full) to Intl.DateTimeFormatOptions.\n */\nexport function presetToDateOptions(\n\tpreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tconst dateOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { year: \"2-digit\", month: \"numeric\", day: \"numeric\" },\n\t\tmedium: { year: \"numeric\", month: \"short\", day: \"numeric\" },\n\t\tlong: { year: \"numeric\", month: \"long\", day: \"numeric\" },\n\t\tfull: { year: \"numeric\", month: \"long\", day: \"numeric\", weekday: \"long\" },\n\t}\n\n\tconst timeOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { hour: \"numeric\", minute: \"numeric\" },\n\t\tmedium: { hour: \"numeric\", minute: \"numeric\", second: \"numeric\" },\n\t\tlong: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"short\",\n\t\t},\n\t\tfull: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"long\",\n\t\t},\n\t}\n\n\tswitch (type) {\n\t\tcase \"date\":\n\t\t\treturn dateOptions[preset] ?? dateOptions.medium\n\t\tcase \"time\":\n\t\t\treturn timeOptions[preset] ?? timeOptions.medium\n\t\tcase \"datetime\":\n\t\t\treturn {\n\t\t\t\t...(dateOptions[preset] ?? dateOptions.medium),\n\t\t\t\t...(timeOptions[preset] ?? timeOptions.medium),\n\t\t\t}\n\t}\n}\n\n/**\n * Resolve a format value: string preset -> Intl options, object -> use directly.\n */\nexport function resolveDateFormat(\n\tformat: string | Intl.DateTimeFormatOptions | undefined,\n\tdefaultPreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tif (!format) return presetToDateOptions(defaultPreset, type)\n\tif (typeof format === \"object\") return format\n\treturn presetToDateOptions(format as DateFormatPreset, type)\n}\n\n/**\n * Replace locale-default separators with custom ones in a formatted string.\n * Uses temporary placeholders to avoid replacement collisions.\n */\nexport function applyCustomSeparators(\n\tformatted: string,\n\tlocale: string,\n\tcustomDecimal?: string | null,\n\tcustomThousand?: string | null,\n): string {\n\tif (customDecimal == null && customThousand == null) return formatted\n\n\t// Detect locale-default separators\n\tconst parts = new Intl.NumberFormat(locale).formatToParts(1234567.89)\n\tconst localeDecimal = parts.find((p) => p.type === \"decimal\")?.value ?? \".\"\n\tconst localeGroup = parts.find((p) => p.type === \"group\")?.value ?? \",\"\n\n\tlet result = formatted\n\n\t// Temporary placeholders to prevent collision during replacement\n\tconst PLACEHOLDER_DEC = \"\\x01\"\n\tconst PLACEHOLDER_GRP = \"\\x02\"\n\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(localeDecimal, PLACEHOLDER_DEC)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(localeGroup, PLACEHOLDER_GRP)\n\t}\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_DEC, customDecimal)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_GRP, customThousand)\n\t}\n\n\treturn result\n}\n","import { getNumberShortConfig, getSpellout } from \"./locales/index.js\"\nimport type {\n\tEmailOptions,\n\tFormatterOptions,\n\tFormatWidth,\n\tGpsDistanceOptions,\n\tHtmlSanitizeConfig,\n\tImageOptions,\n\tMaskOptions,\n\tMeasureUnitConfig,\n\tNumberShortOptions,\n\tOrdinalSuffixMap,\n\tParagraphOptions,\n\tUnitSystem,\n\tUrlOptions,\n} from \"./types.js\"\nimport {\n\tapplyCustomSeparators,\n\tescapeHtml,\n\tnormalizeDate,\n\tnormalizeNumber,\n\tresolveDateFormat,\n} from \"./utils.js\"\n\n/** Returns true for null or undefined only. */\nfunction isNullish(value: unknown): value is null | undefined {\n\treturn value === null || value === undefined\n}\n\n/** Returns true for null, undefined, or empty/whitespace-only strings. */\nfunction isBlank(value: unknown): value is null | undefined {\n\tif (value === null || value === undefined) return true\n\tif (typeof value === \"string\" && value.trim() === \"\") return true\n\treturn false\n}\n\n/**\n * TypeScript port of yii\\i18n\\Formatter.\n *\n * Uses only built-in Intl APIs - zero external dependencies.\n * Supports: strings, HTML, numbers, currency, dates, times,\n * file sizes, measurement units, and more.\n */\nexport class Formatter {\n\tpublic locale: string\n\tpublic timeZone: string\n\tpublic defaultTimeZone: string\n\tpublic dateFormat: string | Intl.DateTimeFormatOptions\n\tpublic timeFormat: string | Intl.DateTimeFormatOptions\n\tpublic datetimeFormat: string | Intl.DateTimeFormatOptions\n\tpublic booleanFormat: [string, string]\n\tpublic nullDisplay: string\n\tpublic currencyCode: string\n\tpublic decimalSeparator: string | null\n\tpublic thousandSeparator: string | null\n\tpublic currencyDecimalSeparator: string | null\n\tpublic sizeFormatBase: 1024 | 1000\n\tpublic systemOfUnits: UnitSystem\n\tpublic defaultDecimalDigits: number | null\n\n\tconstructor(options: FormatterOptions = {}) {\n\t\tthis.locale = options.locale ?? \"en-US\"\n\t\tthis.timeZone = options.timeZone ?? \"UTC\"\n\t\tthis.defaultTimeZone = options.defaultTimeZone ?? \"UTC\"\n\t\tthis.dateFormat = options.dateFormat ?? \"medium\"\n\t\tthis.timeFormat = options.timeFormat ?? \"medium\"\n\t\tthis.datetimeFormat = options.datetimeFormat ?? \"medium\"\n\t\tthis.booleanFormat = options.booleanFormat ?? [\"No\", \"Yes\"]\n\t\tthis.nullDisplay = options.nullDisplay ?? \"(not set)\"\n\t\tthis.currencyCode = options.currencyCode ?? \"USD\"\n\t\tthis.decimalSeparator = options.decimalSeparator ?? null\n\t\tthis.thousandSeparator = options.thousandSeparator ?? null\n\t\tthis.currencyDecimalSeparator = options.currencyDecimalSeparator ?? null\n\t\tthis.sizeFormatBase = options.sizeFormatBase ?? 1024\n\t\tthis.systemOfUnits = options.systemOfUnits ?? \"metric\"\n\t\tthis.defaultDecimalDigits = options.defaultDecimalDigits ?? null\n\t}\n\n\t// ─── Generic dispatch ─────────────────────────────────────────────\n\n\t/**\n\t * Format a value by type name, like Yii2's `$formatter->format($value, 'date')`.\n\t * Supports both string and tuple `[formatName, ...params]` signatures.\n\t */\n\tpublic format(value: unknown, type: string | [string, ...unknown[]]): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\n\t\tconst formatName = Array.isArray(type) ? type[0] : type\n\t\tconst params = Array.isArray(type) ? type.slice(1) : []\n\t\tconst methodName = `as${formatName.charAt(0).toUpperCase()}${formatName.slice(1)}`\n\n\t\tconst method = (this as Record<string, unknown>)[methodName]\n\t\tif (typeof method === \"function\") {\n\t\t\treturn (method as (...args: unknown[]) => string).call(\n\t\t\t\tthis,\n\t\t\t\tvalue,\n\t\t\t\t...params,\n\t\t\t)\n\t\t}\n\n\t\tthrow new Error(`Unknown format type: ${formatName}`)\n\t}\n\n\t// ─── String & HTML ────────────────────────────────────────────────\n\n\t/** Returns the value as-is without any formatting. */\n\tpublic asRaw(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn String(value)\n\t}\n\n\t/** Formats the value as HTML-encoded plain text. */\n\tpublic asText(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn escapeHtml(String(value))\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text with newlines converted to `<br />`.\n\t * Handles all line-ending variants: `\\r\\n` (Windows), `\\r` (old Mac), `\\n` (Unix).\n\t * Consecutive newlines produce multiple `<br />` tags.\n\t */\n\tpublic asNtext(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst escaped = escapeHtml(String(value))\n\t\treturn escaped.replace(/\\r\\n/g, \"<br />\").replace(/[\\r\\n]/g, \"<br />\")\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text paragraphs (split by double newlines).\n\t * Supports configurable wrapper tag and inline line-break conversion.\n\t */\n\tpublic asParagraphs(value: unknown, options?: ParagraphOptions): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst tag = options?.tag ?? \"p\"\n\t\tconst lineBreaks = options?.lineBreaks ?? false\n\t\tconst text = String(value)\n\t\t// Normalize line endings before splitting\n\t\tconst normalized = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\")\n\t\tconst paragraphs = normalized.split(/\\n\\s*\\n/)\n\t\treturn paragraphs\n\t\t\t.map((p) => {\n\t\t\t\tlet content = escapeHtml(p.trim())\n\t\t\t\tif (lineBreaks) {\n\t\t\t\t\tcontent = content.replace(/\\n/g, \"<br />\")\n\t\t\t\t}\n\t\t\t\treturn `<${tag}>${content}</${tag}>`\n\t\t\t})\n\t\t\t.filter((p) => p !== `<${tag}></${tag}>`)\n\t\t\t.join(\"\\n\")\n\t}\n\n\t/**\n\t * Returns the value as HTML text.\n\t * When a sanitize config is provided, only allowed tags and attributes are kept.\n\t * Without config, the value is returned as-is (caller is responsible for safety).\n\t */\n\tpublic asHtml(value: unknown, sanitize?: HtmlSanitizeConfig): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst html = String(value)\n\t\tif (!sanitize) return html\n\t\treturn Formatter.sanitizeHtml(html, sanitize)\n\t}\n\n\t/**\n\t * Allowlist-based HTML sanitizer. Strips tags and attributes not in the config.\n\t * Handles self-closing tags, nested tags, and attribute filtering.\n\t */\n\tprivate static sanitizeHtml(\n\t\thtml: string,\n\t\tconfig: HtmlSanitizeConfig,\n\t): string {\n\t\tconst allowedTags = new Set(\n\t\t\t(config.allowedTags ?? []).map((t) => t.toLowerCase()),\n\t\t)\n\t\tconst allowedAttrs = config.allowedAttributes ?? {}\n\n\t\t// Match opening tags, closing tags, and self-closing tags\n\t\treturn html.replace(\n\t\t\t/<\\/?([a-zA-Z][a-zA-Z0-9]*)\\b([^>]*?)\\s*\\/?>/g,\n\t\t\t(match, tagName: string, attrsStr: string) => {\n\t\t\t\tconst tag = tagName.toLowerCase()\n\t\t\t\tif (!allowedTags.has(tag)) return \"\"\n\n\t\t\t\tconst isClosing = match.startsWith(\"</\")\n\t\t\t\tif (isClosing) return `</${tag}>`\n\n\t\t\t\tconst isSelfClosing = match.endsWith(\"/>\")\n\t\t\t\tconst tagAllowedAttrs = new Set(\n\t\t\t\t\t(allowedAttrs[tag] ?? []).map((a) => a.toLowerCase()),\n\t\t\t\t)\n\n\t\t\t\t// Parse and filter attributes\n\t\t\t\tconst filteredAttrs: string[] = []\n\t\t\t\tconst attrRegex =\n\t\t\t\t\t/([a-zA-Z_:][\\w:.-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|(\\S+)))?/g\n\t\t\t\tlet attrMatch: RegExpExecArray | null = null\n\t\t\t\twhile (true) {\n\t\t\t\t\tattrMatch = attrRegex.exec(attrsStr)\n\t\t\t\t\tif (!attrMatch) break\n\t\t\t\t\tconst attrName = attrMatch[1].toLowerCase()\n\t\t\t\t\tif (tagAllowedAttrs.has(attrName)) {\n\t\t\t\t\t\tconst attrValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4]\n\t\t\t\t\t\tif (attrValue !== undefined) {\n\t\t\t\t\t\t\tfilteredAttrs.push(`${attrName}=\"${escapeHtml(attrValue)}\"`)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilteredAttrs.push(attrName)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst attrsOut =\n\t\t\t\t\tfilteredAttrs.length > 0 ? ` ${filteredAttrs.join(\" \")}` : \"\"\n\t\t\t\treturn isSelfClosing ? `<${tag}${attrsOut} />` : `<${tag}${attrsOut}>`\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Formats the value as a mailto link.\n\t * Supports custom display text, subject, and body parameters.\n\t * Validates email format - returns escaped plain text for invalid emails.\n\t */\n\tpublic asEmail(value: unknown, options?: EmailOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst email = String(value)\n\n\t\tif (!Formatter.isValidEmail(email)) {\n\t\t\treturn escapeHtml(email)\n\t\t}\n\n\t\tconst params: string[] = []\n\t\tif (options?.subject)\n\t\t\tparams.push(`subject=${encodeURIComponent(options.subject)}`)\n\t\tif (options?.body) params.push(`body=${encodeURIComponent(options.body)}`)\n\t\tconst query = params.length > 0 ? `?${params.join(\"&\")}` : \"\"\n\t\tconst displayText = escapeHtml(options?.text ?? email)\n\n\t\treturn `<a href=\"mailto:${escapeHtml(email)}${query}\">${displayText}</a>`\n\t}\n\n\t/** Basic email format validation (covers most common patterns). */\n\tprivate static isValidEmail(email: string): boolean {\n\t\treturn /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)\n\t}\n\n\t/**\n\t * Formats the value as a hyperlink.\n\t * Detects http, https, ftp, ftps, and mailto schemes.\n\t * Prepends `http://` when no recognized scheme is present.\n\t */\n\tpublic asUrl(value: unknown, options?: UrlOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst url = String(value)\n\t\tconst href = /^(https?|ftps?|mailto):/i.test(url) ? url : `http://${url}`\n\t\tconst target = options?.target ?? \"_blank\"\n\t\tconst displayText = escapeHtml(options?.text ?? url)\n\n\t\tconst attrs: string[] = [\n\t\t\t`href=\"${escapeHtml(href)}\"`,\n\t\t\t`target=\"${escapeHtml(target)}\"`,\n\t\t]\n\t\tif (options?.rel) attrs.push(`rel=\"${escapeHtml(options.rel)}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\n\t\treturn `<a ${attrs.join(\" \")}>${displayText}</a>`\n\t}\n\n\t/**\n\t * Formats the value as an image tag.\n\t * Supports width, height, CSS class, and loading strategy attributes.\n\t */\n\tpublic asImage(value: unknown, options?: ImageOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst src = String(value)\n\t\tconst alt = options?.alt ?? \"\"\n\n\t\tconst attrs: string[] = [\n\t\t\t`src=\"${escapeHtml(src)}\"`,\n\t\t\t`alt=\"${escapeHtml(alt)}\"`,\n\t\t]\n\t\tif (options?.width != null)\n\t\t\tattrs.push(`width=\"${escapeHtml(String(options.width))}\"`)\n\t\tif (options?.height != null)\n\t\t\tattrs.push(`height=\"${escapeHtml(String(options.height))}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\t\tif (options?.loading) attrs.push(`loading=\"${escapeHtml(options.loading)}\"`)\n\n\t\treturn `<img ${attrs.join(\" \")} />`\n\t}\n\n\t/** Formats the value as a boolean using the configured booleanFormat labels. */\n\tpublic asBoolean(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn value ? this.booleanFormat[1] : this.booleanFormat[0]\n\t}\n\n\t// ─── Number & Currency ────────────────────────────────────────────\n\n\t/** Formats the value as an integer by removing decimal digits without rounding. */\n\tpublic asInteger(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst intVal = Math.trunc(num)\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tmaximumFractionDigits: 0,\n\t\t\tminimumFractionDigits: 0,\n\t\t}).format(intVal)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a decimal number. */\n\tpublic asDecimal(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a percent number with \"%\" sign. */\n\tpublic asPercent(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 0\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"percent\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a currency number using ISO 4217 codes. */\n\tpublic asCurrency(value: unknown, currency?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst code = currency ?? this.currencyCode\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"currency\",\n\t\t\tcurrency: code,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.currencyDecimalSeparator ?? this.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a scientific number (e-notation). */\n\tpublic asScientific(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\tnotation: \"scientific\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\t}\n\n\t/**\n\t * Formats the value as a number spellout (e.g. 42 -> \"forty-two\").\n\t * Supports multiple locales via the locales/ registry.\n\t */\n\tpublic asSpellout(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\n\t\tconst spellout = getSpellout(this.locale)\n\n\t\tif (num === 0) return spellout.zeroWord\n\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\t\tconst intPart = Math.trunc(absNum)\n\t\tconst decPart = absNum - intPart\n\n\t\tlet result = spellout.integerToWords(intPart)\n\n\t\tif (decPart > 0) {\n\t\t\tconst decStr = String(absNum).split(\".\")[1] ?? \"\"\n\t\t\tconst decDigits = decStr.split(\"\").map((d) => spellout.digitToWord(d))\n\t\t\tresult += ` ${spellout.pointWord} ${decDigits.join(\" \")}`\n\t\t}\n\n\t\treturn isNegative ? `${spellout.negativePrefix} ${result}` : result\n\t}\n\n\t/**\n\t * Formats the value as an ordinal number (e.g. 1 -> \"1st\", 2 -> \"2nd\").\n\t * Supports multiple locales via built-in suffix maps and custom overrides\n\t * through `Formatter.registerOrdinalSuffixes()`.\n\t */\n\tpublic asOrdinal(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = Math.trunc(normalizeNumber(value))\n\n\t\ttry {\n\t\t\tconst pr = new Intl.PluralRules(this.locale, { type: \"ordinal\" })\n\t\t\tconst rule = pr.select(num)\n\n\t\t\tconst lang = this.locale.split(\"-\")[0]\n\t\t\tconst enSuffixes = Formatter.ordinalSuffixes.en ?? { other: \"th\" }\n\t\t\tconst langSuffixes = Formatter.ordinalSuffixes[lang] ?? enSuffixes\n\t\t\tconst suffix = langSuffixes[rule] ?? langSuffixes.other ?? \"\"\n\n\t\t\treturn `${new Intl.NumberFormat(this.locale).format(num)}${suffix}`\n\t\t} catch {\n\t\t\treturn `${num}${this.getOrdinalSuffixEn(num)}`\n\t\t}\n\t}\n\n\t/** Built-in ordinal suffix registry. Extensible at runtime. */\n\tprivate static ordinalSuffixes: Record<string, OrdinalSuffixMap> = {\n\t\ten: { one: \"st\", two: \"nd\", few: \"rd\", other: \"th\" },\n\t\tvi: { other: \"\" },\n\t\tfr: { one: \"er\", other: \"e\" },\n\t\tde: { other: \".\" },\n\t\tes: { other: \".\" },\n\t\tpt: { other: \".\" },\n\t\tit: { other: \".\" },\n\t\tja: { other: \"\" },\n\t\tko: { other: \"\" },\n\t\tzh: { other: \"\" },\n\t}\n\n\t/**\n\t * Register ordinal suffixes for a language at runtime.\n\t * Keys are Intl.PluralRules ordinal categories: \"one\", \"two\", \"few\", \"other\".\n\t */\n\tpublic static registerOrdinalSuffixes(\n\t\tlang: string,\n\t\tsuffixes: OrdinalSuffixMap,\n\t): void {\n\t\tFormatter.ordinalSuffixes[lang] = suffixes\n\t}\n\n\t// ─── Date & Time ──────────────────────────────────────────────────\n\n\t/** Formats the value as a date. */\n\tpublic asDate(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.dateFormat,\n\t\t\t\"medium\",\n\t\t\t\"date\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a time. */\n\tpublic asTime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.timeFormat,\n\t\t\t\"medium\",\n\t\t\t\"time\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a datetime. */\n\tpublic asDatetime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.datetimeFormat,\n\t\t\t\"medium\",\n\t\t\t\"datetime\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Returns the value as a UNIX timestamp (seconds since epoch). */\n\tpublic asTimestamp(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\treturn String(Math.floor(date.getTime() / 1000))\n\t}\n\n\t/**\n\t * Formats the value as the time interval between a date and now in human readable form.\n\t * Uses Intl.RelativeTimeFormat (built-in in Node.js / browsers).\n\t */\n\tpublic asRelativeTime(value: unknown, referenceTime?: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\n\t\tconst date = normalizeDate(value)\n\t\tconst ref = referenceTime ? normalizeDate(referenceTime) : new Date()\n\t\tconst diffMs = date.getTime() - ref.getTime()\n\t\tconst diffSec = Math.round(diffMs / 1000)\n\n\t\tconst rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: \"auto\" })\n\n\t\tconst absSec = Math.abs(diffSec)\n\t\tif (absSec < 60) return rtf.format(diffSec, \"second\")\n\t\tif (absSec < 3600) return rtf.format(Math.round(diffSec / 60), \"minute\")\n\t\tif (absSec < 86400) return rtf.format(Math.round(diffSec / 3600), \"hour\")\n\t\tif (absSec < 2592000) return rtf.format(Math.round(diffSec / 86400), \"day\")\n\t\tif (absSec < 31536000)\n\t\t\treturn rtf.format(Math.round(diffSec / 2592000), \"month\")\n\t\treturn rtf.format(Math.round(diffSec / 31536000), \"year\")\n\t}\n\n\t/**\n\t * Represents the value as duration in human readable format.\n\t * Example: 5400 -> \"1 hour, 30 minutes\"\n\t */\n\tpublic asDuration(value: unknown, implode?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet seconds = Math.abs(normalizeNumber(value))\n\t\tconst separator = implode ?? \", \"\n\n\t\tif (seconds === 0) return this.getDurationLabel(\"second\", 0)\n\n\t\tconst units: Array<{ unit: string; divisor: number }> = [\n\t\t\t{ unit: \"year\", divisor: 31536000 },\n\t\t\t{ unit: \"month\", divisor: 2592000 },\n\t\t\t{ unit: \"day\", divisor: 86400 },\n\t\t\t{ unit: \"hour\", divisor: 3600 },\n\t\t\t{ unit: \"minute\", divisor: 60 },\n\t\t\t{ unit: \"second\", divisor: 1 },\n\t\t]\n\n\t\tconst parts: string[] = []\n\t\tfor (const { unit, divisor } of units) {\n\t\t\tif (seconds >= divisor) {\n\t\t\t\tconst count = Math.floor(seconds / divisor)\n\t\t\t\tseconds %= divisor\n\t\t\t\tparts.push(this.getDurationLabel(unit, count))\n\t\t\t}\n\t\t}\n\n\t\treturn parts.join(separator)\n\t}\n\n\t// ─── Size & Measurement ──────────────────────────────────────────\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kilobytes\"). */\n\tpublic asSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"long\")\n\t}\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kB\"). */\n\tpublic asShortSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"short\")\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 meters\"). */\n\tpublic asLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 m\"). */\n\tpublic asShortLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"short\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kilograms\"). */\n\tpublic asWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kg\"). */\n\tpublic asShortWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"short\", decimals)\n\t}\n\n\t// ─── Utility Methods ─────────────────────────────────────────────\n\n\t/**\n\t * Abbreviate a large number with locale-aware suffixes.\n\t * e.g. 1500000 -> \"1.5 Million\" (en) or \"1,5 Trieu\" (vi).\n\t */\n\tpublic asNumberShort(value: unknown, options?: NumberShortOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst absNum = Math.abs(num)\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst fallback = options?.fallback ?? \"currency\"\n\t\tconst addSpace = options?.spaceBefore ?? false\n\t\tconst config = getNumberShortConfig(this.locale)\n\n\t\tfor (const { value: threshold, suffix } of config.thresholds) {\n\t\t\tif (absNum >= threshold) {\n\t\t\t\tconst short =\n\t\t\t\t\tMath.round((num / threshold) * 10 ** decimals) / 10 ** decimals\n\t\t\t\tconst sep = addSpace && !suffix.startsWith(\" \") ? \" \" : \"\"\n\t\t\t\treturn `${this.asDecimal(short, decimals)}${sep}${suffix}`\n\t\t\t}\n\t\t}\n\n\t\tswitch (fallback) {\n\t\t\tcase \"decimal\":\n\t\t\t\treturn this.asDecimal(num, decimals)\n\t\t\tcase \"integer\":\n\t\t\t\treturn this.asInteger(num)\n\t\t\tdefault:\n\t\t\t\treturn this.asCurrency(num)\n\t\t}\n\t}\n\n\t/**\n\t * Format the GPS (great-circle) distance between two coordinates.\n\t * Returns a human-readable string with unit suffix.\n\t */\n\tpublic asGpsDistance(\n\t\tlatFrom: number,\n\t\tlonFrom: number,\n\t\tlatTo: number,\n\t\tlonTo: number,\n\t\toptions?: GpsDistanceOptions,\n\t): string {\n\t\tconst earthRadius = options?.earthRadius ?? 6_371_000\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst rawUnit = options?.unit ?? \"auto\"\n\t\tconst meters = Formatter.gpsDistance(\n\t\t\tlatFrom,\n\t\t\tlonFrom,\n\t\t\tlatTo,\n\t\t\tlonTo,\n\t\t\tearthRadius,\n\t\t)\n\n\t\tlet value: number\n\t\tlet unit: string\n\t\tif (rawUnit === \"mi\") {\n\t\t\tvalue = meters / 1609.344\n\t\t\tunit = \"mi\"\n\t\t} else if (rawUnit === \"km\") {\n\t\t\tvalue = meters / 1000\n\t\t\tunit = \"km\"\n\t\t} else if (rawUnit === \"m\") {\n\t\t\tvalue = meters\n\t\t\tunit = \"m\"\n\t\t} else {\n\t\t\t// auto: use km if >= 1000m, otherwise m\n\t\t\tif (meters >= 1000) {\n\t\t\t\tvalue = meters / 1000\n\t\t\t\tunit = \"km\"\n\t\t\t} else {\n\t\t\t\tvalue = meters\n\t\t\t\tunit = \"m\"\n\t\t\t}\n\t\t}\n\n\t\treturn `${this.asDecimal(value, decimals)} ${unit}`\n\t}\n\n\t/** Haversine formula: returns distance in meters between two GPS coordinates. */\n\tprivate static gpsDistance(\n\t\tlatitudeFrom: number,\n\t\tlongitudeFrom: number,\n\t\tlatitudeTo: number,\n\t\tlongitudeTo: number,\n\t\tearthRadius = 6_371_000,\n\t): number {\n\t\tconst toRad = (deg: number) => (deg * Math.PI) / 180\n\n\t\tconst latFrom = toRad(latitudeFrom)\n\t\tconst latTo = toRad(latitudeTo)\n\t\tconst deltaLat = toRad(latitudeTo - latitudeFrom)\n\t\tconst deltaLon = toRad(longitudeTo - longitudeFrom)\n\n\t\tconst a =\n\t\t\tMath.sin(deltaLat / 2) ** 2 +\n\t\t\tMath.cos(latFrom) * Math.cos(latTo) * Math.sin(deltaLon / 2) ** 2\n\t\tconst c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))\n\n\t\treturn earthRadius * c\n\t}\n\n\t/**\n\t * Mask a string value, showing only the first and last N characters.\n\t * Instance method with options support.\n\t */\n\tpublic asMaskedValue(value: unknown, options?: MaskOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst str = String(value)\n\t\treturn Formatter.getMaskedValue(\n\t\t\tstr,\n\t\t\toptions?.startVisible ?? 4,\n\t\t\toptions?.endVisible ?? 3,\n\t\t\toptions?.maskChar ?? \"X\",\n\t\t)\n\t}\n\n\t/** Core masking logic used by asMaskedValue(). */\n\tprivate static getMaskedValue(\n\t\tvalue: string,\n\t\tstartVisible = 4,\n\t\tendVisible = 3,\n\t\tmaskChar = \"X\",\n\t): string {\n\t\tif (!value) return \"\"\n\t\tconst len = value.length\n\n\t\tif (len <= startVisible + endVisible) return value\n\n\t\tconst start = value.slice(0, startVisible)\n\t\tconst end = value.slice(len - endVisible)\n\t\tconst masked = maskChar.repeat(len - startVisible - endVisible)\n\n\t\treturn `${start}${masked}${end}`\n\t}\n\n\t// ─── Private helpers ──────────────────────────────────────────────\n\n\t/**\n\t * Format bytes into the most appropriate size unit.\n\t * Supports both base-1024 (binary) and base-1000 (decimal).\n\t */\n\tprivate formatBytes(\n\t\tvalue: unknown,\n\t\tdecimals: number | undefined,\n\t\twidth: FormatWidth,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet bytes = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\t\tconst base = this.sizeFormatBase\n\n\t\tconst isNegative = bytes < 0\n\t\tbytes = Math.abs(bytes)\n\n\t\ttype SizeUnit = {\n\t\t\tthreshold: number\n\t\t\tlong: string\n\t\t\tshort: string\n\t\t}\n\n\t\tconst units1024: SizeUnit[] = [\n\t\t\t{ threshold: 1099511627776, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1073741824, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1048576, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1024, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units1000: SizeUnit[] = [\n\t\t\t{ threshold: 1000000000000, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1000000000, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1000000, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1000, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units = base === 1024 ? units1024 : units1000\n\n\t\tfor (const unit of units) {\n\t\t\tif (bytes >= unit.threshold && unit.threshold > 0) {\n\t\t\t\tconst val = bytes / unit.threshold\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? unit.long : unit.short\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\tconst label = width === \"long\" ? \"bytes\" : \"B\"\n\t\treturn `${sign}${Math.round(bytes)} ${label}`\n\t}\n\n\t/**\n\t * Format a measurement value (length or mass).\n\t * Automatically selects the most appropriate unit based on value magnitude.\n\t */\n\tprivate formatMeasure(\n\t\tvalue: unknown,\n\t\ttype: \"length\" | \"mass\",\n\t\twidth: FormatWidth,\n\t\tdecimals?: number,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst configs = this.getMeasureUnits(type)\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\n\t\t// Find the best-fitting unit (largest unit where value >= 1)\n\t\tfor (let i = configs.length - 1; i >= 0; i--) {\n\t\t\tconst config = configs[i]\n\t\t\tif (!config) continue\n\t\t\tif (absNum >= config.factor || i === 0) {\n\t\t\t\tconst val = absNum / config.factor\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? config.longLabel : config.shortLabel\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\treturn String(num)\n\t}\n\n\t/** Get measurement unit configs for the configured system (metric/imperial). */\n\tprivate getMeasureUnits(type: \"length\" | \"mass\"): MeasureUnitConfig[] {\n\t\tif (type === \"length\") {\n\t\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\t\treturn [\n\t\t\t\t\t{ factor: 1, longLabel: \"inches\", shortLabel: \"in\" },\n\t\t\t\t\t{ factor: 12, longLabel: \"feet\", shortLabel: \"ft\" },\n\t\t\t\t\t{ factor: 36, longLabel: \"yards\", shortLabel: \"yd\" },\n\t\t\t\t\t{ factor: 63360, longLabel: \"miles\", shortLabel: \"mi\" },\n\t\t\t\t]\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"millimeters\", shortLabel: \"mm\" },\n\t\t\t\t{ factor: 1000, longLabel: \"meters\", shortLabel: \"m\" },\n\t\t\t\t{ factor: 1000000, longLabel: \"kilometers\", shortLabel: \"km\" },\n\t\t\t]\n\t\t}\n\n\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"grains\", shortLabel: \"gr\" },\n\t\t\t\t{ factor: 437.5, longLabel: \"ounces\", shortLabel: \"oz\" },\n\t\t\t\t{ factor: 7000, longLabel: \"pounds\", shortLabel: \"lb\" },\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t{ factor: 1, longLabel: \"grams\", shortLabel: \"g\" },\n\t\t\t{ factor: 1000, longLabel: \"kilograms\", shortLabel: \"kg\" },\n\t\t\t{ factor: 1000000, longLabel: \"tons\", shortLabel: \"t\" },\n\t\t]\n\t}\n\n\t/** Format the numeric part of a result using locale-aware Intl. */\n\tprivate formatNumberPart(num: number, digits: number): string {\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: 0,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Create a locale-aware duration label using Intl unit formatting. */\n\tprivate getDurationLabel(unit: string, count: number): string {\n\t\ttry {\n\t\t\tconst intlUnit = unit === \"month\" ? \"month\" : unit\n\t\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\t\tstyle: \"unit\",\n\t\t\t\tunit: intlUnit,\n\t\t\t\tunitDisplay: \"long\",\n\t\t\t}).format(count)\n\t\t} catch {\n\t\t\tconst plural = count !== 1 ? \"s\" : \"\"\n\t\t\treturn `${count} ${unit}${plural}`\n\t\t}\n\t}\n\n\t/** English ordinal suffix fallback. */\n\tprivate getOrdinalSuffixEn(n: number): string {\n\t\tconst abs = Math.abs(n)\n\t\tconst mod100 = abs % 100\n\t\tif (mod100 >= 11 && mod100 <= 13) return \"th\"\n\t\tswitch (abs % 10) {\n\t\t\tcase 1:\n\t\t\t\treturn \"st\"\n\t\t\tcase 2:\n\t\t\t\treturn \"nd\"\n\t\t\tcase 3:\n\t\t\t\treturn \"rd\"\n\t\t\tdefault:\n\t\t\t\treturn \"th\"\n\t\t}\n\t}\n}\n","import { Formatter } from \"./formatter.js\"\nimport type { FormatterOptions } from \"./types.js\"\nimport { parseDate } from \"./utils.js\"\n\n/**\n * Global singleton Formatter instance.\n *\n * Usage: import once at app entry, configure once, then use `formatter` everywhere.\n *\n * ```ts\n * // main.ts (once)\n * import { configureFormatter } from \"@template/helpers\"\n * configureFormatter({ locale: \"vi-VN\", currencyCode: \"VND\" })\n *\n * // any-page.tsx (no setup needed)\n * import { formatter } from \"@template/helpers\"\n * formatter.asCurrency(1234567)\n * ```\n */\nlet instance = new Formatter()\n\n/** The global Formatter singleton. Ready to use after `configureFormatter()`. */\nexport const formatter: Formatter = new Proxy({} as Formatter, {\n\tget(_target, prop, receiver) {\n\t\treturn Reflect.get(instance, prop, receiver)\n\t},\n})\n\n/**\n * Configure the global formatter once (typically at app bootstrap).\n * Replaces the internal instance - all existing `formatter` references\n * automatically pick up the new config via the proxy.\n */\nexport function configureFormatter(options: FormatterOptions): void {\n\tinstance = new Formatter(options)\n}\n\n/**\n * Format a date value safely for display using the global formatter.\n * Returns `fallback` (default: \"\\u2014\") for null, undefined, empty, or invalid dates.\n * Handles timezone-naive ISO strings by treating them as UTC.\n */\nexport function formatDate(\n\tdate: string | Date | null | undefined,\n\tfallback = \"\\u2014\",\n): string {\n\tif (!date) return fallback\n\ttry {\n\t\tconst d = parseDate(date)\n\t\tif (Number.isNaN(d.getTime())) return fallback\n\t\treturn formatter.asDate(d)\n\t} catch {\n\t\treturn fallback\n\t}\n}\n\n/**\n * Format a value as a percentage for display using the global formatter.\n * - If the string already contains \"%\", returns it as-is.\n * - Values between 0 and 1 (exclusive) are treated as ratios (multiplied by 100).\n * - Returns `fallback` (default: \"\\u2014\") for null/undefined/unparseable values.\n */\nexport function formatPercent(\n\tvalue: number | string | null | undefined,\n\tfallback = \"\\u2014\",\n): string {\n\tif (value === null || value === undefined) return fallback\n\tif (typeof value === \"string\") {\n\t\tif (value.trim() === \"\") return fallback\n\t\tif (value.includes(\"%\")) return value\n\t\tconst num = Number(value)\n\t\tif (Number.isNaN(num)) return value || fallback\n\t\tconst pct = num > 0 && num <= 1 ? num * 100 : num\n\t\treturn `${formatter.asDecimal(pct, 2)}%`\n\t}\n\tconst pct = value > 0 && value <= 1 ? value * 100 : value\n\treturn `${formatter.asDecimal(pct, 2)}%`\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -313,6 +313,19 @@ export declare const formatter: Formatter;
|
|
|
313
313
|
* automatically pick up the new config via the proxy.
|
|
314
314
|
*/
|
|
315
315
|
export declare function configureFormatter(options: FormatterOptions): void;
|
|
316
|
+
/**
|
|
317
|
+
* Format a date value safely for display using the global formatter.
|
|
318
|
+
* Returns `fallback` (default: "\u2014") for null, undefined, empty, or invalid dates.
|
|
319
|
+
* Handles timezone-naive ISO strings by treating them as UTC.
|
|
320
|
+
*/
|
|
321
|
+
export declare function formatDate(date: string | Date | null | undefined, fallback?: string): string;
|
|
322
|
+
/**
|
|
323
|
+
* Format a value as a percentage for display using the global formatter.
|
|
324
|
+
* - If the string already contains "%", returns it as-is.
|
|
325
|
+
* - Values between 0 and 1 (exclusive) are treated as ratios (multiplied by 100).
|
|
326
|
+
* - Returns `fallback` (default: "\u2014") for null/undefined/unparseable values.
|
|
327
|
+
*/
|
|
328
|
+
export declare function formatPercent(value: number | string | null | undefined, fallback?: string): string;
|
|
316
329
|
//#endregion
|
|
317
330
|
//#region src/locales/types.d.ts
|
|
318
331
|
/**
|
|
@@ -363,6 +376,14 @@ export declare function escapeHtml(value: string): string;
|
|
|
363
376
|
* Accepts: Date, number (UNIX seconds or milliseconds), string (ISO 8601).
|
|
364
377
|
*/
|
|
365
378
|
export declare function normalizeDate(value: unknown): Date;
|
|
379
|
+
/**
|
|
380
|
+
* Parse a date string safely, treating timezone-naive ISO strings as UTC.
|
|
381
|
+
* Strings like "2024-03-15T14:30:00" (no Z, no +offset) get "Z" appended
|
|
382
|
+
* to ensure consistent UTC interpretation across environments.
|
|
383
|
+
* Returns an Invalid Date (NaN) for null, undefined, or empty values
|
|
384
|
+
* instead of throwing - suitable for display pipelines.
|
|
385
|
+
*/
|
|
386
|
+
export declare function parseDate(date: string | Date | null | undefined): Date;
|
|
366
387
|
/**
|
|
367
388
|
* Normalize an input value into a number.
|
|
368
389
|
* Accepts: number, numeric string (with optional comma grouping), boolean.
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/formatter.ts","../src/global.ts","../src/locales/types.ts","../src/locales/index.ts","../src/utils.ts"],"mappings":";;;;;UAIiB;;EAEhB;;EAGA;;EAGA;;EAGA,sBAAsB,KAAK;;EAG3B,sBAAsB,KAAK;;EAG3B,0BAA0B,KAAK;;EAG/B;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;KAIW;;KAGA;;KAUA;;KAGA;;KAGA;;UAGK;EAChB;EACA;EACA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;;UAIgB;;EAEhB;;EAEA,oBAAoB;;;KAIT,mBAAmB;;UAGd;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;;;;;;;;;;qBCzHY;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/formatter.ts","../src/global.ts","../src/locales/types.ts","../src/locales/index.ts","../src/utils.ts"],"mappings":";;;;;UAIiB;;EAEhB;;EAGA;;EAGA;;EAGA,sBAAsB,KAAK;;EAG3B,sBAAsB,KAAK;;EAG3B,0BAA0B,KAAK;;EAG/B;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;KAIW;;KAGA;;KAUA;;KAGA;;KAGA;;UAGK;EAChB;EACA;EACA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;;UAIgB;;EAEhB;;EAEA,oBAAoB;;;KAIT,mBAAmB;;UAGd;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;;;;;;;;;;qBCzHY;EACZ;EACA;EACA;EACA,qBAA4B,KAAK;EACjC,qBAA4B,KAAK;EACjC,yBAAgC,KAAK;EACrC;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eAAsB;EACtB;EAEA,YAAY,UAAS;;;;;EAwBrB,OAAc,gBAAgB;;EAsB9B,MAAa;;EAMb,OAAc;;;;;;EAUd,QAAe;;;;;EAUf,aAAoB,gBAAgB,UAAU;;;;;;EAyB9C,OAAc,gBAAgB,WAAW;;;;;iBAW1B;;;;;;EAuDf,QAAe,gBAAgB,UAAU;;iBAmB1B;;;;;;EASf,MAAa,gBAAgB,UAAU;;;;;EAqBvC,QAAe,gBAAgB,UAAU;;EAoBzC,UAAiB;;EAQjB,UAAiB;;EAmBjB,UAAiB,gBAAgB;;EAmBjC,UAAiB,gBAAgB;;EAoBjC,WAAkB,gBAAgB;;EAmBlC,aAAoB,gBAAgB;;;;;EAgBpC,WAAkB;;;;;;EA6BlB,UAAiB;;iBAoBF;;;;;SAiBD,wBACb,cACA,UAAU;;EAQX,OACC,gBACA,kBAAkB,KAAK;;EAiBxB,OACC,gBACA,kBAAkB,KAAK;;EAiBxB,WACC,gBACA,kBAAkB,KAAK;;EAiBxB,YAAmB;;;;;EAUnB,eAAsB,gBAAgB;;;;;EAwBtC,WAAkB,gBAAgB;;EA+BlC,OAAc,gBAAgB;;EAK9B,YAAmB,gBAAgB;;EAKnC,SAAgB,gBAAgB;;EAKhC,cAAqB,gBAAgB;;EAKrC,SAAgB,gBAAgB;;EAKhC,cAAqB,gBAAgB;;;;;EAUrC,cAAqB,gBAAgB,UAAU;;;;;EAgC/C,cACC,iBACA,iBACA,eACA,eACA,UAAU;;iBAuCI;;;;;EA0Bf,cAAqB,gBAAgB,UAAU;;iBAYhC;;;;;UAwBP;;;;;UAwDA;;UA+BA;;UAgCA;;UAeA;;UAeA;;;;;qBC33BI,WAAW;;;;;;wBAWR,mBAAmB,SAAS;;;;;;wBAS5B,WACf,eAAe,yBACf;;;;;;;wBAkBe,cACf,2CACA;;;;;;;UC5DgB;;EAEhB,eAAe;;EAGf,YAAY;;EAGZ;;EAGA;;EAGA;;;;;;UAOgB;EAChB,YAAY;IACX;IACA;;;;;;wBCJc,YAAY,iBAAiB;;wBAM7B,qBAAqB,iBAAiB;;wBAMtC,iBAAiB,cAAc,MAAM;;wBAKrC,oBACf,cACA,QAAQ;;;;;;;wBCrCO,WAAW;;;;;wBAaX,cAAc,iBAAiB;;;;;;;;wBA0B/B,UACf,eAAe,0BACb;;;;;wBAca,gBAAgB"}
|
package/dist/index.d.mts
CHANGED
|
@@ -313,6 +313,19 @@ export declare const formatter: Formatter;
|
|
|
313
313
|
* automatically pick up the new config via the proxy.
|
|
314
314
|
*/
|
|
315
315
|
export declare function configureFormatter(options: FormatterOptions): void;
|
|
316
|
+
/**
|
|
317
|
+
* Format a date value safely for display using the global formatter.
|
|
318
|
+
* Returns `fallback` (default: "\u2014") for null, undefined, empty, or invalid dates.
|
|
319
|
+
* Handles timezone-naive ISO strings by treating them as UTC.
|
|
320
|
+
*/
|
|
321
|
+
export declare function formatDate(date: string | Date | null | undefined, fallback?: string): string;
|
|
322
|
+
/**
|
|
323
|
+
* Format a value as a percentage for display using the global formatter.
|
|
324
|
+
* - If the string already contains "%", returns it as-is.
|
|
325
|
+
* - Values between 0 and 1 (exclusive) are treated as ratios (multiplied by 100).
|
|
326
|
+
* - Returns `fallback` (default: "\u2014") for null/undefined/unparseable values.
|
|
327
|
+
*/
|
|
328
|
+
export declare function formatPercent(value: number | string | null | undefined, fallback?: string): string;
|
|
316
329
|
//#endregion
|
|
317
330
|
//#region src/locales/types.d.ts
|
|
318
331
|
/**
|
|
@@ -363,6 +376,14 @@ export declare function escapeHtml(value: string): string;
|
|
|
363
376
|
* Accepts: Date, number (UNIX seconds or milliseconds), string (ISO 8601).
|
|
364
377
|
*/
|
|
365
378
|
export declare function normalizeDate(value: unknown): Date;
|
|
379
|
+
/**
|
|
380
|
+
* Parse a date string safely, treating timezone-naive ISO strings as UTC.
|
|
381
|
+
* Strings like "2024-03-15T14:30:00" (no Z, no +offset) get "Z" appended
|
|
382
|
+
* to ensure consistent UTC interpretation across environments.
|
|
383
|
+
* Returns an Invalid Date (NaN) for null, undefined, or empty values
|
|
384
|
+
* instead of throwing - suitable for display pipelines.
|
|
385
|
+
*/
|
|
386
|
+
export declare function parseDate(date: string | Date | null | undefined): Date;
|
|
366
387
|
/**
|
|
367
388
|
* Normalize an input value into a number.
|
|
368
389
|
* Accepts: number, numeric string (with optional comma grouping), boolean.
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/formatter.ts","../src/global.ts","../src/locales/types.ts","../src/locales/index.ts","../src/utils.ts"],"mappings":";;;;;UAIiB;;EAEhB;;EAGA;;EAGA;;EAGA,sBAAsB,KAAK;;EAG3B,sBAAsB,KAAK;;EAG3B,0BAA0B,KAAK;;EAG/B;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;KAIW;;KAGA;;KAUA;;KAGA;;KAGA;;UAGK;EAChB;EACA;EACA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;;UAIgB;;EAEhB;;EAEA,oBAAoB;;;KAIT,mBAAmB;;UAGd;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;;;;;;;;;;qBCzHY;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/formatter.ts","../src/global.ts","../src/locales/types.ts","../src/locales/index.ts","../src/utils.ts"],"mappings":";;;;;UAIiB;;EAEhB;;EAGA;;EAGA;;EAGA,sBAAsB,KAAK;;EAG3B,sBAAsB,KAAK;;EAG3B,0BAA0B,KAAK;;EAG/B;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;KAIW;;KAGA;;KAUA;;KAGA;;KAGA;;UAGK;EAChB;EACA;EACA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;;UAIgB;;EAEhB;;EAEA,oBAAoB;;;KAIT,mBAAmB;;UAGd;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;;UAIgB;;EAEhB;;EAEA;;EAEA;;EAEA;;;;;;;;;;;qBCzHY;EACZ;EACA;EACA;EACA,qBAA4B,KAAK;EACjC,qBAA4B,KAAK;EACjC,yBAAgC,KAAK;EACrC;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eAAsB;EACtB;EAEA,YAAY,UAAS;;;;;EAwBrB,OAAc,gBAAgB;;EAsB9B,MAAa;;EAMb,OAAc;;;;;;EAUd,QAAe;;;;;EAUf,aAAoB,gBAAgB,UAAU;;;;;;EAyB9C,OAAc,gBAAgB,WAAW;;;;;iBAW1B;;;;;;EAuDf,QAAe,gBAAgB,UAAU;;iBAmB1B;;;;;;EASf,MAAa,gBAAgB,UAAU;;;;;EAqBvC,QAAe,gBAAgB,UAAU;;EAoBzC,UAAiB;;EAQjB,UAAiB;;EAmBjB,UAAiB,gBAAgB;;EAmBjC,UAAiB,gBAAgB;;EAoBjC,WAAkB,gBAAgB;;EAmBlC,aAAoB,gBAAgB;;;;;EAgBpC,WAAkB;;;;;;EA6BlB,UAAiB;;iBAoBF;;;;;SAiBD,wBACb,cACA,UAAU;;EAQX,OACC,gBACA,kBAAkB,KAAK;;EAiBxB,OACC,gBACA,kBAAkB,KAAK;;EAiBxB,WACC,gBACA,kBAAkB,KAAK;;EAiBxB,YAAmB;;;;;EAUnB,eAAsB,gBAAgB;;;;;EAwBtC,WAAkB,gBAAgB;;EA+BlC,OAAc,gBAAgB;;EAK9B,YAAmB,gBAAgB;;EAKnC,SAAgB,gBAAgB;;EAKhC,cAAqB,gBAAgB;;EAKrC,SAAgB,gBAAgB;;EAKhC,cAAqB,gBAAgB;;;;;EAUrC,cAAqB,gBAAgB,UAAU;;;;;EAgC/C,cACC,iBACA,iBACA,eACA,eACA,UAAU;;iBAuCI;;;;;EA0Bf,cAAqB,gBAAgB,UAAU;;iBAYhC;;;;;UAwBP;;;;;UAwDA;;UA+BA;;UAgCA;;UAeA;;UAeA;;;;;qBC33BI,WAAW;;;;;;wBAWR,mBAAmB,SAAS;;;;;;wBAS5B,WACf,eAAe,yBACf;;;;;;;wBAkBe,cACf,2CACA;;;;;;;UC5DgB;;EAEhB,eAAe;;EAGf,YAAY;;EAGZ;;EAGA;;EAGA;;;;;;UAOgB;EAChB,YAAY;IACX;IACA;;;;;;wBCJc,YAAY,iBAAiB;;wBAM7B,qBAAqB,iBAAiB;;wBAMtC,iBAAiB,cAAc,MAAM;;wBAKrC,oBACf,cACA,QAAQ;;;;;;;wBCrCO,WAAW;;;;;wBAaX,cAAc,iBAAiB;;;;;;;;wBA0B/B,UACf,eAAe,0BACb;;;;;wBAca,gBAAgB"}
|
package/dist/index.mjs
CHANGED
|
@@ -294,6 +294,20 @@ function normalizeDate(value) {
|
|
|
294
294
|
throw new Error(`Invalid data type for date: ${typeof value}`);
|
|
295
295
|
}
|
|
296
296
|
/**
|
|
297
|
+
* Parse a date string safely, treating timezone-naive ISO strings as UTC.
|
|
298
|
+
* Strings like "2024-03-15T14:30:00" (no Z, no +offset) get "Z" appended
|
|
299
|
+
* to ensure consistent UTC interpretation across environments.
|
|
300
|
+
* Returns an Invalid Date (NaN) for null, undefined, or empty values
|
|
301
|
+
* instead of throwing - suitable for display pipelines.
|
|
302
|
+
*/
|
|
303
|
+
function parseDate(date) {
|
|
304
|
+
if (!date) return /* @__PURE__ */ new Date(NaN);
|
|
305
|
+
if (date instanceof Date) return date;
|
|
306
|
+
let str = date;
|
|
307
|
+
if (typeof str === "string" && !str.endsWith("Z") && !str.includes("+") && str.includes("T")) str += "Z";
|
|
308
|
+
return new Date(str);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
297
311
|
* Normalize an input value into a number.
|
|
298
312
|
* Accepts: number, numeric string (with optional comma grouping), boolean.
|
|
299
313
|
*/
|
|
@@ -1141,7 +1155,41 @@ const formatter = new Proxy({}, { get(_target, prop, receiver) {
|
|
|
1141
1155
|
function configureFormatter(options) {
|
|
1142
1156
|
instance = new Formatter(options);
|
|
1143
1157
|
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Format a date value safely for display using the global formatter.
|
|
1160
|
+
* Returns `fallback` (default: "\u2014") for null, undefined, empty, or invalid dates.
|
|
1161
|
+
* Handles timezone-naive ISO strings by treating them as UTC.
|
|
1162
|
+
*/
|
|
1163
|
+
function formatDate(date, fallback = "—") {
|
|
1164
|
+
if (!date) return fallback;
|
|
1165
|
+
try {
|
|
1166
|
+
const d = parseDate(date);
|
|
1167
|
+
if (Number.isNaN(d.getTime())) return fallback;
|
|
1168
|
+
return formatter.asDate(d);
|
|
1169
|
+
} catch {
|
|
1170
|
+
return fallback;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Format a value as a percentage for display using the global formatter.
|
|
1175
|
+
* - If the string already contains "%", returns it as-is.
|
|
1176
|
+
* - Values between 0 and 1 (exclusive) are treated as ratios (multiplied by 100).
|
|
1177
|
+
* - Returns `fallback` (default: "\u2014") for null/undefined/unparseable values.
|
|
1178
|
+
*/
|
|
1179
|
+
function formatPercent(value, fallback = "—") {
|
|
1180
|
+
if (value === null || value === void 0) return fallback;
|
|
1181
|
+
if (typeof value === "string") {
|
|
1182
|
+
if (value.trim() === "") return fallback;
|
|
1183
|
+
if (value.includes("%")) return value;
|
|
1184
|
+
const num = Number(value);
|
|
1185
|
+
if (Number.isNaN(num)) return value || fallback;
|
|
1186
|
+
const pct = num > 0 && num <= 1 ? num * 100 : num;
|
|
1187
|
+
return `${formatter.asDecimal(pct, 2)}%`;
|
|
1188
|
+
}
|
|
1189
|
+
const pct = value > 0 && value <= 1 ? value * 100 : value;
|
|
1190
|
+
return `${formatter.asDecimal(pct, 2)}%`;
|
|
1191
|
+
}
|
|
1144
1192
|
//#endregion
|
|
1145
|
-
export { Formatter, configureFormatter, escapeHtml, formatter, getNumberShortConfig, getSpellout, normalizeDate, normalizeNumber, registerNumberShort, registerSpellout };
|
|
1193
|
+
export { Formatter, configureFormatter, escapeHtml, formatDate, formatPercent, formatter, getNumberShortConfig, getSpellout, normalizeDate, normalizeNumber, parseDate, registerNumberShort, registerSpellout };
|
|
1146
1194
|
|
|
1147
1195
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["ones","digitWords","convert"],"sources":["../src/locales/en.ts","../src/locales/vi.ts","../src/locales/index.ts","../src/utils.ts","../src/formatter.ts","../src/global.ts"],"sourcesContent":["import type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\nconst ones = [\n\t\"\",\n\t\"one\",\n\t\"two\",\n\t\"three\",\n\t\"four\",\n\t\"five\",\n\t\"six\",\n\t\"seven\",\n\t\"eight\",\n\t\"nine\",\n\t\"ten\",\n\t\"eleven\",\n\t\"twelve\",\n\t\"thirteen\",\n\t\"fourteen\",\n\t\"fifteen\",\n\t\"sixteen\",\n\t\"seventeen\",\n\t\"eighteen\",\n\t\"nineteen\",\n]\n\nconst tens = [\n\t\"\",\n\t\"\",\n\t\"twenty\",\n\t\"thirty\",\n\t\"forty\",\n\t\"fifty\",\n\t\"sixty\",\n\t\"seventy\",\n\t\"eighty\",\n\t\"ninety\",\n]\n\nconst digitWords: Record<string, string> = {\n\t\"0\": \"zero\",\n\t\"1\": \"one\",\n\t\"2\": \"two\",\n\t\"3\": \"three\",\n\t\"4\": \"four\",\n\t\"5\": \"five\",\n\t\"6\": \"six\",\n\t\"7\": \"seven\",\n\t\"8\": \"eight\",\n\t\"9\": \"nine\",\n}\n\nfunction convert(num: number): string {\n\tif (num === 0) return \"\"\n\tif (num < 20) return ones[num] ?? \"\"\n\tif (num < 100) {\n\t\tconst t = tens[Math.floor(num / 10)] ?? \"\"\n\t\tconst o = ones[num % 10]\n\t\treturn o ? `${t}-${o}` : t\n\t}\n\tif (num < 1000) {\n\t\tconst h = ones[Math.floor(num / 100)] ?? \"\"\n\t\tconst remainder = num % 100\n\t\treturn remainder ? `${h} hundred ${convert(remainder)}` : `${h} hundred`\n\t}\n\tif (num < 1_000_000) {\n\t\tconst th = convert(Math.floor(num / 1000))\n\t\tconst remainder = num % 1000\n\t\treturn remainder ? `${th} thousand ${convert(remainder)}` : `${th} thousand`\n\t}\n\tif (num < 1_000_000_000) {\n\t\tconst m = convert(Math.floor(num / 1_000_000))\n\t\tconst remainder = num % 1_000_000\n\t\treturn remainder ? `${m} million ${convert(remainder)}` : `${m} million`\n\t}\n\tconst b = convert(Math.floor(num / 1_000_000_000))\n\tconst remainder = num % 1_000_000_000\n\treturn remainder ? `${b} billion ${convert(remainder)}` : `${b} billion`\n}\n\nexport const enSpellout: LocaleSpellout = {\n\tzeroWord: \"zero\",\n\tpointWord: \"point\",\n\tnegativePrefix: \"minus\",\n\n\tintegerToWords(n: number): string {\n\t\tif (n === 0) return \"zero\"\n\t\treturn convert(n).trim()\n\t},\n\n\tdigitToWord(digit: string): string {\n\t\treturn digitWords[digit] ?? digit\n\t},\n}\n\nexport const enNumberShort: NumberShortConfig = {\n\tthresholds: [\n\t\t{ value: 1_000_000_000_000, suffix: \" Trillion\" },\n\t\t{ value: 1_000_000_000, suffix: \" Billion\" },\n\t\t{ value: 1_000_000, suffix: \" Million\" },\n\t\t{ value: 1_000, suffix: \"K\" },\n\t],\n}\n","import type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\nconst ones = [\n\t\"\",\n\t\"m\\u1ed9t\",\n\t\"hai\",\n\t\"ba\",\n\t\"b\\u1ed1n\",\n\t\"n\\u0103m\",\n\t\"s\\u00e1u\",\n\t\"b\\u1ea3y\",\n\t\"t\\u00e1m\",\n\t\"ch\\u00edn\",\n]\n\nconst onesInTens = [\n\t\"\",\n\t\"m\\u1ed1t\",\n\t\"hai\",\n\t\"ba\",\n\t\"b\\u1ed1n\",\n\t\"l\\u0103m\", // 5 in tens position uses \"lam\" not \"nam\"\n\t\"s\\u00e1u\",\n\t\"b\\u1ea3y\",\n\t\"t\\u00e1m\",\n\t\"ch\\u00edn\",\n]\n\nconst digitWords: Record<string, string> = {\n\t\"0\": \"kh\\u00f4ng\",\n\t\"1\": \"m\\u1ed9t\",\n\t\"2\": \"hai\",\n\t\"3\": \"ba\",\n\t\"4\": \"b\\u1ed1n\",\n\t\"5\": \"n\\u0103m\",\n\t\"6\": \"s\\u00e1u\",\n\t\"7\": \"b\\u1ea3y\",\n\t\"8\": \"t\\u00e1m\",\n\t\"9\": \"ch\\u00edn\",\n}\n\n/**\n * Vietnamese number spellout following standard rules:\n * - 5 in ones position of tens => \"lam\" (not \"nam\")\n * - 1 in ones position of tens (>=20) => \"mot\" with special handling\n * - 0 in ones position of tens => \"muoi\" only (no trailing)\n * - Tens starting with 1 => \"muoi\", otherwise => \"muoi\" with prefix\n */\nfunction readTens(t: number, u: number): string {\n\tlet result = \"\"\n\n\tif (t === 1) {\n\t\tresult = \"m\\u01b0\\u1eddi\"\n\t} else {\n\t\tresult = `${ones[t]} m\\u01b0\\u01a1i`\n\t}\n\n\tif (u === 0) return result\n\tif (u === 1 && t > 1) return `${result} m\\u1ed1t`\n\tif (u === 5 && t > 0) return `${result} l\\u0103m`\n\treturn `${result} ${onesInTens[u] ?? \"\"}`\n}\n\nfunction readHundreds(h: number, t: number, u: number): string {\n\tconst result = `${ones[h]} tr\\u0103m`\n\tif (t === 0 && u === 0) return result\n\tif (t === 0) return `${result} linh ${ones[u]}`\n\treturn `${result} ${readTens(t, u)}`\n}\n\nfunction readBlock(num: number): string {\n\tif (num === 0) return \"\"\n\n\tconst h = Math.floor(num / 100)\n\tconst t = Math.floor((num % 100) / 10)\n\tconst u = num % 10\n\n\tif (h > 0) return readHundreds(h, t, u)\n\tif (t > 0) return readTens(t, u)\n\treturn ones[u] ?? \"\"\n}\n\nfunction convert(num: number): string {\n\tif (num === 0) return \"kh\\u00f4ng\"\n\n\tconst units = [\n\t\t{ value: 1_000_000_000, label: \"t\\u1ef7\" },\n\t\t{ value: 1_000_000, label: \"tri\\u1ec7u\" },\n\t\t{ value: 1_000, label: \"ngh\\u00ecn\" },\n\t\t{ value: 1, label: \"\" },\n\t]\n\n\tconst parts: string[] = []\n\tlet remaining = num\n\n\tfor (const unit of units) {\n\t\tif (remaining >= unit.value) {\n\t\t\tconst block = Math.floor(remaining / unit.value)\n\t\t\tremaining %= unit.value\n\n\t\t\tconst blockStr = readBlock(block)\n\t\t\tif (blockStr) {\n\t\t\t\tparts.push(unit.label ? `${blockStr} ${unit.label}` : blockStr)\n\t\t\t}\n\n\t\t\t// Handle leading zeros in next block (e.g. 1001 -> \"mot nghin khong tram linh mot\")\n\t\t\tif (remaining > 0 && remaining < unit.value / 10) {\n\t\t\t\t// Needs \"khong tram\" prefix if next block < 100\n\t\t\t\tif (remaining < 100 && unit.value >= 1000) {\n\t\t\t\t\tparts.push(\"kh\\u00f4ng tr\\u0103m\")\n\t\t\t\t\tif (remaining < 10) {\n\t\t\t\t\t\tparts.push(`linh ${ones[remaining]}`)\n\t\t\t\t\t\tremaining = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\" \").trim()\n}\n\nexport const viSpellout: LocaleSpellout = {\n\tzeroWord: \"kh\\u00f4ng\",\n\tpointWord: \"ph\\u1ea9y\",\n\tnegativePrefix: \"\\u00e2m\",\n\n\tintegerToWords(n: number): string {\n\t\tif (n === 0) return \"kh\\u00f4ng\"\n\t\treturn convert(n)\n\t},\n\n\tdigitToWord(digit: string): string {\n\t\treturn digitWords[digit] ?? digit\n\t},\n}\n\nexport const viNumberShort: NumberShortConfig = {\n\tthresholds: [\n\t\t{ value: 1_000_000_000_000, suffix: \" Ngh\\u00ecn T\\u1ef7\" },\n\t\t{ value: 1_000_000_000, suffix: \" T\\u1ef7\" },\n\t\t{ value: 1_000_000, suffix: \" Tri\\u1ec7u\" },\n\t\t{ value: 1_000, suffix: \" Ng\\u00e0n\" },\n\t],\n}\n","import { enNumberShort, enSpellout } from \"./en.js\"\nimport type {\n\tLocaleRegistry,\n\tLocaleSpellout,\n\tNumberShortConfig,\n\tNumberShortRegistry,\n} from \"./types.js\"\nimport { viNumberShort, viSpellout } from \"./vi.js\"\n\nexport type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\n/** Built-in spellout locale registry. */\nconst spelloutRegistry: LocaleRegistry = {\n\ten: enSpellout,\n\tvi: viSpellout,\n}\n\n/** Built-in number-short locale registry. */\nconst numberShortRegistry: NumberShortRegistry = {\n\ten: enNumberShort,\n\tvi: viNumberShort,\n}\n\n/** Get the spellout provider for a locale, falling back to English. */\nexport function getSpellout(locale: string): LocaleSpellout {\n\tconst lang = locale.split(\"-\")[0]\n\treturn spelloutRegistry[lang] ?? enSpellout\n}\n\n/** Get the number-short config for a locale, falling back to English. */\nexport function getNumberShortConfig(locale: string): NumberShortConfig {\n\tconst lang = locale.split(\"-\")[0]\n\treturn numberShortRegistry[lang] ?? enNumberShort\n}\n\n/** Register a custom spellout locale at runtime. */\nexport function registerSpellout(lang: string, impl: LocaleSpellout): void {\n\tspelloutRegistry[lang] = impl\n}\n\n/** Register a custom number-short config at runtime. */\nexport function registerNumberShort(\n\tlang: string,\n\tconfig: NumberShortConfig,\n): void {\n\tnumberShortRegistry[lang] = config\n}\n","import type { DateFormatPreset } from \"./types.js\"\n\n/**\n * Escape the 5 HTML-special characters, equivalent to PHP's htmlspecialchars().\n * No external dependency - pure string replacement.\n */\nexport function escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\")\n}\n\n/**\n * Normalize an input value into a Date object.\n * Accepts: Date, number (UNIX seconds or milliseconds), string (ISO 8601).\n */\nexport function normalizeDate(value: unknown): Date {\n\tif (value instanceof Date) return value\n\n\tif (typeof value === \"number\") {\n\t\t// Values below 1e12 are treated as seconds, otherwise milliseconds\n\t\treturn new Date(value < 1e12 ? value * 1000 : value)\n\t}\n\n\tif (typeof value === \"string\") {\n\t\tconst parsed = new Date(value)\n\t\tif (Number.isNaN(parsed.getTime())) {\n\t\t\tthrow new Error(`Cannot parse date value: \"${value}\"`)\n\t\t}\n\t\treturn parsed\n\t}\n\n\tthrow new Error(`Invalid data type for date: ${typeof value}`)\n}\n\n/**\n * Normalize an input value into a number.\n * Accepts: number, numeric string (with optional comma grouping), boolean.\n */\nexport function normalizeNumber(value: unknown): number {\n\tif (typeof value === \"number\") return value\n\n\tif (typeof value === \"string\") {\n\t\tconst trimmed = value.trim()\n\t\t// Strip common thousand separators before parsing\n\t\tconst cleaned = trimmed.replace(/,/g, \"\")\n\t\tconst num = Number(cleaned)\n\t\tif (Number.isNaN(num)) {\n\t\t\tthrow new Error(`Cannot parse numeric value: \"${value}\"`)\n\t\t}\n\t\treturn num\n\t}\n\n\tif (typeof value === \"boolean\") return value ? 1 : 0\n\n\tthrow new Error(`Invalid data type for number: ${typeof value}`)\n}\n\n/**\n * Convert a preset name (short/medium/long/full) to Intl.DateTimeFormatOptions.\n */\nexport function presetToDateOptions(\n\tpreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tconst dateOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { year: \"2-digit\", month: \"numeric\", day: \"numeric\" },\n\t\tmedium: { year: \"numeric\", month: \"short\", day: \"numeric\" },\n\t\tlong: { year: \"numeric\", month: \"long\", day: \"numeric\" },\n\t\tfull: { year: \"numeric\", month: \"long\", day: \"numeric\", weekday: \"long\" },\n\t}\n\n\tconst timeOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { hour: \"numeric\", minute: \"numeric\" },\n\t\tmedium: { hour: \"numeric\", minute: \"numeric\", second: \"numeric\" },\n\t\tlong: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"short\",\n\t\t},\n\t\tfull: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"long\",\n\t\t},\n\t}\n\n\tswitch (type) {\n\t\tcase \"date\":\n\t\t\treturn dateOptions[preset] ?? dateOptions.medium\n\t\tcase \"time\":\n\t\t\treturn timeOptions[preset] ?? timeOptions.medium\n\t\tcase \"datetime\":\n\t\t\treturn {\n\t\t\t\t...(dateOptions[preset] ?? dateOptions.medium),\n\t\t\t\t...(timeOptions[preset] ?? timeOptions.medium),\n\t\t\t}\n\t}\n}\n\n/**\n * Resolve a format value: string preset -> Intl options, object -> use directly.\n */\nexport function resolveDateFormat(\n\tformat: string | Intl.DateTimeFormatOptions | undefined,\n\tdefaultPreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tif (!format) return presetToDateOptions(defaultPreset, type)\n\tif (typeof format === \"object\") return format\n\treturn presetToDateOptions(format as DateFormatPreset, type)\n}\n\n/**\n * Replace locale-default separators with custom ones in a formatted string.\n * Uses temporary placeholders to avoid replacement collisions.\n */\nexport function applyCustomSeparators(\n\tformatted: string,\n\tlocale: string,\n\tcustomDecimal?: string | null,\n\tcustomThousand?: string | null,\n): string {\n\tif (customDecimal == null && customThousand == null) return formatted\n\n\t// Detect locale-default separators\n\tconst parts = new Intl.NumberFormat(locale).formatToParts(1234567.89)\n\tconst localeDecimal = parts.find((p) => p.type === \"decimal\")?.value ?? \".\"\n\tconst localeGroup = parts.find((p) => p.type === \"group\")?.value ?? \",\"\n\n\tlet result = formatted\n\n\t// Temporary placeholders to prevent collision during replacement\n\tconst PLACEHOLDER_DEC = \"\\x01\"\n\tconst PLACEHOLDER_GRP = \"\\x02\"\n\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(localeDecimal, PLACEHOLDER_DEC)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(localeGroup, PLACEHOLDER_GRP)\n\t}\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_DEC, customDecimal)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_GRP, customThousand)\n\t}\n\n\treturn result\n}\n","import { getNumberShortConfig, getSpellout } from \"./locales/index.js\"\nimport type {\n\tEmailOptions,\n\tFormatterOptions,\n\tFormatWidth,\n\tGpsDistanceOptions,\n\tHtmlSanitizeConfig,\n\tImageOptions,\n\tMaskOptions,\n\tMeasureUnitConfig,\n\tNumberShortOptions,\n\tOrdinalSuffixMap,\n\tParagraphOptions,\n\tUnitSystem,\n\tUrlOptions,\n} from \"./types.js\"\nimport {\n\tapplyCustomSeparators,\n\tescapeHtml,\n\tnormalizeDate,\n\tnormalizeNumber,\n\tresolveDateFormat,\n} from \"./utils.js\"\n\n/** Returns true for null or undefined only. */\nfunction isNullish(value: unknown): value is null | undefined {\n\treturn value === null || value === undefined\n}\n\n/** Returns true for null, undefined, or empty/whitespace-only strings. */\nfunction isBlank(value: unknown): value is null | undefined {\n\tif (value === null || value === undefined) return true\n\tif (typeof value === \"string\" && value.trim() === \"\") return true\n\treturn false\n}\n\n/**\n * TypeScript port of yii\\i18n\\Formatter.\n *\n * Uses only built-in Intl APIs - zero external dependencies.\n * Supports: strings, HTML, numbers, currency, dates, times,\n * file sizes, measurement units, and more.\n */\nexport class Formatter {\n\tpublic locale: string\n\tpublic timeZone: string\n\tpublic defaultTimeZone: string\n\tpublic dateFormat: string | Intl.DateTimeFormatOptions\n\tpublic timeFormat: string | Intl.DateTimeFormatOptions\n\tpublic datetimeFormat: string | Intl.DateTimeFormatOptions\n\tpublic booleanFormat: [string, string]\n\tpublic nullDisplay: string\n\tpublic currencyCode: string\n\tpublic decimalSeparator: string | null\n\tpublic thousandSeparator: string | null\n\tpublic currencyDecimalSeparator: string | null\n\tpublic sizeFormatBase: 1024 | 1000\n\tpublic systemOfUnits: UnitSystem\n\tpublic defaultDecimalDigits: number | null\n\n\tconstructor(options: FormatterOptions = {}) {\n\t\tthis.locale = options.locale ?? \"en-US\"\n\t\tthis.timeZone = options.timeZone ?? \"UTC\"\n\t\tthis.defaultTimeZone = options.defaultTimeZone ?? \"UTC\"\n\t\tthis.dateFormat = options.dateFormat ?? \"medium\"\n\t\tthis.timeFormat = options.timeFormat ?? \"medium\"\n\t\tthis.datetimeFormat = options.datetimeFormat ?? \"medium\"\n\t\tthis.booleanFormat = options.booleanFormat ?? [\"No\", \"Yes\"]\n\t\tthis.nullDisplay = options.nullDisplay ?? \"(not set)\"\n\t\tthis.currencyCode = options.currencyCode ?? \"USD\"\n\t\tthis.decimalSeparator = options.decimalSeparator ?? null\n\t\tthis.thousandSeparator = options.thousandSeparator ?? null\n\t\tthis.currencyDecimalSeparator = options.currencyDecimalSeparator ?? null\n\t\tthis.sizeFormatBase = options.sizeFormatBase ?? 1024\n\t\tthis.systemOfUnits = options.systemOfUnits ?? \"metric\"\n\t\tthis.defaultDecimalDigits = options.defaultDecimalDigits ?? null\n\t}\n\n\t// ─── Generic dispatch ─────────────────────────────────────────────\n\n\t/**\n\t * Format a value by type name, like Yii2's `$formatter->format($value, 'date')`.\n\t * Supports both string and tuple `[formatName, ...params]` signatures.\n\t */\n\tpublic format(value: unknown, type: string | [string, ...unknown[]]): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\n\t\tconst formatName = Array.isArray(type) ? type[0] : type\n\t\tconst params = Array.isArray(type) ? type.slice(1) : []\n\t\tconst methodName = `as${formatName.charAt(0).toUpperCase()}${formatName.slice(1)}`\n\n\t\tconst method = (this as Record<string, unknown>)[methodName]\n\t\tif (typeof method === \"function\") {\n\t\t\treturn (method as (...args: unknown[]) => string).call(\n\t\t\t\tthis,\n\t\t\t\tvalue,\n\t\t\t\t...params,\n\t\t\t)\n\t\t}\n\n\t\tthrow new Error(`Unknown format type: ${formatName}`)\n\t}\n\n\t// ─── String & HTML ────────────────────────────────────────────────\n\n\t/** Returns the value as-is without any formatting. */\n\tpublic asRaw(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn String(value)\n\t}\n\n\t/** Formats the value as HTML-encoded plain text. */\n\tpublic asText(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn escapeHtml(String(value))\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text with newlines converted to `<br />`.\n\t * Handles all line-ending variants: `\\r\\n` (Windows), `\\r` (old Mac), `\\n` (Unix).\n\t * Consecutive newlines produce multiple `<br />` tags.\n\t */\n\tpublic asNtext(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst escaped = escapeHtml(String(value))\n\t\treturn escaped.replace(/\\r\\n/g, \"<br />\").replace(/[\\r\\n]/g, \"<br />\")\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text paragraphs (split by double newlines).\n\t * Supports configurable wrapper tag and inline line-break conversion.\n\t */\n\tpublic asParagraphs(value: unknown, options?: ParagraphOptions): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst tag = options?.tag ?? \"p\"\n\t\tconst lineBreaks = options?.lineBreaks ?? false\n\t\tconst text = String(value)\n\t\t// Normalize line endings before splitting\n\t\tconst normalized = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\")\n\t\tconst paragraphs = normalized.split(/\\n\\s*\\n/)\n\t\treturn paragraphs\n\t\t\t.map((p) => {\n\t\t\t\tlet content = escapeHtml(p.trim())\n\t\t\t\tif (lineBreaks) {\n\t\t\t\t\tcontent = content.replace(/\\n/g, \"<br />\")\n\t\t\t\t}\n\t\t\t\treturn `<${tag}>${content}</${tag}>`\n\t\t\t})\n\t\t\t.filter((p) => p !== `<${tag}></${tag}>`)\n\t\t\t.join(\"\\n\")\n\t}\n\n\t/**\n\t * Returns the value as HTML text.\n\t * When a sanitize config is provided, only allowed tags and attributes are kept.\n\t * Without config, the value is returned as-is (caller is responsible for safety).\n\t */\n\tpublic asHtml(value: unknown, sanitize?: HtmlSanitizeConfig): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst html = String(value)\n\t\tif (!sanitize) return html\n\t\treturn Formatter.sanitizeHtml(html, sanitize)\n\t}\n\n\t/**\n\t * Allowlist-based HTML sanitizer. Strips tags and attributes not in the config.\n\t * Handles self-closing tags, nested tags, and attribute filtering.\n\t */\n\tprivate static sanitizeHtml(\n\t\thtml: string,\n\t\tconfig: HtmlSanitizeConfig,\n\t): string {\n\t\tconst allowedTags = new Set(\n\t\t\t(config.allowedTags ?? []).map((t) => t.toLowerCase()),\n\t\t)\n\t\tconst allowedAttrs = config.allowedAttributes ?? {}\n\n\t\t// Match opening tags, closing tags, and self-closing tags\n\t\treturn html.replace(\n\t\t\t/<\\/?([a-zA-Z][a-zA-Z0-9]*)\\b([^>]*?)\\s*\\/?>/g,\n\t\t\t(match, tagName: string, attrsStr: string) => {\n\t\t\t\tconst tag = tagName.toLowerCase()\n\t\t\t\tif (!allowedTags.has(tag)) return \"\"\n\n\t\t\t\tconst isClosing = match.startsWith(\"</\")\n\t\t\t\tif (isClosing) return `</${tag}>`\n\n\t\t\t\tconst isSelfClosing = match.endsWith(\"/>\")\n\t\t\t\tconst tagAllowedAttrs = new Set(\n\t\t\t\t\t(allowedAttrs[tag] ?? []).map((a) => a.toLowerCase()),\n\t\t\t\t)\n\n\t\t\t\t// Parse and filter attributes\n\t\t\t\tconst filteredAttrs: string[] = []\n\t\t\t\tconst attrRegex =\n\t\t\t\t\t/([a-zA-Z_:][\\w:.-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|(\\S+)))?/g\n\t\t\t\tlet attrMatch: RegExpExecArray | null = null\n\t\t\t\twhile (true) {\n\t\t\t\t\tattrMatch = attrRegex.exec(attrsStr)\n\t\t\t\t\tif (!attrMatch) break\n\t\t\t\t\tconst attrName = attrMatch[1].toLowerCase()\n\t\t\t\t\tif (tagAllowedAttrs.has(attrName)) {\n\t\t\t\t\t\tconst attrValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4]\n\t\t\t\t\t\tif (attrValue !== undefined) {\n\t\t\t\t\t\t\tfilteredAttrs.push(`${attrName}=\"${escapeHtml(attrValue)}\"`)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilteredAttrs.push(attrName)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst attrsOut =\n\t\t\t\t\tfilteredAttrs.length > 0 ? ` ${filteredAttrs.join(\" \")}` : \"\"\n\t\t\t\treturn isSelfClosing ? `<${tag}${attrsOut} />` : `<${tag}${attrsOut}>`\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Formats the value as a mailto link.\n\t * Supports custom display text, subject, and body parameters.\n\t * Validates email format - returns escaped plain text for invalid emails.\n\t */\n\tpublic asEmail(value: unknown, options?: EmailOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst email = String(value)\n\n\t\tif (!Formatter.isValidEmail(email)) {\n\t\t\treturn escapeHtml(email)\n\t\t}\n\n\t\tconst params: string[] = []\n\t\tif (options?.subject)\n\t\t\tparams.push(`subject=${encodeURIComponent(options.subject)}`)\n\t\tif (options?.body) params.push(`body=${encodeURIComponent(options.body)}`)\n\t\tconst query = params.length > 0 ? `?${params.join(\"&\")}` : \"\"\n\t\tconst displayText = escapeHtml(options?.text ?? email)\n\n\t\treturn `<a href=\"mailto:${escapeHtml(email)}${query}\">${displayText}</a>`\n\t}\n\n\t/** Basic email format validation (covers most common patterns). */\n\tprivate static isValidEmail(email: string): boolean {\n\t\treturn /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)\n\t}\n\n\t/**\n\t * Formats the value as a hyperlink.\n\t * Detects http, https, ftp, ftps, and mailto schemes.\n\t * Prepends `http://` when no recognized scheme is present.\n\t */\n\tpublic asUrl(value: unknown, options?: UrlOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst url = String(value)\n\t\tconst href = /^(https?|ftps?|mailto):/i.test(url) ? url : `http://${url}`\n\t\tconst target = options?.target ?? \"_blank\"\n\t\tconst displayText = escapeHtml(options?.text ?? url)\n\n\t\tconst attrs: string[] = [\n\t\t\t`href=\"${escapeHtml(href)}\"`,\n\t\t\t`target=\"${escapeHtml(target)}\"`,\n\t\t]\n\t\tif (options?.rel) attrs.push(`rel=\"${escapeHtml(options.rel)}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\n\t\treturn `<a ${attrs.join(\" \")}>${displayText}</a>`\n\t}\n\n\t/**\n\t * Formats the value as an image tag.\n\t * Supports width, height, CSS class, and loading strategy attributes.\n\t */\n\tpublic asImage(value: unknown, options?: ImageOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst src = String(value)\n\t\tconst alt = options?.alt ?? \"\"\n\n\t\tconst attrs: string[] = [\n\t\t\t`src=\"${escapeHtml(src)}\"`,\n\t\t\t`alt=\"${escapeHtml(alt)}\"`,\n\t\t]\n\t\tif (options?.width != null)\n\t\t\tattrs.push(`width=\"${escapeHtml(String(options.width))}\"`)\n\t\tif (options?.height != null)\n\t\t\tattrs.push(`height=\"${escapeHtml(String(options.height))}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\t\tif (options?.loading) attrs.push(`loading=\"${escapeHtml(options.loading)}\"`)\n\n\t\treturn `<img ${attrs.join(\" \")} />`\n\t}\n\n\t/** Formats the value as a boolean using the configured booleanFormat labels. */\n\tpublic asBoolean(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn value ? this.booleanFormat[1] : this.booleanFormat[0]\n\t}\n\n\t// ─── Number & Currency ────────────────────────────────────────────\n\n\t/** Formats the value as an integer by removing decimal digits without rounding. */\n\tpublic asInteger(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst intVal = Math.trunc(num)\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tmaximumFractionDigits: 0,\n\t\t\tminimumFractionDigits: 0,\n\t\t}).format(intVal)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a decimal number. */\n\tpublic asDecimal(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a percent number with \"%\" sign. */\n\tpublic asPercent(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 0\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"percent\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a currency number using ISO 4217 codes. */\n\tpublic asCurrency(value: unknown, currency?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst code = currency ?? this.currencyCode\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"currency\",\n\t\t\tcurrency: code,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.currencyDecimalSeparator ?? this.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a scientific number (e-notation). */\n\tpublic asScientific(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\tnotation: \"scientific\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\t}\n\n\t/**\n\t * Formats the value as a number spellout (e.g. 42 -> \"forty-two\").\n\t * Supports multiple locales via the locales/ registry.\n\t */\n\tpublic asSpellout(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\n\t\tconst spellout = getSpellout(this.locale)\n\n\t\tif (num === 0) return spellout.zeroWord\n\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\t\tconst intPart = Math.trunc(absNum)\n\t\tconst decPart = absNum - intPart\n\n\t\tlet result = spellout.integerToWords(intPart)\n\n\t\tif (decPart > 0) {\n\t\t\tconst decStr = String(absNum).split(\".\")[1] ?? \"\"\n\t\t\tconst decDigits = decStr.split(\"\").map((d) => spellout.digitToWord(d))\n\t\t\tresult += ` ${spellout.pointWord} ${decDigits.join(\" \")}`\n\t\t}\n\n\t\treturn isNegative ? `${spellout.negativePrefix} ${result}` : result\n\t}\n\n\t/**\n\t * Formats the value as an ordinal number (e.g. 1 -> \"1st\", 2 -> \"2nd\").\n\t * Supports multiple locales via built-in suffix maps and custom overrides\n\t * through `Formatter.registerOrdinalSuffixes()`.\n\t */\n\tpublic asOrdinal(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = Math.trunc(normalizeNumber(value))\n\n\t\ttry {\n\t\t\tconst pr = new Intl.PluralRules(this.locale, { type: \"ordinal\" })\n\t\t\tconst rule = pr.select(num)\n\n\t\t\tconst lang = this.locale.split(\"-\")[0]\n\t\t\tconst enSuffixes = Formatter.ordinalSuffixes.en ?? { other: \"th\" }\n\t\t\tconst langSuffixes = Formatter.ordinalSuffixes[lang] ?? enSuffixes\n\t\t\tconst suffix = langSuffixes[rule] ?? langSuffixes.other ?? \"\"\n\n\t\t\treturn `${new Intl.NumberFormat(this.locale).format(num)}${suffix}`\n\t\t} catch {\n\t\t\treturn `${num}${this.getOrdinalSuffixEn(num)}`\n\t\t}\n\t}\n\n\t/** Built-in ordinal suffix registry. Extensible at runtime. */\n\tprivate static ordinalSuffixes: Record<string, OrdinalSuffixMap> = {\n\t\ten: { one: \"st\", two: \"nd\", few: \"rd\", other: \"th\" },\n\t\tvi: { other: \"\" },\n\t\tfr: { one: \"er\", other: \"e\" },\n\t\tde: { other: \".\" },\n\t\tes: { other: \".\" },\n\t\tpt: { other: \".\" },\n\t\tit: { other: \".\" },\n\t\tja: { other: \"\" },\n\t\tko: { other: \"\" },\n\t\tzh: { other: \"\" },\n\t}\n\n\t/**\n\t * Register ordinal suffixes for a language at runtime.\n\t * Keys are Intl.PluralRules ordinal categories: \"one\", \"two\", \"few\", \"other\".\n\t */\n\tpublic static registerOrdinalSuffixes(\n\t\tlang: string,\n\t\tsuffixes: OrdinalSuffixMap,\n\t): void {\n\t\tFormatter.ordinalSuffixes[lang] = suffixes\n\t}\n\n\t// ─── Date & Time ──────────────────────────────────────────────────\n\n\t/** Formats the value as a date. */\n\tpublic asDate(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.dateFormat,\n\t\t\t\"medium\",\n\t\t\t\"date\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a time. */\n\tpublic asTime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.timeFormat,\n\t\t\t\"medium\",\n\t\t\t\"time\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a datetime. */\n\tpublic asDatetime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.datetimeFormat,\n\t\t\t\"medium\",\n\t\t\t\"datetime\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Returns the value as a UNIX timestamp (seconds since epoch). */\n\tpublic asTimestamp(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\treturn String(Math.floor(date.getTime() / 1000))\n\t}\n\n\t/**\n\t * Formats the value as the time interval between a date and now in human readable form.\n\t * Uses Intl.RelativeTimeFormat (built-in in Node.js / browsers).\n\t */\n\tpublic asRelativeTime(value: unknown, referenceTime?: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\n\t\tconst date = normalizeDate(value)\n\t\tconst ref = referenceTime ? normalizeDate(referenceTime) : new Date()\n\t\tconst diffMs = date.getTime() - ref.getTime()\n\t\tconst diffSec = Math.round(diffMs / 1000)\n\n\t\tconst rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: \"auto\" })\n\n\t\tconst absSec = Math.abs(diffSec)\n\t\tif (absSec < 60) return rtf.format(diffSec, \"second\")\n\t\tif (absSec < 3600) return rtf.format(Math.round(diffSec / 60), \"minute\")\n\t\tif (absSec < 86400) return rtf.format(Math.round(diffSec / 3600), \"hour\")\n\t\tif (absSec < 2592000) return rtf.format(Math.round(diffSec / 86400), \"day\")\n\t\tif (absSec < 31536000)\n\t\t\treturn rtf.format(Math.round(diffSec / 2592000), \"month\")\n\t\treturn rtf.format(Math.round(diffSec / 31536000), \"year\")\n\t}\n\n\t/**\n\t * Represents the value as duration in human readable format.\n\t * Example: 5400 -> \"1 hour, 30 minutes\"\n\t */\n\tpublic asDuration(value: unknown, implode?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet seconds = Math.abs(normalizeNumber(value))\n\t\tconst separator = implode ?? \", \"\n\n\t\tif (seconds === 0) return this.getDurationLabel(\"second\", 0)\n\n\t\tconst units: Array<{ unit: string; divisor: number }> = [\n\t\t\t{ unit: \"year\", divisor: 31536000 },\n\t\t\t{ unit: \"month\", divisor: 2592000 },\n\t\t\t{ unit: \"day\", divisor: 86400 },\n\t\t\t{ unit: \"hour\", divisor: 3600 },\n\t\t\t{ unit: \"minute\", divisor: 60 },\n\t\t\t{ unit: \"second\", divisor: 1 },\n\t\t]\n\n\t\tconst parts: string[] = []\n\t\tfor (const { unit, divisor } of units) {\n\t\t\tif (seconds >= divisor) {\n\t\t\t\tconst count = Math.floor(seconds / divisor)\n\t\t\t\tseconds %= divisor\n\t\t\t\tparts.push(this.getDurationLabel(unit, count))\n\t\t\t}\n\t\t}\n\n\t\treturn parts.join(separator)\n\t}\n\n\t// ─── Size & Measurement ──────────────────────────────────────────\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kilobytes\"). */\n\tpublic asSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"long\")\n\t}\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kB\"). */\n\tpublic asShortSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"short\")\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 meters\"). */\n\tpublic asLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 m\"). */\n\tpublic asShortLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"short\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kilograms\"). */\n\tpublic asWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kg\"). */\n\tpublic asShortWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"short\", decimals)\n\t}\n\n\t// ─── Utility Methods ─────────────────────────────────────────────\n\n\t/**\n\t * Abbreviate a large number with locale-aware suffixes.\n\t * e.g. 1500000 -> \"1.5 Million\" (en) or \"1,5 Trieu\" (vi).\n\t */\n\tpublic asNumberShort(value: unknown, options?: NumberShortOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst absNum = Math.abs(num)\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst fallback = options?.fallback ?? \"currency\"\n\t\tconst addSpace = options?.spaceBefore ?? false\n\t\tconst config = getNumberShortConfig(this.locale)\n\n\t\tfor (const { value: threshold, suffix } of config.thresholds) {\n\t\t\tif (absNum >= threshold) {\n\t\t\t\tconst short =\n\t\t\t\t\tMath.round((num / threshold) * 10 ** decimals) / 10 ** decimals\n\t\t\t\tconst sep = addSpace && !suffix.startsWith(\" \") ? \" \" : \"\"\n\t\t\t\treturn `${this.asDecimal(short, decimals)}${sep}${suffix}`\n\t\t\t}\n\t\t}\n\n\t\tswitch (fallback) {\n\t\t\tcase \"decimal\":\n\t\t\t\treturn this.asDecimal(num, decimals)\n\t\t\tcase \"integer\":\n\t\t\t\treturn this.asInteger(num)\n\t\t\tdefault:\n\t\t\t\treturn this.asCurrency(num)\n\t\t}\n\t}\n\n\t/**\n\t * Format the GPS (great-circle) distance between two coordinates.\n\t * Returns a human-readable string with unit suffix.\n\t */\n\tpublic asGpsDistance(\n\t\tlatFrom: number,\n\t\tlonFrom: number,\n\t\tlatTo: number,\n\t\tlonTo: number,\n\t\toptions?: GpsDistanceOptions,\n\t): string {\n\t\tconst earthRadius = options?.earthRadius ?? 6_371_000\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst rawUnit = options?.unit ?? \"auto\"\n\t\tconst meters = Formatter.gpsDistance(\n\t\t\tlatFrom,\n\t\t\tlonFrom,\n\t\t\tlatTo,\n\t\t\tlonTo,\n\t\t\tearthRadius,\n\t\t)\n\n\t\tlet value: number\n\t\tlet unit: string\n\t\tif (rawUnit === \"mi\") {\n\t\t\tvalue = meters / 1609.344\n\t\t\tunit = \"mi\"\n\t\t} else if (rawUnit === \"km\") {\n\t\t\tvalue = meters / 1000\n\t\t\tunit = \"km\"\n\t\t} else if (rawUnit === \"m\") {\n\t\t\tvalue = meters\n\t\t\tunit = \"m\"\n\t\t} else {\n\t\t\t// auto: use km if >= 1000m, otherwise m\n\t\t\tif (meters >= 1000) {\n\t\t\t\tvalue = meters / 1000\n\t\t\t\tunit = \"km\"\n\t\t\t} else {\n\t\t\t\tvalue = meters\n\t\t\t\tunit = \"m\"\n\t\t\t}\n\t\t}\n\n\t\treturn `${this.asDecimal(value, decimals)} ${unit}`\n\t}\n\n\t/** Haversine formula: returns distance in meters between two GPS coordinates. */\n\tprivate static gpsDistance(\n\t\tlatitudeFrom: number,\n\t\tlongitudeFrom: number,\n\t\tlatitudeTo: number,\n\t\tlongitudeTo: number,\n\t\tearthRadius = 6_371_000,\n\t): number {\n\t\tconst toRad = (deg: number) => (deg * Math.PI) / 180\n\n\t\tconst latFrom = toRad(latitudeFrom)\n\t\tconst latTo = toRad(latitudeTo)\n\t\tconst deltaLat = toRad(latitudeTo - latitudeFrom)\n\t\tconst deltaLon = toRad(longitudeTo - longitudeFrom)\n\n\t\tconst a =\n\t\t\tMath.sin(deltaLat / 2) ** 2 +\n\t\t\tMath.cos(latFrom) * Math.cos(latTo) * Math.sin(deltaLon / 2) ** 2\n\t\tconst c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))\n\n\t\treturn earthRadius * c\n\t}\n\n\t/**\n\t * Mask a string value, showing only the first and last N characters.\n\t * Instance method with options support.\n\t */\n\tpublic asMaskedValue(value: unknown, options?: MaskOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst str = String(value)\n\t\treturn Formatter.getMaskedValue(\n\t\t\tstr,\n\t\t\toptions?.startVisible ?? 4,\n\t\t\toptions?.endVisible ?? 3,\n\t\t\toptions?.maskChar ?? \"X\",\n\t\t)\n\t}\n\n\t/** Core masking logic used by asMaskedValue(). */\n\tprivate static getMaskedValue(\n\t\tvalue: string,\n\t\tstartVisible = 4,\n\t\tendVisible = 3,\n\t\tmaskChar = \"X\",\n\t): string {\n\t\tif (!value) return \"\"\n\t\tconst len = value.length\n\n\t\tif (len <= startVisible + endVisible) return value\n\n\t\tconst start = value.slice(0, startVisible)\n\t\tconst end = value.slice(len - endVisible)\n\t\tconst masked = maskChar.repeat(len - startVisible - endVisible)\n\n\t\treturn `${start}${masked}${end}`\n\t}\n\n\t// ─── Private helpers ──────────────────────────────────────────────\n\n\t/**\n\t * Format bytes into the most appropriate size unit.\n\t * Supports both base-1024 (binary) and base-1000 (decimal).\n\t */\n\tprivate formatBytes(\n\t\tvalue: unknown,\n\t\tdecimals: number | undefined,\n\t\twidth: FormatWidth,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet bytes = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\t\tconst base = this.sizeFormatBase\n\n\t\tconst isNegative = bytes < 0\n\t\tbytes = Math.abs(bytes)\n\n\t\ttype SizeUnit = {\n\t\t\tthreshold: number\n\t\t\tlong: string\n\t\t\tshort: string\n\t\t}\n\n\t\tconst units1024: SizeUnit[] = [\n\t\t\t{ threshold: 1099511627776, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1073741824, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1048576, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1024, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units1000: SizeUnit[] = [\n\t\t\t{ threshold: 1000000000000, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1000000000, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1000000, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1000, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units = base === 1024 ? units1024 : units1000\n\n\t\tfor (const unit of units) {\n\t\t\tif (bytes >= unit.threshold && unit.threshold > 0) {\n\t\t\t\tconst val = bytes / unit.threshold\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? unit.long : unit.short\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\tconst label = width === \"long\" ? \"bytes\" : \"B\"\n\t\treturn `${sign}${Math.round(bytes)} ${label}`\n\t}\n\n\t/**\n\t * Format a measurement value (length or mass).\n\t * Automatically selects the most appropriate unit based on value magnitude.\n\t */\n\tprivate formatMeasure(\n\t\tvalue: unknown,\n\t\ttype: \"length\" | \"mass\",\n\t\twidth: FormatWidth,\n\t\tdecimals?: number,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst configs = this.getMeasureUnits(type)\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\n\t\t// Find the best-fitting unit (largest unit where value >= 1)\n\t\tfor (let i = configs.length - 1; i >= 0; i--) {\n\t\t\tconst config = configs[i]\n\t\t\tif (!config) continue\n\t\t\tif (absNum >= config.factor || i === 0) {\n\t\t\t\tconst val = absNum / config.factor\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? config.longLabel : config.shortLabel\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\treturn String(num)\n\t}\n\n\t/** Get measurement unit configs for the configured system (metric/imperial). */\n\tprivate getMeasureUnits(type: \"length\" | \"mass\"): MeasureUnitConfig[] {\n\t\tif (type === \"length\") {\n\t\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\t\treturn [\n\t\t\t\t\t{ factor: 1, longLabel: \"inches\", shortLabel: \"in\" },\n\t\t\t\t\t{ factor: 12, longLabel: \"feet\", shortLabel: \"ft\" },\n\t\t\t\t\t{ factor: 36, longLabel: \"yards\", shortLabel: \"yd\" },\n\t\t\t\t\t{ factor: 63360, longLabel: \"miles\", shortLabel: \"mi\" },\n\t\t\t\t]\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"millimeters\", shortLabel: \"mm\" },\n\t\t\t\t{ factor: 1000, longLabel: \"meters\", shortLabel: \"m\" },\n\t\t\t\t{ factor: 1000000, longLabel: \"kilometers\", shortLabel: \"km\" },\n\t\t\t]\n\t\t}\n\n\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"grains\", shortLabel: \"gr\" },\n\t\t\t\t{ factor: 437.5, longLabel: \"ounces\", shortLabel: \"oz\" },\n\t\t\t\t{ factor: 7000, longLabel: \"pounds\", shortLabel: \"lb\" },\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t{ factor: 1, longLabel: \"grams\", shortLabel: \"g\" },\n\t\t\t{ factor: 1000, longLabel: \"kilograms\", shortLabel: \"kg\" },\n\t\t\t{ factor: 1000000, longLabel: \"tons\", shortLabel: \"t\" },\n\t\t]\n\t}\n\n\t/** Format the numeric part of a result using locale-aware Intl. */\n\tprivate formatNumberPart(num: number, digits: number): string {\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: 0,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Create a locale-aware duration label using Intl unit formatting. */\n\tprivate getDurationLabel(unit: string, count: number): string {\n\t\ttry {\n\t\t\tconst intlUnit = unit === \"month\" ? \"month\" : unit\n\t\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\t\tstyle: \"unit\",\n\t\t\t\tunit: intlUnit,\n\t\t\t\tunitDisplay: \"long\",\n\t\t\t}).format(count)\n\t\t} catch {\n\t\t\tconst plural = count !== 1 ? \"s\" : \"\"\n\t\t\treturn `${count} ${unit}${plural}`\n\t\t}\n\t}\n\n\t/** English ordinal suffix fallback. */\n\tprivate getOrdinalSuffixEn(n: number): string {\n\t\tconst abs = Math.abs(n)\n\t\tconst mod100 = abs % 100\n\t\tif (mod100 >= 11 && mod100 <= 13) return \"th\"\n\t\tswitch (abs % 10) {\n\t\t\tcase 1:\n\t\t\t\treturn \"st\"\n\t\t\tcase 2:\n\t\t\t\treturn \"nd\"\n\t\t\tcase 3:\n\t\t\t\treturn \"rd\"\n\t\t\tdefault:\n\t\t\t\treturn \"th\"\n\t\t}\n\t}\n}\n","import { Formatter } from \"./formatter.js\"\nimport type { FormatterOptions } from \"./types.js\"\n\n/**\n * Global singleton Formatter instance.\n *\n * Usage: import once at app entry, configure once, then use `formatter` everywhere.\n *\n * ```ts\n * // main.ts (once)\n * import { configureFormatter } from \"@template/helpers\"\n * configureFormatter({ locale: \"vi-VN\", currencyCode: \"VND\" })\n *\n * // any-page.tsx (no setup needed)\n * import { formatter } from \"@template/helpers\"\n * formatter.asCurrency(1234567)\n * ```\n */\nlet instance = new Formatter()\n\n/** The global Formatter singleton. Ready to use after `configureFormatter()`. */\nexport const formatter = new Proxy({} as Formatter, {\n\tget(_target, prop, receiver) {\n\t\treturn Reflect.get(instance, prop, receiver)\n\t},\n})\n\n/**\n * Configure the global formatter once (typically at app bootstrap).\n * Replaces the internal instance - all existing `formatter` references\n * automatically pick up the new config via the proxy.\n */\nexport function configureFormatter(options: FormatterOptions): void {\n\tinstance = new Formatter(options)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["ones","digitWords","convert"],"sources":["../src/locales/en.ts","../src/locales/vi.ts","../src/locales/index.ts","../src/utils.ts","../src/formatter.ts","../src/global.ts"],"sourcesContent":["import type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\nconst ones = [\n\t\"\",\n\t\"one\",\n\t\"two\",\n\t\"three\",\n\t\"four\",\n\t\"five\",\n\t\"six\",\n\t\"seven\",\n\t\"eight\",\n\t\"nine\",\n\t\"ten\",\n\t\"eleven\",\n\t\"twelve\",\n\t\"thirteen\",\n\t\"fourteen\",\n\t\"fifteen\",\n\t\"sixteen\",\n\t\"seventeen\",\n\t\"eighteen\",\n\t\"nineteen\",\n]\n\nconst tens = [\n\t\"\",\n\t\"\",\n\t\"twenty\",\n\t\"thirty\",\n\t\"forty\",\n\t\"fifty\",\n\t\"sixty\",\n\t\"seventy\",\n\t\"eighty\",\n\t\"ninety\",\n]\n\nconst digitWords: Record<string, string> = {\n\t\"0\": \"zero\",\n\t\"1\": \"one\",\n\t\"2\": \"two\",\n\t\"3\": \"three\",\n\t\"4\": \"four\",\n\t\"5\": \"five\",\n\t\"6\": \"six\",\n\t\"7\": \"seven\",\n\t\"8\": \"eight\",\n\t\"9\": \"nine\",\n}\n\nfunction convert(num: number): string {\n\tif (num === 0) return \"\"\n\tif (num < 20) return ones[num] ?? \"\"\n\tif (num < 100) {\n\t\tconst t = tens[Math.floor(num / 10)] ?? \"\"\n\t\tconst o = ones[num % 10]\n\t\treturn o ? `${t}-${o}` : t\n\t}\n\tif (num < 1000) {\n\t\tconst h = ones[Math.floor(num / 100)] ?? \"\"\n\t\tconst remainder = num % 100\n\t\treturn remainder ? `${h} hundred ${convert(remainder)}` : `${h} hundred`\n\t}\n\tif (num < 1_000_000) {\n\t\tconst th = convert(Math.floor(num / 1000))\n\t\tconst remainder = num % 1000\n\t\treturn remainder ? `${th} thousand ${convert(remainder)}` : `${th} thousand`\n\t}\n\tif (num < 1_000_000_000) {\n\t\tconst m = convert(Math.floor(num / 1_000_000))\n\t\tconst remainder = num % 1_000_000\n\t\treturn remainder ? `${m} million ${convert(remainder)}` : `${m} million`\n\t}\n\tconst b = convert(Math.floor(num / 1_000_000_000))\n\tconst remainder = num % 1_000_000_000\n\treturn remainder ? `${b} billion ${convert(remainder)}` : `${b} billion`\n}\n\nexport const enSpellout: LocaleSpellout = {\n\tzeroWord: \"zero\",\n\tpointWord: \"point\",\n\tnegativePrefix: \"minus\",\n\n\tintegerToWords(n: number): string {\n\t\tif (n === 0) return \"zero\"\n\t\treturn convert(n).trim()\n\t},\n\n\tdigitToWord(digit: string): string {\n\t\treturn digitWords[digit] ?? digit\n\t},\n}\n\nexport const enNumberShort: NumberShortConfig = {\n\tthresholds: [\n\t\t{ value: 1_000_000_000_000, suffix: \" Trillion\" },\n\t\t{ value: 1_000_000_000, suffix: \" Billion\" },\n\t\t{ value: 1_000_000, suffix: \" Million\" },\n\t\t{ value: 1_000, suffix: \"K\" },\n\t],\n}\n","import type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\nconst ones = [\n\t\"\",\n\t\"m\\u1ed9t\",\n\t\"hai\",\n\t\"ba\",\n\t\"b\\u1ed1n\",\n\t\"n\\u0103m\",\n\t\"s\\u00e1u\",\n\t\"b\\u1ea3y\",\n\t\"t\\u00e1m\",\n\t\"ch\\u00edn\",\n]\n\nconst onesInTens = [\n\t\"\",\n\t\"m\\u1ed1t\",\n\t\"hai\",\n\t\"ba\",\n\t\"b\\u1ed1n\",\n\t\"l\\u0103m\", // 5 in tens position uses \"lam\" not \"nam\"\n\t\"s\\u00e1u\",\n\t\"b\\u1ea3y\",\n\t\"t\\u00e1m\",\n\t\"ch\\u00edn\",\n]\n\nconst digitWords: Record<string, string> = {\n\t\"0\": \"kh\\u00f4ng\",\n\t\"1\": \"m\\u1ed9t\",\n\t\"2\": \"hai\",\n\t\"3\": \"ba\",\n\t\"4\": \"b\\u1ed1n\",\n\t\"5\": \"n\\u0103m\",\n\t\"6\": \"s\\u00e1u\",\n\t\"7\": \"b\\u1ea3y\",\n\t\"8\": \"t\\u00e1m\",\n\t\"9\": \"ch\\u00edn\",\n}\n\n/**\n * Vietnamese number spellout following standard rules:\n * - 5 in ones position of tens => \"lam\" (not \"nam\")\n * - 1 in ones position of tens (>=20) => \"mot\" with special handling\n * - 0 in ones position of tens => \"muoi\" only (no trailing)\n * - Tens starting with 1 => \"muoi\", otherwise => \"muoi\" with prefix\n */\nfunction readTens(t: number, u: number): string {\n\tlet result = \"\"\n\n\tif (t === 1) {\n\t\tresult = \"m\\u01b0\\u1eddi\"\n\t} else {\n\t\tresult = `${ones[t]} m\\u01b0\\u01a1i`\n\t}\n\n\tif (u === 0) return result\n\tif (u === 1 && t > 1) return `${result} m\\u1ed1t`\n\tif (u === 5 && t > 0) return `${result} l\\u0103m`\n\treturn `${result} ${onesInTens[u] ?? \"\"}`\n}\n\nfunction readHundreds(h: number, t: number, u: number): string {\n\tconst result = `${ones[h]} tr\\u0103m`\n\tif (t === 0 && u === 0) return result\n\tif (t === 0) return `${result} linh ${ones[u]}`\n\treturn `${result} ${readTens(t, u)}`\n}\n\nfunction readBlock(num: number): string {\n\tif (num === 0) return \"\"\n\n\tconst h = Math.floor(num / 100)\n\tconst t = Math.floor((num % 100) / 10)\n\tconst u = num % 10\n\n\tif (h > 0) return readHundreds(h, t, u)\n\tif (t > 0) return readTens(t, u)\n\treturn ones[u] ?? \"\"\n}\n\nfunction convert(num: number): string {\n\tif (num === 0) return \"kh\\u00f4ng\"\n\n\tconst units = [\n\t\t{ value: 1_000_000_000, label: \"t\\u1ef7\" },\n\t\t{ value: 1_000_000, label: \"tri\\u1ec7u\" },\n\t\t{ value: 1_000, label: \"ngh\\u00ecn\" },\n\t\t{ value: 1, label: \"\" },\n\t]\n\n\tconst parts: string[] = []\n\tlet remaining = num\n\n\tfor (const unit of units) {\n\t\tif (remaining >= unit.value) {\n\t\t\tconst block = Math.floor(remaining / unit.value)\n\t\t\tremaining %= unit.value\n\n\t\t\tconst blockStr = readBlock(block)\n\t\t\tif (blockStr) {\n\t\t\t\tparts.push(unit.label ? `${blockStr} ${unit.label}` : blockStr)\n\t\t\t}\n\n\t\t\t// Handle leading zeros in next block (e.g. 1001 -> \"mot nghin khong tram linh mot\")\n\t\t\tif (remaining > 0 && remaining < unit.value / 10) {\n\t\t\t\t// Needs \"khong tram\" prefix if next block < 100\n\t\t\t\tif (remaining < 100 && unit.value >= 1000) {\n\t\t\t\t\tparts.push(\"kh\\u00f4ng tr\\u0103m\")\n\t\t\t\t\tif (remaining < 10) {\n\t\t\t\t\t\tparts.push(`linh ${ones[remaining]}`)\n\t\t\t\t\t\tremaining = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\" \").trim()\n}\n\nexport const viSpellout: LocaleSpellout = {\n\tzeroWord: \"kh\\u00f4ng\",\n\tpointWord: \"ph\\u1ea9y\",\n\tnegativePrefix: \"\\u00e2m\",\n\n\tintegerToWords(n: number): string {\n\t\tif (n === 0) return \"kh\\u00f4ng\"\n\t\treturn convert(n)\n\t},\n\n\tdigitToWord(digit: string): string {\n\t\treturn digitWords[digit] ?? digit\n\t},\n}\n\nexport const viNumberShort: NumberShortConfig = {\n\tthresholds: [\n\t\t{ value: 1_000_000_000_000, suffix: \" Ngh\\u00ecn T\\u1ef7\" },\n\t\t{ value: 1_000_000_000, suffix: \" T\\u1ef7\" },\n\t\t{ value: 1_000_000, suffix: \" Tri\\u1ec7u\" },\n\t\t{ value: 1_000, suffix: \" Ng\\u00e0n\" },\n\t],\n}\n","import { enNumberShort, enSpellout } from \"./en.js\"\nimport type {\n\tLocaleRegistry,\n\tLocaleSpellout,\n\tNumberShortConfig,\n\tNumberShortRegistry,\n} from \"./types.js\"\nimport { viNumberShort, viSpellout } from \"./vi.js\"\n\nexport type { LocaleSpellout, NumberShortConfig } from \"./types.js\"\n\n/** Built-in spellout locale registry. */\nconst spelloutRegistry: LocaleRegistry = {\n\ten: enSpellout,\n\tvi: viSpellout,\n}\n\n/** Built-in number-short locale registry. */\nconst numberShortRegistry: NumberShortRegistry = {\n\ten: enNumberShort,\n\tvi: viNumberShort,\n}\n\n/** Get the spellout provider for a locale, falling back to English. */\nexport function getSpellout(locale: string): LocaleSpellout {\n\tconst lang = locale.split(\"-\")[0]\n\treturn spelloutRegistry[lang] ?? enSpellout\n}\n\n/** Get the number-short config for a locale, falling back to English. */\nexport function getNumberShortConfig(locale: string): NumberShortConfig {\n\tconst lang = locale.split(\"-\")[0]\n\treturn numberShortRegistry[lang] ?? enNumberShort\n}\n\n/** Register a custom spellout locale at runtime. */\nexport function registerSpellout(lang: string, impl: LocaleSpellout): void {\n\tspelloutRegistry[lang] = impl\n}\n\n/** Register a custom number-short config at runtime. */\nexport function registerNumberShort(\n\tlang: string,\n\tconfig: NumberShortConfig,\n): void {\n\tnumberShortRegistry[lang] = config\n}\n","import type { DateFormatPreset } from \"./types.js\"\n\n/**\n * Escape the 5 HTML-special characters, equivalent to PHP's htmlspecialchars().\n * No external dependency - pure string replacement.\n */\nexport function escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\")\n}\n\n/**\n * Normalize an input value into a Date object.\n * Accepts: Date, number (UNIX seconds or milliseconds), string (ISO 8601).\n */\nexport function normalizeDate(value: unknown): Date {\n\tif (value instanceof Date) return value\n\n\tif (typeof value === \"number\") {\n\t\t// Values below 1e12 are treated as seconds, otherwise milliseconds\n\t\treturn new Date(value < 1e12 ? value * 1000 : value)\n\t}\n\n\tif (typeof value === \"string\") {\n\t\tconst parsed = new Date(value)\n\t\tif (Number.isNaN(parsed.getTime())) {\n\t\t\tthrow new Error(`Cannot parse date value: \"${value}\"`)\n\t\t}\n\t\treturn parsed\n\t}\n\n\tthrow new Error(`Invalid data type for date: ${typeof value}`)\n}\n\n/**\n * Parse a date string safely, treating timezone-naive ISO strings as UTC.\n * Strings like \"2024-03-15T14:30:00\" (no Z, no +offset) get \"Z\" appended\n * to ensure consistent UTC interpretation across environments.\n * Returns an Invalid Date (NaN) for null, undefined, or empty values\n * instead of throwing - suitable for display pipelines.\n */\nexport function parseDate(\n\tdate: string | Date | null | undefined,\n): Date {\n\tif (!date) return new Date(Number.NaN)\n\tif (date instanceof Date) return date\n\tlet str = date\n\tif (typeof str === \"string\" && !str.endsWith(\"Z\") && !str.includes(\"+\") && str.includes(\"T\")) {\n\t\tstr += \"Z\"\n\t}\n\treturn new Date(str)\n}\n\n/**\n * Normalize an input value into a number.\n * Accepts: number, numeric string (with optional comma grouping), boolean.\n */\nexport function normalizeNumber(value: unknown): number {\n\tif (typeof value === \"number\") return value\n\n\tif (typeof value === \"string\") {\n\t\tconst trimmed = value.trim()\n\t\t// Strip common thousand separators before parsing\n\t\tconst cleaned = trimmed.replace(/,/g, \"\")\n\t\tconst num = Number(cleaned)\n\t\tif (Number.isNaN(num)) {\n\t\t\tthrow new Error(`Cannot parse numeric value: \"${value}\"`)\n\t\t}\n\t\treturn num\n\t}\n\n\tif (typeof value === \"boolean\") return value ? 1 : 0\n\n\tthrow new Error(`Invalid data type for number: ${typeof value}`)\n}\n\n/**\n * Convert a preset name (short/medium/long/full) to Intl.DateTimeFormatOptions.\n */\nexport function presetToDateOptions(\n\tpreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tconst dateOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { year: \"2-digit\", month: \"numeric\", day: \"numeric\" },\n\t\tmedium: { year: \"numeric\", month: \"short\", day: \"numeric\" },\n\t\tlong: { year: \"numeric\", month: \"long\", day: \"numeric\" },\n\t\tfull: { year: \"numeric\", month: \"long\", day: \"numeric\", weekday: \"long\" },\n\t}\n\n\tconst timeOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { hour: \"numeric\", minute: \"numeric\" },\n\t\tmedium: { hour: \"numeric\", minute: \"numeric\", second: \"numeric\" },\n\t\tlong: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"short\",\n\t\t},\n\t\tfull: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"long\",\n\t\t},\n\t}\n\n\tswitch (type) {\n\t\tcase \"date\":\n\t\t\treturn dateOptions[preset] ?? dateOptions.medium\n\t\tcase \"time\":\n\t\t\treturn timeOptions[preset] ?? timeOptions.medium\n\t\tcase \"datetime\":\n\t\t\treturn {\n\t\t\t\t...(dateOptions[preset] ?? dateOptions.medium),\n\t\t\t\t...(timeOptions[preset] ?? timeOptions.medium),\n\t\t\t}\n\t}\n}\n\n/**\n * Resolve a format value: string preset -> Intl options, object -> use directly.\n */\nexport function resolveDateFormat(\n\tformat: string | Intl.DateTimeFormatOptions | undefined,\n\tdefaultPreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tif (!format) return presetToDateOptions(defaultPreset, type)\n\tif (typeof format === \"object\") return format\n\treturn presetToDateOptions(format as DateFormatPreset, type)\n}\n\n/**\n * Replace locale-default separators with custom ones in a formatted string.\n * Uses temporary placeholders to avoid replacement collisions.\n */\nexport function applyCustomSeparators(\n\tformatted: string,\n\tlocale: string,\n\tcustomDecimal?: string | null,\n\tcustomThousand?: string | null,\n): string {\n\tif (customDecimal == null && customThousand == null) return formatted\n\n\t// Detect locale-default separators\n\tconst parts = new Intl.NumberFormat(locale).formatToParts(1234567.89)\n\tconst localeDecimal = parts.find((p) => p.type === \"decimal\")?.value ?? \".\"\n\tconst localeGroup = parts.find((p) => p.type === \"group\")?.value ?? \",\"\n\n\tlet result = formatted\n\n\t// Temporary placeholders to prevent collision during replacement\n\tconst PLACEHOLDER_DEC = \"\\x01\"\n\tconst PLACEHOLDER_GRP = \"\\x02\"\n\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(localeDecimal, PLACEHOLDER_DEC)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(localeGroup, PLACEHOLDER_GRP)\n\t}\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_DEC, customDecimal)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_GRP, customThousand)\n\t}\n\n\treturn result\n}\n","import { getNumberShortConfig, getSpellout } from \"./locales/index.js\"\nimport type {\n\tEmailOptions,\n\tFormatterOptions,\n\tFormatWidth,\n\tGpsDistanceOptions,\n\tHtmlSanitizeConfig,\n\tImageOptions,\n\tMaskOptions,\n\tMeasureUnitConfig,\n\tNumberShortOptions,\n\tOrdinalSuffixMap,\n\tParagraphOptions,\n\tUnitSystem,\n\tUrlOptions,\n} from \"./types.js\"\nimport {\n\tapplyCustomSeparators,\n\tescapeHtml,\n\tnormalizeDate,\n\tnormalizeNumber,\n\tresolveDateFormat,\n} from \"./utils.js\"\n\n/** Returns true for null or undefined only. */\nfunction isNullish(value: unknown): value is null | undefined {\n\treturn value === null || value === undefined\n}\n\n/** Returns true for null, undefined, or empty/whitespace-only strings. */\nfunction isBlank(value: unknown): value is null | undefined {\n\tif (value === null || value === undefined) return true\n\tif (typeof value === \"string\" && value.trim() === \"\") return true\n\treturn false\n}\n\n/**\n * TypeScript port of yii\\i18n\\Formatter.\n *\n * Uses only built-in Intl APIs - zero external dependencies.\n * Supports: strings, HTML, numbers, currency, dates, times,\n * file sizes, measurement units, and more.\n */\nexport class Formatter {\n\tpublic locale: string\n\tpublic timeZone: string\n\tpublic defaultTimeZone: string\n\tpublic dateFormat: string | Intl.DateTimeFormatOptions\n\tpublic timeFormat: string | Intl.DateTimeFormatOptions\n\tpublic datetimeFormat: string | Intl.DateTimeFormatOptions\n\tpublic booleanFormat: [string, string]\n\tpublic nullDisplay: string\n\tpublic currencyCode: string\n\tpublic decimalSeparator: string | null\n\tpublic thousandSeparator: string | null\n\tpublic currencyDecimalSeparator: string | null\n\tpublic sizeFormatBase: 1024 | 1000\n\tpublic systemOfUnits: UnitSystem\n\tpublic defaultDecimalDigits: number | null\n\n\tconstructor(options: FormatterOptions = {}) {\n\t\tthis.locale = options.locale ?? \"en-US\"\n\t\tthis.timeZone = options.timeZone ?? \"UTC\"\n\t\tthis.defaultTimeZone = options.defaultTimeZone ?? \"UTC\"\n\t\tthis.dateFormat = options.dateFormat ?? \"medium\"\n\t\tthis.timeFormat = options.timeFormat ?? \"medium\"\n\t\tthis.datetimeFormat = options.datetimeFormat ?? \"medium\"\n\t\tthis.booleanFormat = options.booleanFormat ?? [\"No\", \"Yes\"]\n\t\tthis.nullDisplay = options.nullDisplay ?? \"(not set)\"\n\t\tthis.currencyCode = options.currencyCode ?? \"USD\"\n\t\tthis.decimalSeparator = options.decimalSeparator ?? null\n\t\tthis.thousandSeparator = options.thousandSeparator ?? null\n\t\tthis.currencyDecimalSeparator = options.currencyDecimalSeparator ?? null\n\t\tthis.sizeFormatBase = options.sizeFormatBase ?? 1024\n\t\tthis.systemOfUnits = options.systemOfUnits ?? \"metric\"\n\t\tthis.defaultDecimalDigits = options.defaultDecimalDigits ?? null\n\t}\n\n\t// ─── Generic dispatch ─────────────────────────────────────────────\n\n\t/**\n\t * Format a value by type name, like Yii2's `$formatter->format($value, 'date')`.\n\t * Supports both string and tuple `[formatName, ...params]` signatures.\n\t */\n\tpublic format(value: unknown, type: string | [string, ...unknown[]]): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\n\t\tconst formatName = Array.isArray(type) ? type[0] : type\n\t\tconst params = Array.isArray(type) ? type.slice(1) : []\n\t\tconst methodName = `as${formatName.charAt(0).toUpperCase()}${formatName.slice(1)}`\n\n\t\tconst method = (this as Record<string, unknown>)[methodName]\n\t\tif (typeof method === \"function\") {\n\t\t\treturn (method as (...args: unknown[]) => string).call(\n\t\t\t\tthis,\n\t\t\t\tvalue,\n\t\t\t\t...params,\n\t\t\t)\n\t\t}\n\n\t\tthrow new Error(`Unknown format type: ${formatName}`)\n\t}\n\n\t// ─── String & HTML ────────────────────────────────────────────────\n\n\t/** Returns the value as-is without any formatting. */\n\tpublic asRaw(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn String(value)\n\t}\n\n\t/** Formats the value as HTML-encoded plain text. */\n\tpublic asText(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn escapeHtml(String(value))\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text with newlines converted to `<br />`.\n\t * Handles all line-ending variants: `\\r\\n` (Windows), `\\r` (old Mac), `\\n` (Unix).\n\t * Consecutive newlines produce multiple `<br />` tags.\n\t */\n\tpublic asNtext(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst escaped = escapeHtml(String(value))\n\t\treturn escaped.replace(/\\r\\n/g, \"<br />\").replace(/[\\r\\n]/g, \"<br />\")\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text paragraphs (split by double newlines).\n\t * Supports configurable wrapper tag and inline line-break conversion.\n\t */\n\tpublic asParagraphs(value: unknown, options?: ParagraphOptions): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst tag = options?.tag ?? \"p\"\n\t\tconst lineBreaks = options?.lineBreaks ?? false\n\t\tconst text = String(value)\n\t\t// Normalize line endings before splitting\n\t\tconst normalized = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\")\n\t\tconst paragraphs = normalized.split(/\\n\\s*\\n/)\n\t\treturn paragraphs\n\t\t\t.map((p) => {\n\t\t\t\tlet content = escapeHtml(p.trim())\n\t\t\t\tif (lineBreaks) {\n\t\t\t\t\tcontent = content.replace(/\\n/g, \"<br />\")\n\t\t\t\t}\n\t\t\t\treturn `<${tag}>${content}</${tag}>`\n\t\t\t})\n\t\t\t.filter((p) => p !== `<${tag}></${tag}>`)\n\t\t\t.join(\"\\n\")\n\t}\n\n\t/**\n\t * Returns the value as HTML text.\n\t * When a sanitize config is provided, only allowed tags and attributes are kept.\n\t * Without config, the value is returned as-is (caller is responsible for safety).\n\t */\n\tpublic asHtml(value: unknown, sanitize?: HtmlSanitizeConfig): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst html = String(value)\n\t\tif (!sanitize) return html\n\t\treturn Formatter.sanitizeHtml(html, sanitize)\n\t}\n\n\t/**\n\t * Allowlist-based HTML sanitizer. Strips tags and attributes not in the config.\n\t * Handles self-closing tags, nested tags, and attribute filtering.\n\t */\n\tprivate static sanitizeHtml(\n\t\thtml: string,\n\t\tconfig: HtmlSanitizeConfig,\n\t): string {\n\t\tconst allowedTags = new Set(\n\t\t\t(config.allowedTags ?? []).map((t) => t.toLowerCase()),\n\t\t)\n\t\tconst allowedAttrs = config.allowedAttributes ?? {}\n\n\t\t// Match opening tags, closing tags, and self-closing tags\n\t\treturn html.replace(\n\t\t\t/<\\/?([a-zA-Z][a-zA-Z0-9]*)\\b([^>]*?)\\s*\\/?>/g,\n\t\t\t(match, tagName: string, attrsStr: string) => {\n\t\t\t\tconst tag = tagName.toLowerCase()\n\t\t\t\tif (!allowedTags.has(tag)) return \"\"\n\n\t\t\t\tconst isClosing = match.startsWith(\"</\")\n\t\t\t\tif (isClosing) return `</${tag}>`\n\n\t\t\t\tconst isSelfClosing = match.endsWith(\"/>\")\n\t\t\t\tconst tagAllowedAttrs = new Set(\n\t\t\t\t\t(allowedAttrs[tag] ?? []).map((a) => a.toLowerCase()),\n\t\t\t\t)\n\n\t\t\t\t// Parse and filter attributes\n\t\t\t\tconst filteredAttrs: string[] = []\n\t\t\t\tconst attrRegex =\n\t\t\t\t\t/([a-zA-Z_:][\\w:.-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|(\\S+)))?/g\n\t\t\t\tlet attrMatch: RegExpExecArray | null = null\n\t\t\t\twhile (true) {\n\t\t\t\t\tattrMatch = attrRegex.exec(attrsStr)\n\t\t\t\t\tif (!attrMatch) break\n\t\t\t\t\tconst attrName = attrMatch[1].toLowerCase()\n\t\t\t\t\tif (tagAllowedAttrs.has(attrName)) {\n\t\t\t\t\t\tconst attrValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4]\n\t\t\t\t\t\tif (attrValue !== undefined) {\n\t\t\t\t\t\t\tfilteredAttrs.push(`${attrName}=\"${escapeHtml(attrValue)}\"`)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilteredAttrs.push(attrName)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst attrsOut =\n\t\t\t\t\tfilteredAttrs.length > 0 ? ` ${filteredAttrs.join(\" \")}` : \"\"\n\t\t\t\treturn isSelfClosing ? `<${tag}${attrsOut} />` : `<${tag}${attrsOut}>`\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Formats the value as a mailto link.\n\t * Supports custom display text, subject, and body parameters.\n\t * Validates email format - returns escaped plain text for invalid emails.\n\t */\n\tpublic asEmail(value: unknown, options?: EmailOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst email = String(value)\n\n\t\tif (!Formatter.isValidEmail(email)) {\n\t\t\treturn escapeHtml(email)\n\t\t}\n\n\t\tconst params: string[] = []\n\t\tif (options?.subject)\n\t\t\tparams.push(`subject=${encodeURIComponent(options.subject)}`)\n\t\tif (options?.body) params.push(`body=${encodeURIComponent(options.body)}`)\n\t\tconst query = params.length > 0 ? `?${params.join(\"&\")}` : \"\"\n\t\tconst displayText = escapeHtml(options?.text ?? email)\n\n\t\treturn `<a href=\"mailto:${escapeHtml(email)}${query}\">${displayText}</a>`\n\t}\n\n\t/** Basic email format validation (covers most common patterns). */\n\tprivate static isValidEmail(email: string): boolean {\n\t\treturn /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)\n\t}\n\n\t/**\n\t * Formats the value as a hyperlink.\n\t * Detects http, https, ftp, ftps, and mailto schemes.\n\t * Prepends `http://` when no recognized scheme is present.\n\t */\n\tpublic asUrl(value: unknown, options?: UrlOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst url = String(value)\n\t\tconst href = /^(https?|ftps?|mailto):/i.test(url) ? url : `http://${url}`\n\t\tconst target = options?.target ?? \"_blank\"\n\t\tconst displayText = escapeHtml(options?.text ?? url)\n\n\t\tconst attrs: string[] = [\n\t\t\t`href=\"${escapeHtml(href)}\"`,\n\t\t\t`target=\"${escapeHtml(target)}\"`,\n\t\t]\n\t\tif (options?.rel) attrs.push(`rel=\"${escapeHtml(options.rel)}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\n\t\treturn `<a ${attrs.join(\" \")}>${displayText}</a>`\n\t}\n\n\t/**\n\t * Formats the value as an image tag.\n\t * Supports width, height, CSS class, and loading strategy attributes.\n\t */\n\tpublic asImage(value: unknown, options?: ImageOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst src = String(value)\n\t\tconst alt = options?.alt ?? \"\"\n\n\t\tconst attrs: string[] = [\n\t\t\t`src=\"${escapeHtml(src)}\"`,\n\t\t\t`alt=\"${escapeHtml(alt)}\"`,\n\t\t]\n\t\tif (options?.width != null)\n\t\t\tattrs.push(`width=\"${escapeHtml(String(options.width))}\"`)\n\t\tif (options?.height != null)\n\t\t\tattrs.push(`height=\"${escapeHtml(String(options.height))}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\t\tif (options?.loading) attrs.push(`loading=\"${escapeHtml(options.loading)}\"`)\n\n\t\treturn `<img ${attrs.join(\" \")} />`\n\t}\n\n\t/** Formats the value as a boolean using the configured booleanFormat labels. */\n\tpublic asBoolean(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn value ? this.booleanFormat[1] : this.booleanFormat[0]\n\t}\n\n\t// ─── Number & Currency ────────────────────────────────────────────\n\n\t/** Formats the value as an integer by removing decimal digits without rounding. */\n\tpublic asInteger(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst intVal = Math.trunc(num)\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tmaximumFractionDigits: 0,\n\t\t\tminimumFractionDigits: 0,\n\t\t}).format(intVal)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a decimal number. */\n\tpublic asDecimal(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a percent number with \"%\" sign. */\n\tpublic asPercent(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 0\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"percent\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a currency number using ISO 4217 codes. */\n\tpublic asCurrency(value: unknown, currency?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst code = currency ?? this.currencyCode\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"currency\",\n\t\t\tcurrency: code,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.currencyDecimalSeparator ?? this.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a scientific number (e-notation). */\n\tpublic asScientific(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\tnotation: \"scientific\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\t}\n\n\t/**\n\t * Formats the value as a number spellout (e.g. 42 -> \"forty-two\").\n\t * Supports multiple locales via the locales/ registry.\n\t */\n\tpublic asSpellout(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\n\t\tconst spellout = getSpellout(this.locale)\n\n\t\tif (num === 0) return spellout.zeroWord\n\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\t\tconst intPart = Math.trunc(absNum)\n\t\tconst decPart = absNum - intPart\n\n\t\tlet result = spellout.integerToWords(intPart)\n\n\t\tif (decPart > 0) {\n\t\t\tconst decStr = String(absNum).split(\".\")[1] ?? \"\"\n\t\t\tconst decDigits = decStr.split(\"\").map((d) => spellout.digitToWord(d))\n\t\t\tresult += ` ${spellout.pointWord} ${decDigits.join(\" \")}`\n\t\t}\n\n\t\treturn isNegative ? `${spellout.negativePrefix} ${result}` : result\n\t}\n\n\t/**\n\t * Formats the value as an ordinal number (e.g. 1 -> \"1st\", 2 -> \"2nd\").\n\t * Supports multiple locales via built-in suffix maps and custom overrides\n\t * through `Formatter.registerOrdinalSuffixes()`.\n\t */\n\tpublic asOrdinal(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = Math.trunc(normalizeNumber(value))\n\n\t\ttry {\n\t\t\tconst pr = new Intl.PluralRules(this.locale, { type: \"ordinal\" })\n\t\t\tconst rule = pr.select(num)\n\n\t\t\tconst lang = this.locale.split(\"-\")[0]\n\t\t\tconst enSuffixes = Formatter.ordinalSuffixes.en ?? { other: \"th\" }\n\t\t\tconst langSuffixes = Formatter.ordinalSuffixes[lang] ?? enSuffixes\n\t\t\tconst suffix = langSuffixes[rule] ?? langSuffixes.other ?? \"\"\n\n\t\t\treturn `${new Intl.NumberFormat(this.locale).format(num)}${suffix}`\n\t\t} catch {\n\t\t\treturn `${num}${this.getOrdinalSuffixEn(num)}`\n\t\t}\n\t}\n\n\t/** Built-in ordinal suffix registry. Extensible at runtime. */\n\tprivate static ordinalSuffixes: Record<string, OrdinalSuffixMap> = {\n\t\ten: { one: \"st\", two: \"nd\", few: \"rd\", other: \"th\" },\n\t\tvi: { other: \"\" },\n\t\tfr: { one: \"er\", other: \"e\" },\n\t\tde: { other: \".\" },\n\t\tes: { other: \".\" },\n\t\tpt: { other: \".\" },\n\t\tit: { other: \".\" },\n\t\tja: { other: \"\" },\n\t\tko: { other: \"\" },\n\t\tzh: { other: \"\" },\n\t}\n\n\t/**\n\t * Register ordinal suffixes for a language at runtime.\n\t * Keys are Intl.PluralRules ordinal categories: \"one\", \"two\", \"few\", \"other\".\n\t */\n\tpublic static registerOrdinalSuffixes(\n\t\tlang: string,\n\t\tsuffixes: OrdinalSuffixMap,\n\t): void {\n\t\tFormatter.ordinalSuffixes[lang] = suffixes\n\t}\n\n\t// ─── Date & Time ──────────────────────────────────────────────────\n\n\t/** Formats the value as a date. */\n\tpublic asDate(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.dateFormat,\n\t\t\t\"medium\",\n\t\t\t\"date\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a time. */\n\tpublic asTime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.timeFormat,\n\t\t\t\"medium\",\n\t\t\t\"time\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a datetime. */\n\tpublic asDatetime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.datetimeFormat,\n\t\t\t\"medium\",\n\t\t\t\"datetime\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Returns the value as a UNIX timestamp (seconds since epoch). */\n\tpublic asTimestamp(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\treturn String(Math.floor(date.getTime() / 1000))\n\t}\n\n\t/**\n\t * Formats the value as the time interval between a date and now in human readable form.\n\t * Uses Intl.RelativeTimeFormat (built-in in Node.js / browsers).\n\t */\n\tpublic asRelativeTime(value: unknown, referenceTime?: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\n\t\tconst date = normalizeDate(value)\n\t\tconst ref = referenceTime ? normalizeDate(referenceTime) : new Date()\n\t\tconst diffMs = date.getTime() - ref.getTime()\n\t\tconst diffSec = Math.round(diffMs / 1000)\n\n\t\tconst rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: \"auto\" })\n\n\t\tconst absSec = Math.abs(diffSec)\n\t\tif (absSec < 60) return rtf.format(diffSec, \"second\")\n\t\tif (absSec < 3600) return rtf.format(Math.round(diffSec / 60), \"minute\")\n\t\tif (absSec < 86400) return rtf.format(Math.round(diffSec / 3600), \"hour\")\n\t\tif (absSec < 2592000) return rtf.format(Math.round(diffSec / 86400), \"day\")\n\t\tif (absSec < 31536000)\n\t\t\treturn rtf.format(Math.round(diffSec / 2592000), \"month\")\n\t\treturn rtf.format(Math.round(diffSec / 31536000), \"year\")\n\t}\n\n\t/**\n\t * Represents the value as duration in human readable format.\n\t * Example: 5400 -> \"1 hour, 30 minutes\"\n\t */\n\tpublic asDuration(value: unknown, implode?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet seconds = Math.abs(normalizeNumber(value))\n\t\tconst separator = implode ?? \", \"\n\n\t\tif (seconds === 0) return this.getDurationLabel(\"second\", 0)\n\n\t\tconst units: Array<{ unit: string; divisor: number }> = [\n\t\t\t{ unit: \"year\", divisor: 31536000 },\n\t\t\t{ unit: \"month\", divisor: 2592000 },\n\t\t\t{ unit: \"day\", divisor: 86400 },\n\t\t\t{ unit: \"hour\", divisor: 3600 },\n\t\t\t{ unit: \"minute\", divisor: 60 },\n\t\t\t{ unit: \"second\", divisor: 1 },\n\t\t]\n\n\t\tconst parts: string[] = []\n\t\tfor (const { unit, divisor } of units) {\n\t\t\tif (seconds >= divisor) {\n\t\t\t\tconst count = Math.floor(seconds / divisor)\n\t\t\t\tseconds %= divisor\n\t\t\t\tparts.push(this.getDurationLabel(unit, count))\n\t\t\t}\n\t\t}\n\n\t\treturn parts.join(separator)\n\t}\n\n\t// ─── Size & Measurement ──────────────────────────────────────────\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kilobytes\"). */\n\tpublic asSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"long\")\n\t}\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kB\"). */\n\tpublic asShortSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"short\")\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 meters\"). */\n\tpublic asLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 m\"). */\n\tpublic asShortLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"short\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kilograms\"). */\n\tpublic asWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kg\"). */\n\tpublic asShortWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"short\", decimals)\n\t}\n\n\t// ─── Utility Methods ─────────────────────────────────────────────\n\n\t/**\n\t * Abbreviate a large number with locale-aware suffixes.\n\t * e.g. 1500000 -> \"1.5 Million\" (en) or \"1,5 Trieu\" (vi).\n\t */\n\tpublic asNumberShort(value: unknown, options?: NumberShortOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst absNum = Math.abs(num)\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst fallback = options?.fallback ?? \"currency\"\n\t\tconst addSpace = options?.spaceBefore ?? false\n\t\tconst config = getNumberShortConfig(this.locale)\n\n\t\tfor (const { value: threshold, suffix } of config.thresholds) {\n\t\t\tif (absNum >= threshold) {\n\t\t\t\tconst short =\n\t\t\t\t\tMath.round((num / threshold) * 10 ** decimals) / 10 ** decimals\n\t\t\t\tconst sep = addSpace && !suffix.startsWith(\" \") ? \" \" : \"\"\n\t\t\t\treturn `${this.asDecimal(short, decimals)}${sep}${suffix}`\n\t\t\t}\n\t\t}\n\n\t\tswitch (fallback) {\n\t\t\tcase \"decimal\":\n\t\t\t\treturn this.asDecimal(num, decimals)\n\t\t\tcase \"integer\":\n\t\t\t\treturn this.asInteger(num)\n\t\t\tdefault:\n\t\t\t\treturn this.asCurrency(num)\n\t\t}\n\t}\n\n\t/**\n\t * Format the GPS (great-circle) distance between two coordinates.\n\t * Returns a human-readable string with unit suffix.\n\t */\n\tpublic asGpsDistance(\n\t\tlatFrom: number,\n\t\tlonFrom: number,\n\t\tlatTo: number,\n\t\tlonTo: number,\n\t\toptions?: GpsDistanceOptions,\n\t): string {\n\t\tconst earthRadius = options?.earthRadius ?? 6_371_000\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst rawUnit = options?.unit ?? \"auto\"\n\t\tconst meters = Formatter.gpsDistance(\n\t\t\tlatFrom,\n\t\t\tlonFrom,\n\t\t\tlatTo,\n\t\t\tlonTo,\n\t\t\tearthRadius,\n\t\t)\n\n\t\tlet value: number\n\t\tlet unit: string\n\t\tif (rawUnit === \"mi\") {\n\t\t\tvalue = meters / 1609.344\n\t\t\tunit = \"mi\"\n\t\t} else if (rawUnit === \"km\") {\n\t\t\tvalue = meters / 1000\n\t\t\tunit = \"km\"\n\t\t} else if (rawUnit === \"m\") {\n\t\t\tvalue = meters\n\t\t\tunit = \"m\"\n\t\t} else {\n\t\t\t// auto: use km if >= 1000m, otherwise m\n\t\t\tif (meters >= 1000) {\n\t\t\t\tvalue = meters / 1000\n\t\t\t\tunit = \"km\"\n\t\t\t} else {\n\t\t\t\tvalue = meters\n\t\t\t\tunit = \"m\"\n\t\t\t}\n\t\t}\n\n\t\treturn `${this.asDecimal(value, decimals)} ${unit}`\n\t}\n\n\t/** Haversine formula: returns distance in meters between two GPS coordinates. */\n\tprivate static gpsDistance(\n\t\tlatitudeFrom: number,\n\t\tlongitudeFrom: number,\n\t\tlatitudeTo: number,\n\t\tlongitudeTo: number,\n\t\tearthRadius = 6_371_000,\n\t): number {\n\t\tconst toRad = (deg: number) => (deg * Math.PI) / 180\n\n\t\tconst latFrom = toRad(latitudeFrom)\n\t\tconst latTo = toRad(latitudeTo)\n\t\tconst deltaLat = toRad(latitudeTo - latitudeFrom)\n\t\tconst deltaLon = toRad(longitudeTo - longitudeFrom)\n\n\t\tconst a =\n\t\t\tMath.sin(deltaLat / 2) ** 2 +\n\t\t\tMath.cos(latFrom) * Math.cos(latTo) * Math.sin(deltaLon / 2) ** 2\n\t\tconst c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))\n\n\t\treturn earthRadius * c\n\t}\n\n\t/**\n\t * Mask a string value, showing only the first and last N characters.\n\t * Instance method with options support.\n\t */\n\tpublic asMaskedValue(value: unknown, options?: MaskOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst str = String(value)\n\t\treturn Formatter.getMaskedValue(\n\t\t\tstr,\n\t\t\toptions?.startVisible ?? 4,\n\t\t\toptions?.endVisible ?? 3,\n\t\t\toptions?.maskChar ?? \"X\",\n\t\t)\n\t}\n\n\t/** Core masking logic used by asMaskedValue(). */\n\tprivate static getMaskedValue(\n\t\tvalue: string,\n\t\tstartVisible = 4,\n\t\tendVisible = 3,\n\t\tmaskChar = \"X\",\n\t): string {\n\t\tif (!value) return \"\"\n\t\tconst len = value.length\n\n\t\tif (len <= startVisible + endVisible) return value\n\n\t\tconst start = value.slice(0, startVisible)\n\t\tconst end = value.slice(len - endVisible)\n\t\tconst masked = maskChar.repeat(len - startVisible - endVisible)\n\n\t\treturn `${start}${masked}${end}`\n\t}\n\n\t// ─── Private helpers ──────────────────────────────────────────────\n\n\t/**\n\t * Format bytes into the most appropriate size unit.\n\t * Supports both base-1024 (binary) and base-1000 (decimal).\n\t */\n\tprivate formatBytes(\n\t\tvalue: unknown,\n\t\tdecimals: number | undefined,\n\t\twidth: FormatWidth,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet bytes = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\t\tconst base = this.sizeFormatBase\n\n\t\tconst isNegative = bytes < 0\n\t\tbytes = Math.abs(bytes)\n\n\t\ttype SizeUnit = {\n\t\t\tthreshold: number\n\t\t\tlong: string\n\t\t\tshort: string\n\t\t}\n\n\t\tconst units1024: SizeUnit[] = [\n\t\t\t{ threshold: 1099511627776, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1073741824, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1048576, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1024, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units1000: SizeUnit[] = [\n\t\t\t{ threshold: 1000000000000, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1000000000, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1000000, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1000, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units = base === 1024 ? units1024 : units1000\n\n\t\tfor (const unit of units) {\n\t\t\tif (bytes >= unit.threshold && unit.threshold > 0) {\n\t\t\t\tconst val = bytes / unit.threshold\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? unit.long : unit.short\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\tconst label = width === \"long\" ? \"bytes\" : \"B\"\n\t\treturn `${sign}${Math.round(bytes)} ${label}`\n\t}\n\n\t/**\n\t * Format a measurement value (length or mass).\n\t * Automatically selects the most appropriate unit based on value magnitude.\n\t */\n\tprivate formatMeasure(\n\t\tvalue: unknown,\n\t\ttype: \"length\" | \"mass\",\n\t\twidth: FormatWidth,\n\t\tdecimals?: number,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst configs = this.getMeasureUnits(type)\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\n\t\t// Find the best-fitting unit (largest unit where value >= 1)\n\t\tfor (let i = configs.length - 1; i >= 0; i--) {\n\t\t\tconst config = configs[i]\n\t\t\tif (!config) continue\n\t\t\tif (absNum >= config.factor || i === 0) {\n\t\t\t\tconst val = absNum / config.factor\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? config.longLabel : config.shortLabel\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\treturn String(num)\n\t}\n\n\t/** Get measurement unit configs for the configured system (metric/imperial). */\n\tprivate getMeasureUnits(type: \"length\" | \"mass\"): MeasureUnitConfig[] {\n\t\tif (type === \"length\") {\n\t\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\t\treturn [\n\t\t\t\t\t{ factor: 1, longLabel: \"inches\", shortLabel: \"in\" },\n\t\t\t\t\t{ factor: 12, longLabel: \"feet\", shortLabel: \"ft\" },\n\t\t\t\t\t{ factor: 36, longLabel: \"yards\", shortLabel: \"yd\" },\n\t\t\t\t\t{ factor: 63360, longLabel: \"miles\", shortLabel: \"mi\" },\n\t\t\t\t]\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"millimeters\", shortLabel: \"mm\" },\n\t\t\t\t{ factor: 1000, longLabel: \"meters\", shortLabel: \"m\" },\n\t\t\t\t{ factor: 1000000, longLabel: \"kilometers\", shortLabel: \"km\" },\n\t\t\t]\n\t\t}\n\n\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"grains\", shortLabel: \"gr\" },\n\t\t\t\t{ factor: 437.5, longLabel: \"ounces\", shortLabel: \"oz\" },\n\t\t\t\t{ factor: 7000, longLabel: \"pounds\", shortLabel: \"lb\" },\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t{ factor: 1, longLabel: \"grams\", shortLabel: \"g\" },\n\t\t\t{ factor: 1000, longLabel: \"kilograms\", shortLabel: \"kg\" },\n\t\t\t{ factor: 1000000, longLabel: \"tons\", shortLabel: \"t\" },\n\t\t]\n\t}\n\n\t/** Format the numeric part of a result using locale-aware Intl. */\n\tprivate formatNumberPart(num: number, digits: number): string {\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: 0,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Create a locale-aware duration label using Intl unit formatting. */\n\tprivate getDurationLabel(unit: string, count: number): string {\n\t\ttry {\n\t\t\tconst intlUnit = unit === \"month\" ? \"month\" : unit\n\t\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\t\tstyle: \"unit\",\n\t\t\t\tunit: intlUnit,\n\t\t\t\tunitDisplay: \"long\",\n\t\t\t}).format(count)\n\t\t} catch {\n\t\t\tconst plural = count !== 1 ? \"s\" : \"\"\n\t\t\treturn `${count} ${unit}${plural}`\n\t\t}\n\t}\n\n\t/** English ordinal suffix fallback. */\n\tprivate getOrdinalSuffixEn(n: number): string {\n\t\tconst abs = Math.abs(n)\n\t\tconst mod100 = abs % 100\n\t\tif (mod100 >= 11 && mod100 <= 13) return \"th\"\n\t\tswitch (abs % 10) {\n\t\t\tcase 1:\n\t\t\t\treturn \"st\"\n\t\t\tcase 2:\n\t\t\t\treturn \"nd\"\n\t\t\tcase 3:\n\t\t\t\treturn \"rd\"\n\t\t\tdefault:\n\t\t\t\treturn \"th\"\n\t\t}\n\t}\n}\n","import { Formatter } from \"./formatter.js\"\nimport type { FormatterOptions } from \"./types.js\"\nimport { parseDate } from \"./utils.js\"\n\n/**\n * Global singleton Formatter instance.\n *\n * Usage: import once at app entry, configure once, then use `formatter` everywhere.\n *\n * ```ts\n * // main.ts (once)\n * import { configureFormatter } from \"@template/helpers\"\n * configureFormatter({ locale: \"vi-VN\", currencyCode: \"VND\" })\n *\n * // any-page.tsx (no setup needed)\n * import { formatter } from \"@template/helpers\"\n * formatter.asCurrency(1234567)\n * ```\n */\nlet instance = new Formatter()\n\n/** The global Formatter singleton. Ready to use after `configureFormatter()`. */\nexport const formatter: Formatter = new Proxy({} as Formatter, {\n\tget(_target, prop, receiver) {\n\t\treturn Reflect.get(instance, prop, receiver)\n\t},\n})\n\n/**\n * Configure the global formatter once (typically at app bootstrap).\n * Replaces the internal instance - all existing `formatter` references\n * automatically pick up the new config via the proxy.\n */\nexport function configureFormatter(options: FormatterOptions): void {\n\tinstance = new Formatter(options)\n}\n\n/**\n * Format a date value safely for display using the global formatter.\n * Returns `fallback` (default: \"\\u2014\") for null, undefined, empty, or invalid dates.\n * Handles timezone-naive ISO strings by treating them as UTC.\n */\nexport function formatDate(\n\tdate: string | Date | null | undefined,\n\tfallback = \"\\u2014\",\n): string {\n\tif (!date) return fallback\n\ttry {\n\t\tconst d = parseDate(date)\n\t\tif (Number.isNaN(d.getTime())) return fallback\n\t\treturn formatter.asDate(d)\n\t} catch {\n\t\treturn fallback\n\t}\n}\n\n/**\n * Format a value as a percentage for display using the global formatter.\n * - If the string already contains \"%\", returns it as-is.\n * - Values between 0 and 1 (exclusive) are treated as ratios (multiplied by 100).\n * - Returns `fallback` (default: \"\\u2014\") for null/undefined/unparseable values.\n */\nexport function formatPercent(\n\tvalue: number | string | null | undefined,\n\tfallback = \"\\u2014\",\n): string {\n\tif (value === null || value === undefined) return fallback\n\tif (typeof value === \"string\") {\n\t\tif (value.trim() === \"\") return fallback\n\t\tif (value.includes(\"%\")) return value\n\t\tconst num = Number(value)\n\t\tif (Number.isNaN(num)) return value || fallback\n\t\tconst pct = num > 0 && num <= 1 ? num * 100 : num\n\t\treturn `${formatter.asDecimal(pct, 2)}%`\n\t}\n\tconst pct = value > 0 && value <= 1 ? value * 100 : value\n\treturn `${formatter.asDecimal(pct, 2)}%`\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED