@wikytam/helpers 1.0.0 → 1.0.1
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 +37 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +37 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -396,6 +396,16 @@ function applyCustomSeparators(formatted, locale, customDecimal, customThousand)
|
|
|
396
396
|
}
|
|
397
397
|
//#endregion
|
|
398
398
|
//#region src/formatter.ts
|
|
399
|
+
/** Returns true for null or undefined only. */
|
|
400
|
+
function isNullish(value) {
|
|
401
|
+
return value === null || value === void 0;
|
|
402
|
+
}
|
|
403
|
+
/** Returns true for null, undefined, or empty/whitespace-only strings. */
|
|
404
|
+
function isBlank(value) {
|
|
405
|
+
if (value === null || value === void 0) return true;
|
|
406
|
+
if (typeof value === "string" && value.trim() === "") return true;
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
399
409
|
/**
|
|
400
410
|
* TypeScript port of yii\i18n\Formatter.
|
|
401
411
|
*
|
|
@@ -441,7 +451,7 @@ var Formatter = class Formatter {
|
|
|
441
451
|
* Supports both string and tuple `[formatName, ...params]` signatures.
|
|
442
452
|
*/
|
|
443
453
|
format(value, type) {
|
|
444
|
-
if (value
|
|
454
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
445
455
|
const formatName = Array.isArray(type) ? type[0] : type;
|
|
446
456
|
const params = Array.isArray(type) ? type.slice(1) : [];
|
|
447
457
|
const methodName = `as${formatName.charAt(0).toUpperCase()}${formatName.slice(1)}`;
|
|
@@ -451,12 +461,12 @@ var Formatter = class Formatter {
|
|
|
451
461
|
}
|
|
452
462
|
/** Returns the value as-is without any formatting. */
|
|
453
463
|
asRaw(value) {
|
|
454
|
-
if (value
|
|
464
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
455
465
|
return String(value);
|
|
456
466
|
}
|
|
457
467
|
/** Formats the value as HTML-encoded plain text. */
|
|
458
468
|
asText(value) {
|
|
459
|
-
if (value
|
|
469
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
460
470
|
return escapeHtml(String(value));
|
|
461
471
|
}
|
|
462
472
|
/**
|
|
@@ -465,7 +475,7 @@ var Formatter = class Formatter {
|
|
|
465
475
|
* Consecutive newlines produce multiple `<br />` tags.
|
|
466
476
|
*/
|
|
467
477
|
asNtext(value) {
|
|
468
|
-
if (value
|
|
478
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
469
479
|
return escapeHtml(String(value)).replace(/\r\n/g, "<br />").replace(/[\r\n]/g, "<br />");
|
|
470
480
|
}
|
|
471
481
|
/**
|
|
@@ -473,7 +483,7 @@ var Formatter = class Formatter {
|
|
|
473
483
|
* Supports configurable wrapper tag and inline line-break conversion.
|
|
474
484
|
*/
|
|
475
485
|
asParagraphs(value, options) {
|
|
476
|
-
if (value
|
|
486
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
477
487
|
const tag = options?.tag ?? "p";
|
|
478
488
|
const lineBreaks = options?.lineBreaks ?? false;
|
|
479
489
|
return String(value).replace(/\r\n/g, "\n").replace(/\r/g, "\n").split(/\n\s*\n/).map((p) => {
|
|
@@ -488,7 +498,7 @@ var Formatter = class Formatter {
|
|
|
488
498
|
* Without config, the value is returned as-is (caller is responsible for safety).
|
|
489
499
|
*/
|
|
490
500
|
asHtml(value, sanitize) {
|
|
491
|
-
if (value
|
|
501
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
492
502
|
const html = String(value);
|
|
493
503
|
if (!sanitize) return html;
|
|
494
504
|
return Formatter.sanitizeHtml(html, sanitize);
|
|
@@ -529,7 +539,7 @@ var Formatter = class Formatter {
|
|
|
529
539
|
* Validates email format - returns escaped plain text for invalid emails.
|
|
530
540
|
*/
|
|
531
541
|
asEmail(value, options) {
|
|
532
|
-
if (value
|
|
542
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
533
543
|
const email = String(value);
|
|
534
544
|
if (!Formatter.isValidEmail(email)) return escapeHtml(email);
|
|
535
545
|
const params = [];
|
|
@@ -549,7 +559,7 @@ var Formatter = class Formatter {
|
|
|
549
559
|
* Prepends `http://` when no recognized scheme is present.
|
|
550
560
|
*/
|
|
551
561
|
asUrl(value, options) {
|
|
552
|
-
if (value
|
|
562
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
553
563
|
const url = String(value);
|
|
554
564
|
const href = /^(https?|ftps?|mailto):/i.test(url) ? url : `http://${url}`;
|
|
555
565
|
const target = options?.target ?? "_blank";
|
|
@@ -564,7 +574,7 @@ var Formatter = class Formatter {
|
|
|
564
574
|
* Supports width, height, CSS class, and loading strategy attributes.
|
|
565
575
|
*/
|
|
566
576
|
asImage(value, options) {
|
|
567
|
-
if (value
|
|
577
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
568
578
|
const src = String(value);
|
|
569
579
|
const alt = options?.alt ?? "";
|
|
570
580
|
const attrs = [`src="${escapeHtml(src)}"`, `alt="${escapeHtml(alt)}"`];
|
|
@@ -576,12 +586,12 @@ var Formatter = class Formatter {
|
|
|
576
586
|
}
|
|
577
587
|
/** Formats the value as a boolean using the configured booleanFormat labels. */
|
|
578
588
|
asBoolean(value) {
|
|
579
|
-
if (value
|
|
589
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
580
590
|
return value ? this.booleanFormat[1] : this.booleanFormat[0];
|
|
581
591
|
}
|
|
582
592
|
/** Formats the value as an integer by removing decimal digits without rounding. */
|
|
583
593
|
asInteger(value) {
|
|
584
|
-
if (value
|
|
594
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
585
595
|
const num = normalizeNumber(value);
|
|
586
596
|
const intVal = Math.trunc(num);
|
|
587
597
|
return applyCustomSeparators(new Intl.NumberFormat(this.locale, {
|
|
@@ -591,7 +601,7 @@ var Formatter = class Formatter {
|
|
|
591
601
|
}
|
|
592
602
|
/** Formats the value as a decimal number. */
|
|
593
603
|
asDecimal(value, decimals) {
|
|
594
|
-
if (value
|
|
604
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
595
605
|
const num = normalizeNumber(value);
|
|
596
606
|
const digits = decimals ?? this.defaultDecimalDigits ?? 2;
|
|
597
607
|
return applyCustomSeparators(new Intl.NumberFormat(this.locale, {
|
|
@@ -601,7 +611,7 @@ var Formatter = class Formatter {
|
|
|
601
611
|
}
|
|
602
612
|
/** Formats the value as a percent number with "%" sign. */
|
|
603
613
|
asPercent(value, decimals) {
|
|
604
|
-
if (value
|
|
614
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
605
615
|
const num = normalizeNumber(value);
|
|
606
616
|
const digits = decimals ?? this.defaultDecimalDigits ?? 0;
|
|
607
617
|
return applyCustomSeparators(new Intl.NumberFormat(this.locale, {
|
|
@@ -612,7 +622,7 @@ var Formatter = class Formatter {
|
|
|
612
622
|
}
|
|
613
623
|
/** Formats the value as a currency number using ISO 4217 codes. */
|
|
614
624
|
asCurrency(value, currency) {
|
|
615
|
-
if (value
|
|
625
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
616
626
|
const num = normalizeNumber(value);
|
|
617
627
|
const code = currency ?? this.currencyCode;
|
|
618
628
|
return applyCustomSeparators(new Intl.NumberFormat(this.locale, {
|
|
@@ -622,7 +632,7 @@ var Formatter = class Formatter {
|
|
|
622
632
|
}
|
|
623
633
|
/** Formats the value as a scientific number (e-notation). */
|
|
624
634
|
asScientific(value, decimals) {
|
|
625
|
-
if (value
|
|
635
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
626
636
|
const num = normalizeNumber(value);
|
|
627
637
|
const digits = decimals ?? this.defaultDecimalDigits ?? 2;
|
|
628
638
|
return new Intl.NumberFormat(this.locale, {
|
|
@@ -636,7 +646,7 @@ var Formatter = class Formatter {
|
|
|
636
646
|
* Supports multiple locales via the locales/ registry.
|
|
637
647
|
*/
|
|
638
648
|
asSpellout(value) {
|
|
639
|
-
if (value
|
|
649
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
640
650
|
const num = normalizeNumber(value);
|
|
641
651
|
const spellout = getSpellout(this.locale);
|
|
642
652
|
if (num === 0) return spellout.zeroWord;
|
|
@@ -657,7 +667,7 @@ var Formatter = class Formatter {
|
|
|
657
667
|
* through `Formatter.registerOrdinalSuffixes()`.
|
|
658
668
|
*/
|
|
659
669
|
asOrdinal(value) {
|
|
660
|
-
if (value
|
|
670
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
661
671
|
const num = Math.trunc(normalizeNumber(value));
|
|
662
672
|
try {
|
|
663
673
|
const rule = new Intl.PluralRules(this.locale, { type: "ordinal" }).select(num);
|
|
@@ -700,7 +710,7 @@ var Formatter = class Formatter {
|
|
|
700
710
|
}
|
|
701
711
|
/** Formats the value as a date. */
|
|
702
712
|
asDate(value, format) {
|
|
703
|
-
if (value
|
|
713
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
704
714
|
const date = normalizeDate(value);
|
|
705
715
|
const resolved = resolveDateFormat(format ?? this.dateFormat, "medium", "date");
|
|
706
716
|
return new Intl.DateTimeFormat(this.locale, {
|
|
@@ -710,7 +720,7 @@ var Formatter = class Formatter {
|
|
|
710
720
|
}
|
|
711
721
|
/** Formats the value as a time. */
|
|
712
722
|
asTime(value, format) {
|
|
713
|
-
if (value
|
|
723
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
714
724
|
const date = normalizeDate(value);
|
|
715
725
|
const resolved = resolveDateFormat(format ?? this.timeFormat, "medium", "time");
|
|
716
726
|
return new Intl.DateTimeFormat(this.locale, {
|
|
@@ -720,7 +730,7 @@ var Formatter = class Formatter {
|
|
|
720
730
|
}
|
|
721
731
|
/** Formats the value as a datetime. */
|
|
722
732
|
asDatetime(value, format) {
|
|
723
|
-
if (value
|
|
733
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
724
734
|
const date = normalizeDate(value);
|
|
725
735
|
const resolved = resolveDateFormat(format ?? this.datetimeFormat, "medium", "datetime");
|
|
726
736
|
return new Intl.DateTimeFormat(this.locale, {
|
|
@@ -730,7 +740,7 @@ var Formatter = class Formatter {
|
|
|
730
740
|
}
|
|
731
741
|
/** Returns the value as a UNIX timestamp (seconds since epoch). */
|
|
732
742
|
asTimestamp(value) {
|
|
733
|
-
if (value
|
|
743
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
734
744
|
const date = normalizeDate(value);
|
|
735
745
|
return String(Math.floor(date.getTime() / 1e3));
|
|
736
746
|
}
|
|
@@ -739,7 +749,7 @@ var Formatter = class Formatter {
|
|
|
739
749
|
* Uses Intl.RelativeTimeFormat (built-in in Node.js / browsers).
|
|
740
750
|
*/
|
|
741
751
|
asRelativeTime(value, referenceTime) {
|
|
742
|
-
if (value
|
|
752
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
743
753
|
const date = normalizeDate(value);
|
|
744
754
|
const ref = referenceTime ? normalizeDate(referenceTime) : /* @__PURE__ */ new Date();
|
|
745
755
|
const diffMs = date.getTime() - ref.getTime();
|
|
@@ -758,7 +768,7 @@ var Formatter = class Formatter {
|
|
|
758
768
|
* Example: 5400 -> "1 hour, 30 minutes"
|
|
759
769
|
*/
|
|
760
770
|
asDuration(value, implode) {
|
|
761
|
-
if (value
|
|
771
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
762
772
|
let seconds = Math.abs(normalizeNumber(value));
|
|
763
773
|
const separator = implode ?? ", ";
|
|
764
774
|
if (seconds === 0) return this.getDurationLabel("second", 0);
|
|
@@ -825,7 +835,7 @@ var Formatter = class Formatter {
|
|
|
825
835
|
* e.g. 1500000 -> "1.5 Million" (en) or "1,5 Trieu" (vi).
|
|
826
836
|
*/
|
|
827
837
|
asNumberShort(value, options) {
|
|
828
|
-
if (value
|
|
838
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
829
839
|
const num = normalizeNumber(value);
|
|
830
840
|
const absNum = Math.abs(num);
|
|
831
841
|
const decimals = options?.decimals ?? 1;
|
|
@@ -887,7 +897,7 @@ var Formatter = class Formatter {
|
|
|
887
897
|
* Instance method with options support.
|
|
888
898
|
*/
|
|
889
899
|
asMaskedValue(value, options) {
|
|
890
|
-
if (value
|
|
900
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
891
901
|
const str = String(value);
|
|
892
902
|
return Formatter.getMaskedValue(str, options?.startVisible ?? 4, options?.endVisible ?? 3, options?.maskChar ?? "X");
|
|
893
903
|
}
|
|
@@ -905,7 +915,7 @@ var Formatter = class Formatter {
|
|
|
905
915
|
* Supports both base-1024 (binary) and base-1000 (decimal).
|
|
906
916
|
*/
|
|
907
917
|
formatBytes(value, decimals, width) {
|
|
908
|
-
if (value
|
|
918
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
909
919
|
let bytes = normalizeNumber(value);
|
|
910
920
|
const digits = decimals ?? this.defaultDecimalDigits ?? 2;
|
|
911
921
|
const base = this.sizeFormatBase;
|
|
@@ -975,7 +985,7 @@ var Formatter = class Formatter {
|
|
|
975
985
|
* Automatically selects the most appropriate unit based on value magnitude.
|
|
976
986
|
*/
|
|
977
987
|
formatMeasure(value, type, width, decimals) {
|
|
978
|
-
if (value
|
|
988
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
979
989
|
const num = normalizeNumber(value);
|
|
980
990
|
const digits = decimals ?? this.defaultDecimalDigits ?? 2;
|
|
981
991
|
const configs = this.getMeasureUnits(type);
|
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/**\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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 * 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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"}
|
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;;;;;;;;;;;
|
|
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;EACL;EACA;EACA;EACA,qBAAqB,KAAK;EAC1B,qBAAqB,KAAK;EAC1B,yBAAyB,KAAK;EAC9B;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eAAe;EACf;EAEP,YAAY,UAAS;;;;;EAwBd,OAAO,gBAAgB;;EAsBvB,MAAM;;EAMN,OAAO;;;;;;EAUP,QAAQ;;;;;EAUR,aAAa,gBAAgB,UAAU;;;;;;EAyBvC,OAAO,gBAAgB,WAAW;;;;;iBAW1B;;;;;;EAuDR,QAAQ,gBAAgB,UAAU;;iBAmB1B;;;;;;EASR,MAAM,gBAAgB,UAAU;;;;;EAqBhC,QAAQ,gBAAgB,UAAU;;EAoBlC,UAAU;;EAQV,UAAU;;EAmBV,UAAU,gBAAgB;;EAmB1B,UAAU,gBAAgB;;EAoB1B,WAAW,gBAAgB;;EAmB3B,aAAa,gBAAgB;;;;;EAgB7B,WAAW;;;;;;EA6BX,UAAU;;iBAoBF;;;;;SAiBD,wBACb,cACA,UAAU;;EAQJ,OACN,gBACA,kBAAkB,KAAK;;EAiBjB,OACN,gBACA,kBAAkB,KAAK;;EAiBjB,WACN,gBACA,kBAAkB,KAAK;;EAiBjB,YAAY;;;;;EAUZ,eAAe,gBAAgB;;;;;EAwB/B,WAAW,gBAAgB;;EA+B3B,OAAO,gBAAgB;;EAKvB,YAAY,gBAAgB;;EAK5B,SAAS,gBAAgB;;EAKzB,cAAc,gBAAgB;;EAK9B,SAAS,gBAAgB;;EAKzB,cAAc,gBAAgB;;;;;EAU9B,cAAc,gBAAgB,UAAU;;;;;EAgCxC,cACN,iBACA,iBACA,eACA,eACA,UAAU;;iBAuCI;;;;;EA0BR,cAAc,gBAAgB,UAAU;;iBAYhC;;;;;UAwBP;;;;;UAwDA;;UA+BA;;UAgCA;;UAeA;;UAeA;;;;;qBC53BI,WAAS;;;;;;wBAWN,mBAAmB,SAAS;;;;;;;UC5B3B;;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;;;;;wBAuB/B,gBAAgB"}
|
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;;;;;;;;;;;
|
|
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;EACL;EACA;EACA;EACA,qBAAqB,KAAK;EAC1B,qBAAqB,KAAK;EAC1B,yBAAyB,KAAK;EAC9B;EACA;EACA;EACA;EACA;EACA;EACA;EACA,eAAe;EACf;EAEP,YAAY,UAAS;;;;;EAwBd,OAAO,gBAAgB;;EAsBvB,MAAM;;EAMN,OAAO;;;;;;EAUP,QAAQ;;;;;EAUR,aAAa,gBAAgB,UAAU;;;;;;EAyBvC,OAAO,gBAAgB,WAAW;;;;;iBAW1B;;;;;;EAuDR,QAAQ,gBAAgB,UAAU;;iBAmB1B;;;;;;EASR,MAAM,gBAAgB,UAAU;;;;;EAqBhC,QAAQ,gBAAgB,UAAU;;EAoBlC,UAAU;;EAQV,UAAU;;EAmBV,UAAU,gBAAgB;;EAmB1B,UAAU,gBAAgB;;EAoB1B,WAAW,gBAAgB;;EAmB3B,aAAa,gBAAgB;;;;;EAgB7B,WAAW;;;;;;EA6BX,UAAU;;iBAoBF;;;;;SAiBD,wBACb,cACA,UAAU;;EAQJ,OACN,gBACA,kBAAkB,KAAK;;EAiBjB,OACN,gBACA,kBAAkB,KAAK;;EAiBjB,WACN,gBACA,kBAAkB,KAAK;;EAiBjB,YAAY;;;;;EAUZ,eAAe,gBAAgB;;;;;EAwB/B,WAAW,gBAAgB;;EA+B3B,OAAO,gBAAgB;;EAKvB,YAAY,gBAAgB;;EAK5B,SAAS,gBAAgB;;EAKzB,cAAc,gBAAgB;;EAK9B,SAAS,gBAAgB;;EAKzB,cAAc,gBAAgB;;;;;EAU9B,cAAc,gBAAgB,UAAU;;;;;EAgCxC,cACN,iBACA,iBACA,eACA,eACA,UAAU;;iBAuCI;;;;;EA0BR,cAAc,gBAAgB,UAAU;;iBAYhC;;;;;UAwBP;;;;;UAwDA;;UA+BA;;UAgCA;;UAeA;;UAeA;;;;;qBC53BI,WAAS;;;;;;wBAWN,mBAAmB,SAAS;;;;;;;UC5B3B;;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;;;;;wBAuB/B,gBAAgB"}
|
package/dist/index.mjs
CHANGED
|
@@ -395,6 +395,16 @@ function applyCustomSeparators(formatted, locale, customDecimal, customThousand)
|
|
|
395
395
|
}
|
|
396
396
|
//#endregion
|
|
397
397
|
//#region src/formatter.ts
|
|
398
|
+
/** Returns true for null or undefined only. */
|
|
399
|
+
function isNullish(value) {
|
|
400
|
+
return value === null || value === void 0;
|
|
401
|
+
}
|
|
402
|
+
/** Returns true for null, undefined, or empty/whitespace-only strings. */
|
|
403
|
+
function isBlank(value) {
|
|
404
|
+
if (value === null || value === void 0) return true;
|
|
405
|
+
if (typeof value === "string" && value.trim() === "") return true;
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
398
408
|
/**
|
|
399
409
|
* TypeScript port of yii\i18n\Formatter.
|
|
400
410
|
*
|
|
@@ -440,7 +450,7 @@ var Formatter = class Formatter {
|
|
|
440
450
|
* Supports both string and tuple `[formatName, ...params]` signatures.
|
|
441
451
|
*/
|
|
442
452
|
format(value, type) {
|
|
443
|
-
if (value
|
|
453
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
444
454
|
const formatName = Array.isArray(type) ? type[0] : type;
|
|
445
455
|
const params = Array.isArray(type) ? type.slice(1) : [];
|
|
446
456
|
const methodName = `as${formatName.charAt(0).toUpperCase()}${formatName.slice(1)}`;
|
|
@@ -450,12 +460,12 @@ var Formatter = class Formatter {
|
|
|
450
460
|
}
|
|
451
461
|
/** Returns the value as-is without any formatting. */
|
|
452
462
|
asRaw(value) {
|
|
453
|
-
if (value
|
|
463
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
454
464
|
return String(value);
|
|
455
465
|
}
|
|
456
466
|
/** Formats the value as HTML-encoded plain text. */
|
|
457
467
|
asText(value) {
|
|
458
|
-
if (value
|
|
468
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
459
469
|
return escapeHtml(String(value));
|
|
460
470
|
}
|
|
461
471
|
/**
|
|
@@ -464,7 +474,7 @@ var Formatter = class Formatter {
|
|
|
464
474
|
* Consecutive newlines produce multiple `<br />` tags.
|
|
465
475
|
*/
|
|
466
476
|
asNtext(value) {
|
|
467
|
-
if (value
|
|
477
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
468
478
|
return escapeHtml(String(value)).replace(/\r\n/g, "<br />").replace(/[\r\n]/g, "<br />");
|
|
469
479
|
}
|
|
470
480
|
/**
|
|
@@ -472,7 +482,7 @@ var Formatter = class Formatter {
|
|
|
472
482
|
* Supports configurable wrapper tag and inline line-break conversion.
|
|
473
483
|
*/
|
|
474
484
|
asParagraphs(value, options) {
|
|
475
|
-
if (value
|
|
485
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
476
486
|
const tag = options?.tag ?? "p";
|
|
477
487
|
const lineBreaks = options?.lineBreaks ?? false;
|
|
478
488
|
return String(value).replace(/\r\n/g, "\n").replace(/\r/g, "\n").split(/\n\s*\n/).map((p) => {
|
|
@@ -487,7 +497,7 @@ var Formatter = class Formatter {
|
|
|
487
497
|
* Without config, the value is returned as-is (caller is responsible for safety).
|
|
488
498
|
*/
|
|
489
499
|
asHtml(value, sanitize) {
|
|
490
|
-
if (value
|
|
500
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
491
501
|
const html = String(value);
|
|
492
502
|
if (!sanitize) return html;
|
|
493
503
|
return Formatter.sanitizeHtml(html, sanitize);
|
|
@@ -528,7 +538,7 @@ var Formatter = class Formatter {
|
|
|
528
538
|
* Validates email format - returns escaped plain text for invalid emails.
|
|
529
539
|
*/
|
|
530
540
|
asEmail(value, options) {
|
|
531
|
-
if (value
|
|
541
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
532
542
|
const email = String(value);
|
|
533
543
|
if (!Formatter.isValidEmail(email)) return escapeHtml(email);
|
|
534
544
|
const params = [];
|
|
@@ -548,7 +558,7 @@ var Formatter = class Formatter {
|
|
|
548
558
|
* Prepends `http://` when no recognized scheme is present.
|
|
549
559
|
*/
|
|
550
560
|
asUrl(value, options) {
|
|
551
|
-
if (value
|
|
561
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
552
562
|
const url = String(value);
|
|
553
563
|
const href = /^(https?|ftps?|mailto):/i.test(url) ? url : `http://${url}`;
|
|
554
564
|
const target = options?.target ?? "_blank";
|
|
@@ -563,7 +573,7 @@ var Formatter = class Formatter {
|
|
|
563
573
|
* Supports width, height, CSS class, and loading strategy attributes.
|
|
564
574
|
*/
|
|
565
575
|
asImage(value, options) {
|
|
566
|
-
if (value
|
|
576
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
567
577
|
const src = String(value);
|
|
568
578
|
const alt = options?.alt ?? "";
|
|
569
579
|
const attrs = [`src="${escapeHtml(src)}"`, `alt="${escapeHtml(alt)}"`];
|
|
@@ -575,12 +585,12 @@ var Formatter = class Formatter {
|
|
|
575
585
|
}
|
|
576
586
|
/** Formats the value as a boolean using the configured booleanFormat labels. */
|
|
577
587
|
asBoolean(value) {
|
|
578
|
-
if (value
|
|
588
|
+
if (isNullish(value)) return this.nullDisplay;
|
|
579
589
|
return value ? this.booleanFormat[1] : this.booleanFormat[0];
|
|
580
590
|
}
|
|
581
591
|
/** Formats the value as an integer by removing decimal digits without rounding. */
|
|
582
592
|
asInteger(value) {
|
|
583
|
-
if (value
|
|
593
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
584
594
|
const num = normalizeNumber(value);
|
|
585
595
|
const intVal = Math.trunc(num);
|
|
586
596
|
return applyCustomSeparators(new Intl.NumberFormat(this.locale, {
|
|
@@ -590,7 +600,7 @@ var Formatter = class Formatter {
|
|
|
590
600
|
}
|
|
591
601
|
/** Formats the value as a decimal number. */
|
|
592
602
|
asDecimal(value, decimals) {
|
|
593
|
-
if (value
|
|
603
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
594
604
|
const num = normalizeNumber(value);
|
|
595
605
|
const digits = decimals ?? this.defaultDecimalDigits ?? 2;
|
|
596
606
|
return applyCustomSeparators(new Intl.NumberFormat(this.locale, {
|
|
@@ -600,7 +610,7 @@ var Formatter = class Formatter {
|
|
|
600
610
|
}
|
|
601
611
|
/** Formats the value as a percent number with "%" sign. */
|
|
602
612
|
asPercent(value, decimals) {
|
|
603
|
-
if (value
|
|
613
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
604
614
|
const num = normalizeNumber(value);
|
|
605
615
|
const digits = decimals ?? this.defaultDecimalDigits ?? 0;
|
|
606
616
|
return applyCustomSeparators(new Intl.NumberFormat(this.locale, {
|
|
@@ -611,7 +621,7 @@ var Formatter = class Formatter {
|
|
|
611
621
|
}
|
|
612
622
|
/** Formats the value as a currency number using ISO 4217 codes. */
|
|
613
623
|
asCurrency(value, currency) {
|
|
614
|
-
if (value
|
|
624
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
615
625
|
const num = normalizeNumber(value);
|
|
616
626
|
const code = currency ?? this.currencyCode;
|
|
617
627
|
return applyCustomSeparators(new Intl.NumberFormat(this.locale, {
|
|
@@ -621,7 +631,7 @@ var Formatter = class Formatter {
|
|
|
621
631
|
}
|
|
622
632
|
/** Formats the value as a scientific number (e-notation). */
|
|
623
633
|
asScientific(value, decimals) {
|
|
624
|
-
if (value
|
|
634
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
625
635
|
const num = normalizeNumber(value);
|
|
626
636
|
const digits = decimals ?? this.defaultDecimalDigits ?? 2;
|
|
627
637
|
return new Intl.NumberFormat(this.locale, {
|
|
@@ -635,7 +645,7 @@ var Formatter = class Formatter {
|
|
|
635
645
|
* Supports multiple locales via the locales/ registry.
|
|
636
646
|
*/
|
|
637
647
|
asSpellout(value) {
|
|
638
|
-
if (value
|
|
648
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
639
649
|
const num = normalizeNumber(value);
|
|
640
650
|
const spellout = getSpellout(this.locale);
|
|
641
651
|
if (num === 0) return spellout.zeroWord;
|
|
@@ -656,7 +666,7 @@ var Formatter = class Formatter {
|
|
|
656
666
|
* through `Formatter.registerOrdinalSuffixes()`.
|
|
657
667
|
*/
|
|
658
668
|
asOrdinal(value) {
|
|
659
|
-
if (value
|
|
669
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
660
670
|
const num = Math.trunc(normalizeNumber(value));
|
|
661
671
|
try {
|
|
662
672
|
const rule = new Intl.PluralRules(this.locale, { type: "ordinal" }).select(num);
|
|
@@ -699,7 +709,7 @@ var Formatter = class Formatter {
|
|
|
699
709
|
}
|
|
700
710
|
/** Formats the value as a date. */
|
|
701
711
|
asDate(value, format) {
|
|
702
|
-
if (value
|
|
712
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
703
713
|
const date = normalizeDate(value);
|
|
704
714
|
const resolved = resolveDateFormat(format ?? this.dateFormat, "medium", "date");
|
|
705
715
|
return new Intl.DateTimeFormat(this.locale, {
|
|
@@ -709,7 +719,7 @@ var Formatter = class Formatter {
|
|
|
709
719
|
}
|
|
710
720
|
/** Formats the value as a time. */
|
|
711
721
|
asTime(value, format) {
|
|
712
|
-
if (value
|
|
722
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
713
723
|
const date = normalizeDate(value);
|
|
714
724
|
const resolved = resolveDateFormat(format ?? this.timeFormat, "medium", "time");
|
|
715
725
|
return new Intl.DateTimeFormat(this.locale, {
|
|
@@ -719,7 +729,7 @@ var Formatter = class Formatter {
|
|
|
719
729
|
}
|
|
720
730
|
/** Formats the value as a datetime. */
|
|
721
731
|
asDatetime(value, format) {
|
|
722
|
-
if (value
|
|
732
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
723
733
|
const date = normalizeDate(value);
|
|
724
734
|
const resolved = resolveDateFormat(format ?? this.datetimeFormat, "medium", "datetime");
|
|
725
735
|
return new Intl.DateTimeFormat(this.locale, {
|
|
@@ -729,7 +739,7 @@ var Formatter = class Formatter {
|
|
|
729
739
|
}
|
|
730
740
|
/** Returns the value as a UNIX timestamp (seconds since epoch). */
|
|
731
741
|
asTimestamp(value) {
|
|
732
|
-
if (value
|
|
742
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
733
743
|
const date = normalizeDate(value);
|
|
734
744
|
return String(Math.floor(date.getTime() / 1e3));
|
|
735
745
|
}
|
|
@@ -738,7 +748,7 @@ var Formatter = class Formatter {
|
|
|
738
748
|
* Uses Intl.RelativeTimeFormat (built-in in Node.js / browsers).
|
|
739
749
|
*/
|
|
740
750
|
asRelativeTime(value, referenceTime) {
|
|
741
|
-
if (value
|
|
751
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
742
752
|
const date = normalizeDate(value);
|
|
743
753
|
const ref = referenceTime ? normalizeDate(referenceTime) : /* @__PURE__ */ new Date();
|
|
744
754
|
const diffMs = date.getTime() - ref.getTime();
|
|
@@ -757,7 +767,7 @@ var Formatter = class Formatter {
|
|
|
757
767
|
* Example: 5400 -> "1 hour, 30 minutes"
|
|
758
768
|
*/
|
|
759
769
|
asDuration(value, implode) {
|
|
760
|
-
if (value
|
|
770
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
761
771
|
let seconds = Math.abs(normalizeNumber(value));
|
|
762
772
|
const separator = implode ?? ", ";
|
|
763
773
|
if (seconds === 0) return this.getDurationLabel("second", 0);
|
|
@@ -824,7 +834,7 @@ var Formatter = class Formatter {
|
|
|
824
834
|
* e.g. 1500000 -> "1.5 Million" (en) or "1,5 Trieu" (vi).
|
|
825
835
|
*/
|
|
826
836
|
asNumberShort(value, options) {
|
|
827
|
-
if (value
|
|
837
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
828
838
|
const num = normalizeNumber(value);
|
|
829
839
|
const absNum = Math.abs(num);
|
|
830
840
|
const decimals = options?.decimals ?? 1;
|
|
@@ -886,7 +896,7 @@ var Formatter = class Formatter {
|
|
|
886
896
|
* Instance method with options support.
|
|
887
897
|
*/
|
|
888
898
|
asMaskedValue(value, options) {
|
|
889
|
-
if (value
|
|
899
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
890
900
|
const str = String(value);
|
|
891
901
|
return Formatter.getMaskedValue(str, options?.startVisible ?? 4, options?.endVisible ?? 3, options?.maskChar ?? "X");
|
|
892
902
|
}
|
|
@@ -904,7 +914,7 @@ var Formatter = class Formatter {
|
|
|
904
914
|
* Supports both base-1024 (binary) and base-1000 (decimal).
|
|
905
915
|
*/
|
|
906
916
|
formatBytes(value, decimals, width) {
|
|
907
|
-
if (value
|
|
917
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
908
918
|
let bytes = normalizeNumber(value);
|
|
909
919
|
const digits = decimals ?? this.defaultDecimalDigits ?? 2;
|
|
910
920
|
const base = this.sizeFormatBase;
|
|
@@ -974,7 +984,7 @@ var Formatter = class Formatter {
|
|
|
974
984
|
* Automatically selects the most appropriate unit based on value magnitude.
|
|
975
985
|
*/
|
|
976
986
|
formatMeasure(value, type, width, decimals) {
|
|
977
|
-
if (value
|
|
987
|
+
if (isBlank(value)) return this.nullDisplay;
|
|
978
988
|
const num = normalizeNumber(value);
|
|
979
989
|
const digits = decimals ?? this.defaultDecimalDigits ?? 2;
|
|
980
990
|
const configs = this.getMeasureUnits(type);
|
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/**\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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 (value === null || value === undefined) 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 * 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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"}
|
package/package.json
CHANGED