@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 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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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);
@@ -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, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\")\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":";;AAEA,MAAMA,SAAO;CACZ;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAEA,MAAM,OAAO;CACZ;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAEA,MAAMC,eAAqC;CAC1C,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACN;AAEA,SAASC,UAAQ,KAAqB;CACrC,IAAI,QAAQ,GAAG,OAAO;CACtB,IAAI,MAAM,IAAI,OAAOF,OAAK,QAAQ;CAClC,IAAI,MAAM,KAAK;EACd,MAAM,IAAI,KAAK,KAAK,MAAM,MAAM,EAAE,MAAM;EACxC,MAAM,IAAIA,OAAK,MAAM;EACrB,OAAO,IAAI,GAAG,EAAE,GAAG,MAAM;CAC1B;CACA,IAAI,MAAM,KAAM;EACf,MAAM,IAAIA,OAAK,KAAK,MAAM,MAAM,GAAG,MAAM;EACzC,MAAM,YAAY,MAAM;EACxB,OAAO,YAAY,GAAG,EAAE,WAAWE,UAAQ,SAAS,MAAM,GAAG,EAAE;CAChE;CACA,IAAI,MAAM,KAAW;EACpB,MAAM,KAAKA,UAAQ,KAAK,MAAM,MAAM,GAAI,CAAC;EACzC,MAAM,YAAY,MAAM;EACxB,OAAO,YAAY,GAAG,GAAG,YAAYA,UAAQ,SAAS,MAAM,GAAG,GAAG;CACnE;CACA,IAAI,MAAM,KAAe;EACxB,MAAM,IAAIA,UAAQ,KAAK,MAAM,MAAM,GAAS,CAAC;EAC7C,MAAM,YAAY,MAAM;EACxB,OAAO,YAAY,GAAG,EAAE,WAAWA,UAAQ,SAAS,MAAM,GAAG,EAAE;CAChE;CACA,MAAM,IAAIA,UAAQ,KAAK,MAAM,MAAM,GAAa,CAAC;CACjD,MAAM,YAAY,MAAM;CACxB,OAAO,YAAY,GAAG,EAAE,WAAWA,UAAQ,SAAS,MAAM,GAAG,EAAE;AAChE;AAEA,MAAa,aAA6B;CACzC,UAAU;CACV,WAAW;CACX,gBAAgB;CAEhB,eAAe,GAAmB;EACjC,IAAI,MAAM,GAAG,OAAO;EACpB,OAAOA,UAAQ,CAAC,CAAC,CAAC,KAAK;CACxB;CAEA,YAAY,OAAuB;EAClC,OAAOD,aAAW,UAAU;CAC7B;AACD;AAEA,MAAa,gBAAmC,EAC/C,YAAY;CACX;EAAE,OAAO;EAAmB,QAAQ;CAAY;CAChD;EAAE,OAAO;EAAe,QAAQ;CAAW;CAC3C;EAAE,OAAO;EAAW,QAAQ;CAAW;CACvC;EAAE,OAAO;EAAO,QAAQ;CAAI;AAC7B,EACD;;;ACnGA,MAAM,OAAO;CACZ;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAEA,MAAM,aAAa;CAClB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAEA,MAAM,aAAqC;CAC1C,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACN;;;;;;;;AASA,SAAS,SAAS,GAAW,GAAmB;CAC/C,IAAI,SAAS;CAEb,IAAI,MAAM,GACT,SAAS;MAET,SAAS,GAAG,KAAK,GAAG;CAGrB,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,MAAM,KAAK,IAAI,GAAG,OAAO,GAAG,OAAO;CACvC,IAAI,MAAM,KAAK,IAAI,GAAG,OAAO,GAAG,OAAO;CACvC,OAAO,GAAG,OAAO,GAAG,WAAW,MAAM;AACtC;AAEA,SAAS,aAAa,GAAW,GAAW,GAAmB;CAC9D,MAAM,SAAS,GAAG,KAAK,GAAG;CAC1B,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;CAC/B,IAAI,MAAM,GAAG,OAAO,GAAG,OAAO,QAAQ,KAAK;CAC3C,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,CAAC;AAClC;AAEA,SAAS,UAAU,KAAqB;CACvC,IAAI,QAAQ,GAAG,OAAO;CAEtB,MAAM,IAAI,KAAK,MAAM,MAAM,GAAG;CAC9B,MAAM,IAAI,KAAK,MAAO,MAAM,MAAO,EAAE;CACrC,MAAM,IAAI,MAAM;CAEhB,IAAI,IAAI,GAAG,OAAO,aAAa,GAAG,GAAG,CAAC;CACtC,IAAI,IAAI,GAAG,OAAO,SAAS,GAAG,CAAC;CAC/B,OAAO,KAAK,MAAM;AACnB;AAEA,SAAS,QAAQ,KAAqB;CACrC,IAAI,QAAQ,GAAG,OAAO;CAEtB,MAAM,QAAQ;EACb;GAAE,OAAO;GAAe,OAAO;EAAU;EACzC;GAAE,OAAO;GAAW,OAAO;EAAa;EACxC;GAAE,OAAO;GAAO,OAAO;EAAa;EACpC;GAAE,OAAO;GAAG,OAAO;EAAG;CACvB;CAEA,MAAM,QAAkB,CAAC;CACzB,IAAI,YAAY;CAEhB,KAAK,MAAM,QAAQ,OAClB,IAAI,aAAa,KAAK,OAAO;EAC5B,MAAM,QAAQ,KAAK,MAAM,YAAY,KAAK,KAAK;EAC/C,aAAa,KAAK;EAElB,MAAM,WAAW,UAAU,KAAK;EAChC,IAAI,UACH,MAAM,KAAK,KAAK,QAAQ,GAAG,SAAS,GAAG,KAAK,UAAU,QAAQ;EAI/D,IAAI,YAAY,KAAK,YAAY,KAAK,QAAQ,IAEzC;OAAA,YAAY,OAAO,KAAK,SAAS,KAAM;IAC1C,MAAM,KAAK,YAAsB;IACjC,IAAI,YAAY,IAAI;KACnB,MAAM,KAAK,QAAQ,KAAK,YAAY;KACpC,YAAY;IACb;GACD;;CAEF;CAGD,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;AAC7B;AAEA,MAAa,aAA6B;CACzC,UAAU;CACV,WAAW;CACX,gBAAgB;CAEhB,eAAe,GAAmB;EACjC,IAAI,MAAM,GAAG,OAAO;EACpB,OAAO,QAAQ,CAAC;CACjB;CAEA,YAAY,OAAuB;EAClC,OAAO,WAAW,UAAU;CAC7B;AACD;AAEA,MAAa,gBAAmC,EAC/C,YAAY;CACX;EAAE,OAAO;EAAmB,QAAQ;CAAsB;CAC1D;EAAE,OAAO;EAAe,QAAQ;CAAW;CAC3C;EAAE,OAAO;EAAW,QAAQ;CAAc;CAC1C;EAAE,OAAO;EAAO,QAAQ;CAAa;AACtC,EACD;;;;ACpIA,MAAM,mBAAmC;CACxC,IAAI;CACJ,IAAI;AACL;;AAGA,MAAM,sBAA2C;CAChD,IAAI;CACJ,IAAI;AACL;;AAGA,SAAgB,YAAY,QAAgC;CAC3D,MAAM,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;CAC/B,OAAO,iBAAiB,SAAS;AAClC;;AAGA,SAAgB,qBAAqB,QAAmC;CACvE,MAAM,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;CAC/B,OAAO,oBAAoB,SAAS;AACrC;;AAGA,SAAgB,iBAAiB,MAAc,MAA4B;CAC1E,iBAAiB,QAAQ;AAC1B;;AAGA,SAAgB,oBACf,MACA,QACO;CACP,oBAAoB,QAAQ;AAC7B;;;;;;;ACxCA,SAAgB,WAAW,OAAuB;CACjD,OAAO,MACL,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,QAAQ,CAAC,CACvB,QAAQ,MAAM,QAAQ;AACzB;;;;;AAMA,SAAgB,cAAc,OAAsB;CACnD,IAAI,iBAAiB,MAAM,OAAO;CAElC,IAAI,OAAO,UAAU,UAEpB,OAAO,IAAI,KAAK,QAAQ,eAAO,QAAQ,MAAO,KAAK;CAGpD,IAAI,OAAO,UAAU,UAAU;EAC9B,MAAM,SAAS,IAAI,KAAK,KAAK;EAC7B,IAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,GAChC,MAAM,IAAI,MAAM,6BAA6B,MAAM,EAAE;EAEtD,OAAO;CACR;CAEA,MAAM,IAAI,MAAM,+BAA+B,OAAO,OAAO;AAC9D;;;;;AAMA,SAAgB,gBAAgB,OAAwB;CACvD,IAAI,OAAO,UAAU,UAAU,OAAO;CAEtC,IAAI,OAAO,UAAU,UAAU;EAG9B,MAAM,UAFU,MAAM,KAEA,CAAC,CAAC,QAAQ,MAAM,EAAE;EACxC,MAAM,MAAM,OAAO,OAAO;EAC1B,IAAI,OAAO,MAAM,GAAG,GACnB,MAAM,IAAI,MAAM,gCAAgC,MAAM,EAAE;EAEzD,OAAO;CACR;CAEA,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ,IAAI;CAEnD,MAAM,IAAI,MAAM,iCAAiC,OAAO,OAAO;AAChE;;;;AAKA,SAAgB,oBACf,QACA,MAC6B;CAC7B,MAAM,cAAoE;EACzE,OAAO;GAAE,MAAM;GAAW,OAAO;GAAW,KAAK;EAAU;EAC3D,QAAQ;GAAE,MAAM;GAAW,OAAO;GAAS,KAAK;EAAU;EAC1D,MAAM;GAAE,MAAM;GAAW,OAAO;GAAQ,KAAK;EAAU;EACvD,MAAM;GAAE,MAAM;GAAW,OAAO;GAAQ,KAAK;GAAW,SAAS;EAAO;CACzE;CAEA,MAAM,cAAoE;EACzE,OAAO;GAAE,MAAM;GAAW,QAAQ;EAAU;EAC5C,QAAQ;GAAE,MAAM;GAAW,QAAQ;GAAW,QAAQ;EAAU;EAChE,MAAM;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;GACR,cAAc;EACf;EACA,MAAM;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;GACR,cAAc;EACf;CACD;CAEA,QAAQ,MAAR;EACC,KAAK,QACJ,OAAO,YAAY,WAAW,YAAY;EAC3C,KAAK,QACJ,OAAO,YAAY,WAAW,YAAY;EAC3C,KAAK,YACJ,OAAO;GACN,GAAI,YAAY,WAAW,YAAY;GACvC,GAAI,YAAY,WAAW,YAAY;EACxC;CACF;AACD;;;;AAKA,SAAgB,kBACf,QACA,eACA,MAC6B;CAC7B,IAAI,CAAC,QAAQ,OAAO,oBAAoB,eAAe,IAAI;CAC3D,IAAI,OAAO,WAAW,UAAU,OAAO;CACvC,OAAO,oBAAoB,QAA4B,IAAI;AAC5D;;;;;AAMA,SAAgB,sBACf,WACA,QACA,eACA,gBACS;CACT,IAAI,iBAAiB,QAAQ,kBAAkB,MAAM,OAAO;CAG5D,MAAM,QAAQ,IAAI,KAAK,aAAa,MAAM,CAAC,CAAC,cAAc,UAAU;CACpE,MAAM,gBAAgB,MAAM,MAAM,MAAM,EAAE,SAAS,SAAS,CAAC,EAAE,SAAS;CACxE,MAAM,cAAc,MAAM,MAAM,MAAM,EAAE,SAAS,OAAO,CAAC,EAAE,SAAS;CAEpE,IAAI,SAAS;CAGb,MAAM,kBAAkB;CACxB,MAAM,kBAAkB;CAExB,IAAI,iBAAiB,MACpB,SAAS,OAAO,WAAW,eAAe,eAAe;CAE1D,IAAI,kBAAkB,MACrB,SAAS,OAAO,WAAW,aAAa,eAAe;CAExD,IAAI,iBAAiB,MACpB,SAAS,OAAO,WAAW,iBAAiB,aAAa;CAE1D,IAAI,kBAAkB,MACrB,SAAS,OAAO,WAAW,iBAAiB,cAAc;CAG3D,OAAO;AACR;;;;;;;;;;AC5HA,IAAa,YAAb,MAAa,UAAU;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,UAA4B,CAAC,GAAG;EAC3C,KAAK,SAAS,QAAQ,UAAU;EAChC,KAAK,WAAW,QAAQ,YAAY;EACpC,KAAK,kBAAkB,QAAQ,mBAAmB;EAClD,KAAK,aAAa,QAAQ,cAAc;EACxC,KAAK,aAAa,QAAQ,cAAc;EACxC,KAAK,iBAAiB,QAAQ,kBAAkB;EAChD,KAAK,gBAAgB,QAAQ,iBAAiB,CAAC,MAAM,KAAK;EAC1D,KAAK,cAAc,QAAQ,eAAe;EAC1C,KAAK,eAAe,QAAQ,gBAAgB;EAC5C,KAAK,mBAAmB,QAAQ,oBAAoB;EACpD,KAAK,oBAAoB,QAAQ,qBAAqB;EACtD,KAAK,2BAA2B,QAAQ,4BAA4B;EACpE,KAAK,iBAAiB,QAAQ,kBAAkB;EAChD,KAAK,gBAAgB,QAAQ,iBAAiB;EAC9C,KAAK,uBAAuB,QAAQ,wBAAwB;CAC7D;;;;;CAQA,OAAc,OAAgB,MAA+C;EAC5E,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EAEvD,MAAM,aAAa,MAAM,QAAQ,IAAI,IAAI,KAAK,KAAK;EACnD,MAAM,SAAS,MAAM,QAAQ,IAAI,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC;EACtD,MAAM,aAAa,KAAK,WAAW,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,WAAW,MAAM,CAAC;EAE/E,MAAM,SAAU,KAAiC;EACjD,IAAI,OAAO,WAAW,YACrB,OAAQ,OAA0C,KACjD,MACA,OACA,GAAG,MACJ;EAGD,MAAM,IAAI,MAAM,wBAAwB,YAAY;CACrD;;CAKA,MAAa,OAAwB;EACpC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,OAAO,OAAO,KAAK;CACpB;;CAGA,OAAc,OAAwB;EACrC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,OAAO,WAAW,OAAO,KAAK,CAAC;CAChC;;;;;;CAOA,QAAe,OAAwB;EACtC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EAEvD,OADgB,WAAW,OAAO,KAAK,CAC1B,CAAC,CAAC,QAAQ,SAAS,QAAQ,CAAC,CAAC,QAAQ,WAAW,QAAQ;CACtE;;;;;CAMA,aAAoB,OAAgB,SAAoC;EACvE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,SAAS,OAAO;EAC5B,MAAM,aAAa,SAAS,cAAc;EAK1C,OAJa,OAAO,KAEE,CAAC,CAAC,QAAQ,SAAS,IAAI,CAAC,CAAC,QAAQ,OAAO,IAClC,CAAC,CAAC,MAAM,SACpB,CAAC,CACf,KAAK,MAAM;GACX,IAAI,UAAU,WAAW,EAAE,KAAK,CAAC;GACjC,IAAI,YACH,UAAU,QAAQ,QAAQ,OAAO,QAAQ;GAE1C,OAAO,IAAI,IAAI,GAAG,QAAQ,IAAI,IAAI;EACnC,CAAC,CAAC,CACD,QAAQ,MAAM,MAAM,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC,CACxC,KAAK,IAAI;CACZ;;;;;;CAOA,OAAc,OAAgB,UAAuC;EACpE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,OAAO,OAAO,KAAK;EACzB,IAAI,CAAC,UAAU,OAAO;EACtB,OAAO,UAAU,aAAa,MAAM,QAAQ;CAC7C;;;;;CAMA,OAAe,aACd,MACA,QACS;EACT,MAAM,cAAc,IAAI,KACtB,OAAO,eAAe,CAAC,EAAA,CAAG,KAAK,MAAM,EAAE,YAAY,CAAC,CACtD;EACA,MAAM,eAAe,OAAO,qBAAqB,CAAC;EAGlD,OAAO,KAAK,QACX,iDACC,OAAO,SAAiB,aAAqB;GAC7C,MAAM,MAAM,QAAQ,YAAY;GAChC,IAAI,CAAC,YAAY,IAAI,GAAG,GAAG,OAAO;GAGlC,IADkB,MAAM,WAAW,IACvB,GAAG,OAAO,KAAK,IAAI;GAE/B,MAAM,gBAAgB,MAAM,SAAS,IAAI;GACzC,MAAM,kBAAkB,IAAI,KAC1B,aAAa,QAAQ,CAAC,EAAA,CAAG,KAAK,MAAM,EAAE,YAAY,CAAC,CACrD;GAGA,MAAM,gBAA0B,CAAC;GACjC,MAAM,YACL;GACD,IAAI,YAAoC;GACxC,OAAO,MAAM;IACZ,YAAY,UAAU,KAAK,QAAQ;IACnC,IAAI,CAAC,WAAW;IAChB,MAAM,WAAW,UAAU,EAAE,CAAC,YAAY;IAC1C,IAAI,gBAAgB,IAAI,QAAQ,GAAG;KAClC,MAAM,YAAY,UAAU,MAAM,UAAU,MAAM,UAAU;KAC5D,IAAI,cAAc,KAAA,GACjB,cAAc,KAAK,GAAG,SAAS,IAAI,WAAW,SAAS,EAAE,EAAE;UAE3D,cAAc,KAAK,QAAQ;IAE7B;GACD;GAEA,MAAM,WACL,cAAc,SAAS,IAAI,IAAI,cAAc,KAAK,GAAG,MAAM;GAC5D,OAAO,gBAAgB,IAAI,MAAM,SAAS,OAAO,IAAI,MAAM,SAAS;EACrE,CACD;CACD;;;;;;CAOA,QAAe,OAAgB,SAAgC;EAC9D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,QAAQ,OAAO,KAAK;EAE1B,IAAI,CAAC,UAAU,aAAa,KAAK,GAChC,OAAO,WAAW,KAAK;EAGxB,MAAM,SAAmB,CAAC;EAC1B,IAAI,SAAS,SACZ,OAAO,KAAK,WAAW,mBAAmB,QAAQ,OAAO,GAAG;EAC7D,IAAI,SAAS,MAAM,OAAO,KAAK,QAAQ,mBAAmB,QAAQ,IAAI,GAAG;EACzE,MAAM,QAAQ,OAAO,SAAS,IAAI,IAAI,OAAO,KAAK,GAAG,MAAM;EAC3D,MAAM,cAAc,WAAW,SAAS,QAAQ,KAAK;EAErD,OAAO,mBAAmB,WAAW,KAAK,IAAI,MAAM,IAAI,YAAY;CACrE;;CAGA,OAAe,aAAa,OAAwB;EACnD,OAAO,6BAA6B,KAAK,KAAK;CAC/C;;;;;;CAOA,MAAa,OAAgB,SAA8B;EAC1D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,OAAO,KAAK;EACxB,MAAM,OAAO,2BAA2B,KAAK,GAAG,IAAI,MAAM,UAAU;EACpE,MAAM,SAAS,SAAS,UAAU;EAClC,MAAM,cAAc,WAAW,SAAS,QAAQ,GAAG;EAEnD,MAAM,QAAkB,CACvB,SAAS,WAAW,IAAI,EAAE,IAC1B,WAAW,WAAW,MAAM,EAAE,EAC/B;EACA,IAAI,SAAS,KAAK,MAAM,KAAK,QAAQ,WAAW,QAAQ,GAAG,EAAE,EAAE;EAC/D,IAAI,SAAS,OAAO,MAAM,KAAK,UAAU,WAAW,QAAQ,KAAK,EAAE,EAAE;EAErE,OAAO,MAAM,MAAM,KAAK,GAAG,EAAE,GAAG,YAAY;CAC7C;;;;;CAMA,QAAe,OAAgB,SAAgC;EAC9D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,OAAO,KAAK;EACxB,MAAM,MAAM,SAAS,OAAO;EAE5B,MAAM,QAAkB,CACvB,QAAQ,WAAW,GAAG,EAAE,IACxB,QAAQ,WAAW,GAAG,EAAE,EACzB;EACA,IAAI,SAAS,SAAS,MACrB,MAAM,KAAK,UAAU,WAAW,OAAO,QAAQ,KAAK,CAAC,EAAE,EAAE;EAC1D,IAAI,SAAS,UAAU,MACtB,MAAM,KAAK,WAAW,WAAW,OAAO,QAAQ,MAAM,CAAC,EAAE,EAAE;EAC5D,IAAI,SAAS,OAAO,MAAM,KAAK,UAAU,WAAW,QAAQ,KAAK,EAAE,EAAE;EACrE,IAAI,SAAS,SAAS,MAAM,KAAK,YAAY,WAAW,QAAQ,OAAO,EAAE,EAAE;EAE3E,OAAO,QAAQ,MAAM,KAAK,GAAG,EAAE;CAChC;;CAGA,UAAiB,OAAwB;EACxC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,OAAO,QAAQ,KAAK,cAAc,KAAK,KAAK,cAAc;CAC3D;;CAKA,UAAiB,OAAwB;EACxC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,gBAAgB,KAAK;EACjC,MAAM,SAAS,KAAK,MAAM,GAAG;EAO7B,OAAO,sBALW,IAAI,KAAK,aAAa,KAAK,QAAQ;GACpD,uBAAuB;GACvB,uBAAuB;EACxB,CAAC,CAAC,CAAC,OAAO,MAGT,GACA,KAAK,QACL,KAAK,kBACL,KAAK,iBACN;CACD;;CAGA,UAAiB,OAAgB,UAA2B;EAC3D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,gBAAgB,KAAK;EACjC,MAAM,SAAS,YAAY,KAAK,wBAAwB;EAOxD,OAAO,sBALW,IAAI,KAAK,aAAa,KAAK,QAAQ;GACpD,uBAAuB;GACvB,uBAAuB;EACxB,CAAC,CAAC,CAAC,OAAO,GAGT,GACA,KAAK,QACL,KAAK,kBACL,KAAK,iBACN;CACD;;CAGA,UAAiB,OAAgB,UAA2B;EAC3D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,gBAAgB,KAAK;EACjC,MAAM,SAAS,YAAY,KAAK,wBAAwB;EAQxD,OAAO,sBANW,IAAI,KAAK,aAAa,KAAK,QAAQ;GACpD,OAAO;GACP,uBAAuB;GACvB,uBAAuB;EACxB,CAAC,CAAC,CAAC,OAAO,GAGT,GACA,KAAK,QACL,KAAK,kBACL,KAAK,iBACN;CACD;;CAGA,WAAkB,OAAgB,UAA2B;EAC5D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,gBAAgB,KAAK;EACjC,MAAM,OAAO,YAAY,KAAK;EAO9B,OAAO,sBALW,IAAI,KAAK,aAAa,KAAK,QAAQ;GACpD,OAAO;GACP,UAAU;EACX,CAAC,CAAC,CAAC,OAAO,GAGT,GACA,KAAK,QACL,KAAK,4BAA4B,KAAK,kBACtC,KAAK,iBACN;CACD;;CAGA,aAAoB,OAAgB,UAA2B;EAC9D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,gBAAgB,KAAK;EACjC,MAAM,SAAS,YAAY,KAAK,wBAAwB;EAExD,OAAO,IAAI,KAAK,aAAa,KAAK,QAAQ;GACzC,UAAU;GACV,uBAAuB;GACvB,uBAAuB;EACxB,CAAC,CAAC,CAAC,OAAO,GAAG;CACd;;;;;CAMA,WAAkB,OAAwB;EACzC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,gBAAgB,KAAK;EAEjC,MAAM,WAAW,YAAY,KAAK,MAAM;EAExC,IAAI,QAAQ,GAAG,OAAO,SAAS;EAE/B,MAAM,aAAa,MAAM;EACzB,MAAM,SAAS,KAAK,IAAI,GAAG;EAC3B,MAAM,UAAU,KAAK,MAAM,MAAM;EACjC,MAAM,UAAU,SAAS;EAEzB,IAAI,SAAS,SAAS,eAAe,OAAO;EAE5C,IAAI,UAAU,GAAG;GAEhB,MAAM,aADS,OAAO,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,GAAA,CACtB,MAAM,EAAE,CAAC,CAAC,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC;GACrE,UAAU,IAAI,SAAS,UAAU,GAAG,UAAU,KAAK,GAAG;EACvD;EAEA,OAAO,aAAa,GAAG,SAAS,eAAe,GAAG,WAAW;CAC9D;;;;;;CAOA,UAAiB,OAAwB;EACxC,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,KAAK,MAAM,gBAAgB,KAAK,CAAC;EAE7C,IAAI;GAEH,MAAM,OAAO,IADE,KAAK,YAAY,KAAK,QAAQ,EAAE,MAAM,UAAU,CACjD,CAAC,CAAC,OAAO,GAAG;GAE1B,MAAM,OAAO,KAAK,OAAO,MAAM,GAAG,CAAC,CAAC;GACpC,MAAM,aAAa,UAAU,gBAAgB,MAAM,EAAE,OAAO,KAAK;GACjE,MAAM,eAAe,UAAU,gBAAgB,SAAS;GACxD,MAAM,SAAS,aAAa,SAAS,aAAa,SAAS;GAE3D,OAAO,GAAG,IAAI,KAAK,aAAa,KAAK,MAAM,CAAC,CAAC,OAAO,GAAG,IAAI;EAC5D,QAAQ;GACP,OAAO,GAAG,MAAM,KAAK,mBAAmB,GAAG;EAC5C;CACD;;CAGA,OAAe,kBAAoD;EAClE,IAAI;GAAE,KAAK;GAAM,KAAK;GAAM,KAAK;GAAM,OAAO;EAAK;EACnD,IAAI,EAAE,OAAO,GAAG;EAChB,IAAI;GAAE,KAAK;GAAM,OAAO;EAAI;EAC5B,IAAI,EAAE,OAAO,IAAI;EACjB,IAAI,EAAE,OAAO,IAAI;EACjB,IAAI,EAAE,OAAO,IAAI;EACjB,IAAI,EAAE,OAAO,IAAI;EACjB,IAAI,EAAE,OAAO,GAAG;EAChB,IAAI,EAAE,OAAO,GAAG;EAChB,IAAI,EAAE,OAAO,GAAG;CACjB;;;;;CAMA,OAAc,wBACb,MACA,UACO;EACP,UAAU,gBAAgB,QAAQ;CACnC;;CAKA,OACC,OACA,QACS;EACT,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,OAAO,cAAc,KAAK;EAChC,MAAM,WAAW,kBAChB,UAAU,KAAK,YACf,UACA,MACD;EAEA,OAAO,IAAI,KAAK,eAAe,KAAK,QAAQ;GAC3C,GAAG;GACH,UAAU,KAAK;EAChB,CAAC,CAAC,CAAC,OAAO,IAAI;CACf;;CAGA,OACC,OACA,QACS;EACT,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,OAAO,cAAc,KAAK;EAChC,MAAM,WAAW,kBAChB,UAAU,KAAK,YACf,UACA,MACD;EAEA,OAAO,IAAI,KAAK,eAAe,KAAK,QAAQ;GAC3C,GAAG;GACH,UAAU,KAAK;EAChB,CAAC,CAAC,CAAC,OAAO,IAAI;CACf;;CAGA,WACC,OACA,QACS;EACT,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,OAAO,cAAc,KAAK;EAChC,MAAM,WAAW,kBAChB,UAAU,KAAK,gBACf,UACA,UACD;EAEA,OAAO,IAAI,KAAK,eAAe,KAAK,QAAQ;GAC3C,GAAG;GACH,UAAU,KAAK;EAChB,CAAC,CAAC,CAAC,OAAO,IAAI;CACf;;CAGA,YAAmB,OAAwB;EAC1C,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,OAAO,cAAc,KAAK;EAChC,OAAO,OAAO,KAAK,MAAM,KAAK,QAAQ,IAAI,GAAI,CAAC;CAChD;;;;;CAMA,eAAsB,OAAgB,eAAiC;EACtE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EAEvD,MAAM,OAAO,cAAc,KAAK;EAChC,MAAM,MAAM,gBAAgB,cAAc,aAAa,oBAAI,IAAI,KAAK;EACpE,MAAM,SAAS,KAAK,QAAQ,IAAI,IAAI,QAAQ;EAC5C,MAAM,UAAU,KAAK,MAAM,SAAS,GAAI;EAExC,MAAM,MAAM,IAAI,KAAK,mBAAmB,KAAK,QAAQ,EAAE,SAAS,OAAO,CAAC;EAExE,MAAM,SAAS,KAAK,IAAI,OAAO;EAC/B,IAAI,SAAS,IAAI,OAAO,IAAI,OAAO,SAAS,QAAQ;EACpD,IAAI,SAAS,MAAM,OAAO,IAAI,OAAO,KAAK,MAAM,UAAU,EAAE,GAAG,QAAQ;EACvE,IAAI,SAAS,OAAO,OAAO,IAAI,OAAO,KAAK,MAAM,UAAU,IAAI,GAAG,MAAM;EACxE,IAAI,SAAS,QAAS,OAAO,IAAI,OAAO,KAAK,MAAM,UAAU,KAAK,GAAG,KAAK;EAC1E,IAAI,SAAS,SACZ,OAAO,IAAI,OAAO,KAAK,MAAM,UAAU,MAAO,GAAG,OAAO;EACzD,OAAO,IAAI,OAAO,KAAK,MAAM,UAAU,OAAQ,GAAG,MAAM;CACzD;;;;;CAMA,WAAkB,OAAgB,SAA0B;EAC3D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,IAAI,UAAU,KAAK,IAAI,gBAAgB,KAAK,CAAC;EAC7C,MAAM,YAAY,WAAW;EAE7B,IAAI,YAAY,GAAG,OAAO,KAAK,iBAAiB,UAAU,CAAC;EAE3D,MAAM,QAAkD;GACvD;IAAE,MAAM;IAAQ,SAAS;GAAS;GAClC;IAAE,MAAM;IAAS,SAAS;GAAQ;GAClC;IAAE,MAAM;IAAO,SAAS;GAAM;GAC9B;IAAE,MAAM;IAAQ,SAAS;GAAK;GAC9B;IAAE,MAAM;IAAU,SAAS;GAAG;GAC9B;IAAE,MAAM;IAAU,SAAS;GAAE;EAC9B;EAEA,MAAM,QAAkB,CAAC;EACzB,KAAK,MAAM,EAAE,MAAM,aAAa,OAC/B,IAAI,WAAW,SAAS;GACvB,MAAM,QAAQ,KAAK,MAAM,UAAU,OAAO;GAC1C,WAAW;GACX,MAAM,KAAK,KAAK,iBAAiB,MAAM,KAAK,CAAC;EAC9C;EAGD,OAAO,MAAM,KAAK,SAAS;CAC5B;;CAKA,OAAc,OAAgB,UAA2B;EACxD,OAAO,KAAK,YAAY,OAAO,UAAU,MAAM;CAChD;;CAGA,YAAmB,OAAgB,UAA2B;EAC7D,OAAO,KAAK,YAAY,OAAO,UAAU,OAAO;CACjD;;CAGA,SAAgB,OAAgB,UAA2B;EAC1D,OAAO,KAAK,cAAc,OAAO,UAAU,QAAQ,QAAQ;CAC5D;;CAGA,cAAqB,OAAgB,UAA2B;EAC/D,OAAO,KAAK,cAAc,OAAO,UAAU,SAAS,QAAQ;CAC7D;;CAGA,SAAgB,OAAgB,UAA2B;EAC1D,OAAO,KAAK,cAAc,OAAO,QAAQ,QAAQ,QAAQ;CAC1D;;CAGA,cAAqB,OAAgB,UAA2B;EAC/D,OAAO,KAAK,cAAc,OAAO,QAAQ,SAAS,QAAQ;CAC3D;;;;;CAQA,cAAqB,OAAgB,SAAsC;EAC1E,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,gBAAgB,KAAK;EACjC,MAAM,SAAS,KAAK,IAAI,GAAG;EAC3B,MAAM,WAAW,SAAS,YAAY;EACtC,MAAM,WAAW,SAAS,YAAY;EACtC,MAAM,WAAW,SAAS,eAAe;EACzC,MAAM,SAAS,qBAAqB,KAAK,MAAM;EAE/C,KAAK,MAAM,EAAE,OAAO,WAAW,YAAY,OAAO,YACjD,IAAI,UAAU,WAAW;GACxB,MAAM,QACL,KAAK,MAAO,MAAM,YAAa,MAAM,QAAQ,IAAI,MAAM;GACxD,MAAM,MAAM,YAAY,CAAC,OAAO,WAAW,GAAG,IAAI,MAAM;GACxD,OAAO,GAAG,KAAK,UAAU,OAAO,QAAQ,IAAI,MAAM;EACnD;EAGD,QAAQ,UAAR;GACC,KAAK,WACJ,OAAO,KAAK,UAAU,KAAK,QAAQ;GACpC,KAAK,WACJ,OAAO,KAAK,UAAU,GAAG;GAC1B,SACC,OAAO,KAAK,WAAW,GAAG;EAC5B;CACD;;;;;CAMA,cACC,SACA,SACA,OACA,OACA,SACS;EACT,MAAM,cAAc,SAAS,eAAe;EAC5C,MAAM,WAAW,SAAS,YAAY;EACtC,MAAM,UAAU,SAAS,QAAQ;EACjC,MAAM,SAAS,UAAU,YACxB,SACA,SACA,OACA,OACA,WACD;EAEA,IAAI;EACJ,IAAI;EACJ,IAAI,YAAY,MAAM;GACrB,QAAQ,SAAS;GACjB,OAAO;EACR,OAAO,IAAI,YAAY,MAAM;GAC5B,QAAQ,SAAS;GACjB,OAAO;EACR,OAAO,IAAI,YAAY,KAAK;GAC3B,QAAQ;GACR,OAAO;EACR,OAEC,IAAI,UAAU,KAAM;GACnB,QAAQ,SAAS;GACjB,OAAO;EACR,OAAO;GACN,QAAQ;GACR,OAAO;EACR;EAGD,OAAO,GAAG,KAAK,UAAU,OAAO,QAAQ,EAAE,GAAG;CAC9C;;CAGA,OAAe,YACd,cACA,eACA,YACA,aACA,cAAc,QACL;EACT,MAAM,SAAS,QAAiB,MAAM,KAAK,KAAM;EAEjD,MAAM,UAAU,MAAM,YAAY;EAClC,MAAM,QAAQ,MAAM,UAAU;EAC9B,MAAM,WAAW,MAAM,aAAa,YAAY;EAChD,MAAM,WAAW,MAAM,cAAc,aAAa;EAElD,MAAM,IACL,KAAK,IAAI,WAAW,CAAC,KAAK,IAC1B,KAAK,IAAI,OAAO,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,WAAW,CAAC,KAAK;EAGjE,OAAO,eAFG,IAAI,KAAK,MAAM,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC;CAGxD;;;;;CAMA,cAAqB,OAAgB,SAA+B;EACnE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,OAAO,KAAK;EACxB,OAAO,UAAU,eAChB,KACA,SAAS,gBAAgB,GACzB,SAAS,cAAc,GACvB,SAAS,YAAY,GACtB;CACD;;CAGA,OAAe,eACd,OACA,eAAe,GACf,aAAa,GACb,WAAW,KACF;EACT,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,MAAM,MAAM;EAElB,IAAI,OAAO,eAAe,YAAY,OAAO;EAE7C,MAAM,QAAQ,MAAM,MAAM,GAAG,YAAY;EACzC,MAAM,MAAM,MAAM,MAAM,MAAM,UAAU;EAGxC,OAAO,GAAG,QAFK,SAAS,OAAO,MAAM,eAAe,UAE7B,IAAI;CAC5B;;;;;CAQA,YACC,OACA,UACA,OACS;EACT,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,IAAI,QAAQ,gBAAgB,KAAK;EACjC,MAAM,SAAS,YAAY,KAAK,wBAAwB;EACxD,MAAM,OAAO,KAAK;EAElB,MAAM,aAAa,QAAQ;EAC3B,QAAQ,KAAK,IAAI,KAAK;EAwBtB,MAAM,QAAQ,SAAS,OAAO;GAf7B;IAAE,WAAW;IAAe,MAAM;IAAa,OAAO;GAAK;GAC3D;IAAE,WAAW;IAAY,MAAM;IAAa,OAAO;GAAK;GACxD;IAAE,WAAW;IAAS,MAAM;IAAa,OAAO;GAAK;GACrD;IAAE,WAAW;IAAM,MAAM;IAAa,OAAO;GAAK;GAClD;IAAE,WAAW;IAAG,MAAM;IAAS,OAAO;GAAI;EAWL,IAAI;GAPzC;IAAE,WAAW;IAAe,MAAM;IAAa,OAAO;GAAK;GAC3D;IAAE,WAAW;IAAY,MAAM;IAAa,OAAO;GAAK;GACxD;IAAE,WAAW;IAAS,MAAM;IAAa,OAAO;GAAK;GACrD;IAAE,WAAW;IAAM,MAAM;IAAa,OAAO;GAAK;GAClD;IAAE,WAAW;IAAG,MAAM;IAAS,OAAO;GAAI;EAGO;EAElD,KAAK,MAAM,QAAQ,OAClB,IAAI,SAAS,KAAK,aAAa,KAAK,YAAY,GAAG;GAClD,MAAM,MAAM,QAAQ,KAAK;GAIzB,OAAO,GAHM,aAAa,MAAM,KACd,KAAK,iBAAiB,KAAK,MAEpB,EAAE,GADb,UAAU,SAAS,KAAK,OAAO,KAAK;EAEnD;EAKD,OAAO,GAFM,aAAa,MAAM,KAEf,KAAK,MAAM,KAAK,EAAE,GADrB,UAAU,SAAS,UAAU;CAE5C;;;;;CAMA,cACC,OACA,MACA,OACA,UACS;EACT,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO,KAAK;EACvD,MAAM,MAAM,gBAAgB,KAAK;EACjC,MAAM,SAAS,YAAY,KAAK,wBAAwB;EAExD,MAAM,UAAU,KAAK,gBAAgB,IAAI;EACzC,MAAM,aAAa,MAAM;EACzB,MAAM,SAAS,KAAK,IAAI,GAAG;EAG3B,KAAK,IAAI,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;GAC7C,MAAM,SAAS,QAAQ;GACvB,IAAI,CAAC,QAAQ;GACb,IAAI,UAAU,OAAO,UAAU,MAAM,GAAG;IACvC,MAAM,MAAM,SAAS,OAAO;IAI5B,OAAO,GAHM,aAAa,MAAM,KACd,KAAK,iBAAiB,KAAK,MAEpB,EAAE,GADb,UAAU,SAAS,OAAO,YAAY,OAAO;GAE5D;EACD;EAEA,OAAO,OAAO,GAAG;CAClB;;CAGA,gBAAwB,MAA8C;EACrE,IAAI,SAAS,UAAU;GACtB,IAAI,KAAK,kBAAkB,YAC1B,OAAO;IACN;KAAE,QAAQ;KAAG,WAAW;KAAU,YAAY;IAAK;IACnD;KAAE,QAAQ;KAAI,WAAW;KAAQ,YAAY;IAAK;IAClD;KAAE,QAAQ;KAAI,WAAW;KAAS,YAAY;IAAK;IACnD;KAAE,QAAQ;KAAO,WAAW;KAAS,YAAY;IAAK;GACvD;GAED,OAAO;IACN;KAAE,QAAQ;KAAG,WAAW;KAAe,YAAY;IAAK;IACxD;KAAE,QAAQ;KAAM,WAAW;KAAU,YAAY;IAAI;IACrD;KAAE,QAAQ;KAAS,WAAW;KAAc,YAAY;IAAK;GAC9D;EACD;EAEA,IAAI,KAAK,kBAAkB,YAC1B,OAAO;GACN;IAAE,QAAQ;IAAG,WAAW;IAAU,YAAY;GAAK;GACnD;IAAE,QAAQ;IAAO,WAAW;IAAU,YAAY;GAAK;GACvD;IAAE,QAAQ;IAAM,WAAW;IAAU,YAAY;GAAK;EACvD;EAED,OAAO;GACN;IAAE,QAAQ;IAAG,WAAW;IAAS,YAAY;GAAI;GACjD;IAAE,QAAQ;IAAM,WAAW;IAAa,YAAY;GAAK;GACzD;IAAE,QAAQ;IAAS,WAAW;IAAQ,YAAY;GAAI;EACvD;CACD;;CAGA,iBAAyB,KAAa,QAAwB;EAM7D,OAAO,sBALW,IAAI,KAAK,aAAa,KAAK,QAAQ;GACpD,uBAAuB;GACvB,uBAAuB;EACxB,CAAC,CAAC,CAAC,OAAO,GAGT,GACA,KAAK,QACL,KAAK,kBACL,KAAK,iBACN;CACD;;CAGA,iBAAyB,MAAc,OAAuB;EAC7D,IAAI;GACH,MAAM,WAAW,SAAS,UAAU,UAAU;GAC9C,OAAO,IAAI,KAAK,aAAa,KAAK,QAAQ;IACzC,OAAO;IACP,MAAM;IACN,aAAa;GACd,CAAC,CAAC,CAAC,OAAO,KAAK;EAChB,QAAQ;GAEP,OAAO,GAAG,MAAM,GAAG,OADJ,UAAU,IAAI,MAAM;EAEpC;CACD;;CAGA,mBAA2B,GAAmB;EAC7C,MAAM,MAAM,KAAK,IAAI,CAAC;EACtB,MAAM,SAAS,MAAM;EACrB,IAAI,UAAU,MAAM,UAAU,IAAI,OAAO;EACzC,QAAQ,MAAM,IAAd;GACC,KAAK,GACJ,OAAO;GACR,KAAK,GACJ,OAAO;GACR,KAAK,GACJ,OAAO;GACR,SACC,OAAO;EACT;CACD;AACD;;;;;;;;;;;;;;;;;;ACl4BA,IAAI,WAAW,IAAI,UAAU;;AAG7B,MAAa,YAAY,IAAI,MAAM,CAAC,GAAgB,EACnD,IAAI,SAAS,MAAM,UAAU;CAC5B,OAAO,QAAQ,IAAI,UAAU,MAAM,QAAQ;AAC5C,EACD,CAAC;;;;;;AAOD,SAAgB,mBAAmB,SAAiC;CACnE,WAAW,IAAI,UAAU,OAAO;AACjC"}
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, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\")\n}\n\n/**\n * Normalize an input value into a Date object.\n * Accepts: Date, number (UNIX seconds or milliseconds), string (ISO 8601).\n */\nexport function normalizeDate(value: unknown): Date {\n\tif (value instanceof Date) return value\n\n\tif (typeof value === \"number\") {\n\t\t// Values below 1e12 are treated as seconds, otherwise milliseconds\n\t\treturn new Date(value < 1e12 ? value * 1000 : value)\n\t}\n\n\tif (typeof value === \"string\") {\n\t\tconst parsed = new Date(value)\n\t\tif (Number.isNaN(parsed.getTime())) {\n\t\t\tthrow new Error(`Cannot parse date value: \"${value}\"`)\n\t\t}\n\t\treturn parsed\n\t}\n\n\tthrow new Error(`Invalid data type for date: ${typeof value}`)\n}\n\n/**\n * Normalize an input value into a number.\n * Accepts: number, numeric string (with optional comma grouping), boolean.\n */\nexport function normalizeNumber(value: unknown): number {\n\tif (typeof value === \"number\") return value\n\n\tif (typeof value === \"string\") {\n\t\tconst trimmed = value.trim()\n\t\t// Strip common thousand separators before parsing\n\t\tconst cleaned = trimmed.replace(/,/g, \"\")\n\t\tconst num = Number(cleaned)\n\t\tif (Number.isNaN(num)) {\n\t\t\tthrow new Error(`Cannot parse numeric value: \"${value}\"`)\n\t\t}\n\t\treturn num\n\t}\n\n\tif (typeof value === \"boolean\") return value ? 1 : 0\n\n\tthrow new Error(`Invalid data type for number: ${typeof value}`)\n}\n\n/**\n * Convert a preset name (short/medium/long/full) to Intl.DateTimeFormatOptions.\n */\nexport function presetToDateOptions(\n\tpreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tconst dateOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { year: \"2-digit\", month: \"numeric\", day: \"numeric\" },\n\t\tmedium: { year: \"numeric\", month: \"short\", day: \"numeric\" },\n\t\tlong: { year: \"numeric\", month: \"long\", day: \"numeric\" },\n\t\tfull: { year: \"numeric\", month: \"long\", day: \"numeric\", weekday: \"long\" },\n\t}\n\n\tconst timeOptions: Record<DateFormatPreset, Intl.DateTimeFormatOptions> = {\n\t\tshort: { hour: \"numeric\", minute: \"numeric\" },\n\t\tmedium: { hour: \"numeric\", minute: \"numeric\", second: \"numeric\" },\n\t\tlong: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"short\",\n\t\t},\n\t\tfull: {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"numeric\",\n\t\t\tsecond: \"numeric\",\n\t\t\ttimeZoneName: \"long\",\n\t\t},\n\t}\n\n\tswitch (type) {\n\t\tcase \"date\":\n\t\t\treturn dateOptions[preset] ?? dateOptions.medium\n\t\tcase \"time\":\n\t\t\treturn timeOptions[preset] ?? timeOptions.medium\n\t\tcase \"datetime\":\n\t\t\treturn {\n\t\t\t\t...(dateOptions[preset] ?? dateOptions.medium),\n\t\t\t\t...(timeOptions[preset] ?? timeOptions.medium),\n\t\t\t}\n\t}\n}\n\n/**\n * Resolve a format value: string preset -> Intl options, object -> use directly.\n */\nexport function resolveDateFormat(\n\tformat: string | Intl.DateTimeFormatOptions | undefined,\n\tdefaultPreset: DateFormatPreset,\n\ttype: \"date\" | \"time\" | \"datetime\",\n): Intl.DateTimeFormatOptions {\n\tif (!format) return presetToDateOptions(defaultPreset, type)\n\tif (typeof format === \"object\") return format\n\treturn presetToDateOptions(format as DateFormatPreset, type)\n}\n\n/**\n * Replace locale-default separators with custom ones in a formatted string.\n * Uses temporary placeholders to avoid replacement collisions.\n */\nexport function applyCustomSeparators(\n\tformatted: string,\n\tlocale: string,\n\tcustomDecimal?: string | null,\n\tcustomThousand?: string | null,\n): string {\n\tif (customDecimal == null && customThousand == null) return formatted\n\n\t// Detect locale-default separators\n\tconst parts = new Intl.NumberFormat(locale).formatToParts(1234567.89)\n\tconst localeDecimal = parts.find((p) => p.type === \"decimal\")?.value ?? \".\"\n\tconst localeGroup = parts.find((p) => p.type === \"group\")?.value ?? \",\"\n\n\tlet result = formatted\n\n\t// Temporary placeholders to prevent collision during replacement\n\tconst PLACEHOLDER_DEC = \"\\x01\"\n\tconst PLACEHOLDER_GRP = \"\\x02\"\n\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(localeDecimal, PLACEHOLDER_DEC)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(localeGroup, PLACEHOLDER_GRP)\n\t}\n\tif (customDecimal != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_DEC, customDecimal)\n\t}\n\tif (customThousand != null) {\n\t\tresult = result.replaceAll(PLACEHOLDER_GRP, customThousand)\n\t}\n\n\treturn result\n}\n","import { getNumberShortConfig, getSpellout } from \"./locales/index.js\"\nimport type {\n\tEmailOptions,\n\tFormatterOptions,\n\tFormatWidth,\n\tGpsDistanceOptions,\n\tHtmlSanitizeConfig,\n\tImageOptions,\n\tMaskOptions,\n\tMeasureUnitConfig,\n\tNumberShortOptions,\n\tOrdinalSuffixMap,\n\tParagraphOptions,\n\tUnitSystem,\n\tUrlOptions,\n} from \"./types.js\"\nimport {\n\tapplyCustomSeparators,\n\tescapeHtml,\n\tnormalizeDate,\n\tnormalizeNumber,\n\tresolveDateFormat,\n} from \"./utils.js\"\n\n/** Returns true for null or undefined only. */\nfunction isNullish(value: unknown): value is null | undefined {\n\treturn value === null || value === undefined\n}\n\n/** Returns true for null, undefined, or empty/whitespace-only strings. */\nfunction isBlank(value: unknown): value is null | undefined {\n\tif (value === null || value === undefined) return true\n\tif (typeof value === \"string\" && value.trim() === \"\") return true\n\treturn false\n}\n\n/**\n * TypeScript port of yii\\i18n\\Formatter.\n *\n * Uses only built-in Intl APIs - zero external dependencies.\n * Supports: strings, HTML, numbers, currency, dates, times,\n * file sizes, measurement units, and more.\n */\nexport class Formatter {\n\tpublic locale: string\n\tpublic timeZone: string\n\tpublic defaultTimeZone: string\n\tpublic dateFormat: string | Intl.DateTimeFormatOptions\n\tpublic timeFormat: string | Intl.DateTimeFormatOptions\n\tpublic datetimeFormat: string | Intl.DateTimeFormatOptions\n\tpublic booleanFormat: [string, string]\n\tpublic nullDisplay: string\n\tpublic currencyCode: string\n\tpublic decimalSeparator: string | null\n\tpublic thousandSeparator: string | null\n\tpublic currencyDecimalSeparator: string | null\n\tpublic sizeFormatBase: 1024 | 1000\n\tpublic systemOfUnits: UnitSystem\n\tpublic defaultDecimalDigits: number | null\n\n\tconstructor(options: FormatterOptions = {}) {\n\t\tthis.locale = options.locale ?? \"en-US\"\n\t\tthis.timeZone = options.timeZone ?? \"UTC\"\n\t\tthis.defaultTimeZone = options.defaultTimeZone ?? \"UTC\"\n\t\tthis.dateFormat = options.dateFormat ?? \"medium\"\n\t\tthis.timeFormat = options.timeFormat ?? \"medium\"\n\t\tthis.datetimeFormat = options.datetimeFormat ?? \"medium\"\n\t\tthis.booleanFormat = options.booleanFormat ?? [\"No\", \"Yes\"]\n\t\tthis.nullDisplay = options.nullDisplay ?? \"(not set)\"\n\t\tthis.currencyCode = options.currencyCode ?? \"USD\"\n\t\tthis.decimalSeparator = options.decimalSeparator ?? null\n\t\tthis.thousandSeparator = options.thousandSeparator ?? null\n\t\tthis.currencyDecimalSeparator = options.currencyDecimalSeparator ?? null\n\t\tthis.sizeFormatBase = options.sizeFormatBase ?? 1024\n\t\tthis.systemOfUnits = options.systemOfUnits ?? \"metric\"\n\t\tthis.defaultDecimalDigits = options.defaultDecimalDigits ?? null\n\t}\n\n\t// ─── Generic dispatch ─────────────────────────────────────────────\n\n\t/**\n\t * Format a value by type name, like Yii2's `$formatter->format($value, 'date')`.\n\t * Supports both string and tuple `[formatName, ...params]` signatures.\n\t */\n\tpublic format(value: unknown, type: string | [string, ...unknown[]]): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\n\t\tconst formatName = Array.isArray(type) ? type[0] : type\n\t\tconst params = Array.isArray(type) ? type.slice(1) : []\n\t\tconst methodName = `as${formatName.charAt(0).toUpperCase()}${formatName.slice(1)}`\n\n\t\tconst method = (this as Record<string, unknown>)[methodName]\n\t\tif (typeof method === \"function\") {\n\t\t\treturn (method as (...args: unknown[]) => string).call(\n\t\t\t\tthis,\n\t\t\t\tvalue,\n\t\t\t\t...params,\n\t\t\t)\n\t\t}\n\n\t\tthrow new Error(`Unknown format type: ${formatName}`)\n\t}\n\n\t// ─── String & HTML ────────────────────────────────────────────────\n\n\t/** Returns the value as-is without any formatting. */\n\tpublic asRaw(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn String(value)\n\t}\n\n\t/** Formats the value as HTML-encoded plain text. */\n\tpublic asText(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn escapeHtml(String(value))\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text with newlines converted to `<br />`.\n\t * Handles all line-ending variants: `\\r\\n` (Windows), `\\r` (old Mac), `\\n` (Unix).\n\t * Consecutive newlines produce multiple `<br />` tags.\n\t */\n\tpublic asNtext(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst escaped = escapeHtml(String(value))\n\t\treturn escaped.replace(/\\r\\n/g, \"<br />\").replace(/[\\r\\n]/g, \"<br />\")\n\t}\n\n\t/**\n\t * Formats the value as HTML-encoded text paragraphs (split by double newlines).\n\t * Supports configurable wrapper tag and inline line-break conversion.\n\t */\n\tpublic asParagraphs(value: unknown, options?: ParagraphOptions): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst tag = options?.tag ?? \"p\"\n\t\tconst lineBreaks = options?.lineBreaks ?? false\n\t\tconst text = String(value)\n\t\t// Normalize line endings before splitting\n\t\tconst normalized = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\")\n\t\tconst paragraphs = normalized.split(/\\n\\s*\\n/)\n\t\treturn paragraphs\n\t\t\t.map((p) => {\n\t\t\t\tlet content = escapeHtml(p.trim())\n\t\t\t\tif (lineBreaks) {\n\t\t\t\t\tcontent = content.replace(/\\n/g, \"<br />\")\n\t\t\t\t}\n\t\t\t\treturn `<${tag}>${content}</${tag}>`\n\t\t\t})\n\t\t\t.filter((p) => p !== `<${tag}></${tag}>`)\n\t\t\t.join(\"\\n\")\n\t}\n\n\t/**\n\t * Returns the value as HTML text.\n\t * When a sanitize config is provided, only allowed tags and attributes are kept.\n\t * Without config, the value is returned as-is (caller is responsible for safety).\n\t */\n\tpublic asHtml(value: unknown, sanitize?: HtmlSanitizeConfig): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\tconst html = String(value)\n\t\tif (!sanitize) return html\n\t\treturn Formatter.sanitizeHtml(html, sanitize)\n\t}\n\n\t/**\n\t * Allowlist-based HTML sanitizer. Strips tags and attributes not in the config.\n\t * Handles self-closing tags, nested tags, and attribute filtering.\n\t */\n\tprivate static sanitizeHtml(\n\t\thtml: string,\n\t\tconfig: HtmlSanitizeConfig,\n\t): string {\n\t\tconst allowedTags = new Set(\n\t\t\t(config.allowedTags ?? []).map((t) => t.toLowerCase()),\n\t\t)\n\t\tconst allowedAttrs = config.allowedAttributes ?? {}\n\n\t\t// Match opening tags, closing tags, and self-closing tags\n\t\treturn html.replace(\n\t\t\t/<\\/?([a-zA-Z][a-zA-Z0-9]*)\\b([^>]*?)\\s*\\/?>/g,\n\t\t\t(match, tagName: string, attrsStr: string) => {\n\t\t\t\tconst tag = tagName.toLowerCase()\n\t\t\t\tif (!allowedTags.has(tag)) return \"\"\n\n\t\t\t\tconst isClosing = match.startsWith(\"</\")\n\t\t\t\tif (isClosing) return `</${tag}>`\n\n\t\t\t\tconst isSelfClosing = match.endsWith(\"/>\")\n\t\t\t\tconst tagAllowedAttrs = new Set(\n\t\t\t\t\t(allowedAttrs[tag] ?? []).map((a) => a.toLowerCase()),\n\t\t\t\t)\n\n\t\t\t\t// Parse and filter attributes\n\t\t\t\tconst filteredAttrs: string[] = []\n\t\t\t\tconst attrRegex =\n\t\t\t\t\t/([a-zA-Z_:][\\w:.-]*)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|(\\S+)))?/g\n\t\t\t\tlet attrMatch: RegExpExecArray | null = null\n\t\t\t\twhile (true) {\n\t\t\t\t\tattrMatch = attrRegex.exec(attrsStr)\n\t\t\t\t\tif (!attrMatch) break\n\t\t\t\t\tconst attrName = attrMatch[1].toLowerCase()\n\t\t\t\t\tif (tagAllowedAttrs.has(attrName)) {\n\t\t\t\t\t\tconst attrValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4]\n\t\t\t\t\t\tif (attrValue !== undefined) {\n\t\t\t\t\t\t\tfilteredAttrs.push(`${attrName}=\"${escapeHtml(attrValue)}\"`)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfilteredAttrs.push(attrName)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst attrsOut =\n\t\t\t\t\tfilteredAttrs.length > 0 ? ` ${filteredAttrs.join(\" \")}` : \"\"\n\t\t\t\treturn isSelfClosing ? `<${tag}${attrsOut} />` : `<${tag}${attrsOut}>`\n\t\t\t},\n\t\t)\n\t}\n\n\t/**\n\t * Formats the value as a mailto link.\n\t * Supports custom display text, subject, and body parameters.\n\t * Validates email format - returns escaped plain text for invalid emails.\n\t */\n\tpublic asEmail(value: unknown, options?: EmailOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst email = String(value)\n\n\t\tif (!Formatter.isValidEmail(email)) {\n\t\t\treturn escapeHtml(email)\n\t\t}\n\n\t\tconst params: string[] = []\n\t\tif (options?.subject)\n\t\t\tparams.push(`subject=${encodeURIComponent(options.subject)}`)\n\t\tif (options?.body) params.push(`body=${encodeURIComponent(options.body)}`)\n\t\tconst query = params.length > 0 ? `?${params.join(\"&\")}` : \"\"\n\t\tconst displayText = escapeHtml(options?.text ?? email)\n\n\t\treturn `<a href=\"mailto:${escapeHtml(email)}${query}\">${displayText}</a>`\n\t}\n\n\t/** Basic email format validation (covers most common patterns). */\n\tprivate static isValidEmail(email: string): boolean {\n\t\treturn /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)\n\t}\n\n\t/**\n\t * Formats the value as a hyperlink.\n\t * Detects http, https, ftp, ftps, and mailto schemes.\n\t * Prepends `http://` when no recognized scheme is present.\n\t */\n\tpublic asUrl(value: unknown, options?: UrlOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst url = String(value)\n\t\tconst href = /^(https?|ftps?|mailto):/i.test(url) ? url : `http://${url}`\n\t\tconst target = options?.target ?? \"_blank\"\n\t\tconst displayText = escapeHtml(options?.text ?? url)\n\n\t\tconst attrs: string[] = [\n\t\t\t`href=\"${escapeHtml(href)}\"`,\n\t\t\t`target=\"${escapeHtml(target)}\"`,\n\t\t]\n\t\tif (options?.rel) attrs.push(`rel=\"${escapeHtml(options.rel)}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\n\t\treturn `<a ${attrs.join(\" \")}>${displayText}</a>`\n\t}\n\n\t/**\n\t * Formats the value as an image tag.\n\t * Supports width, height, CSS class, and loading strategy attributes.\n\t */\n\tpublic asImage(value: unknown, options?: ImageOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst src = String(value)\n\t\tconst alt = options?.alt ?? \"\"\n\n\t\tconst attrs: string[] = [\n\t\t\t`src=\"${escapeHtml(src)}\"`,\n\t\t\t`alt=\"${escapeHtml(alt)}\"`,\n\t\t]\n\t\tif (options?.width != null)\n\t\t\tattrs.push(`width=\"${escapeHtml(String(options.width))}\"`)\n\t\tif (options?.height != null)\n\t\t\tattrs.push(`height=\"${escapeHtml(String(options.height))}\"`)\n\t\tif (options?.class) attrs.push(`class=\"${escapeHtml(options.class)}\"`)\n\t\tif (options?.loading) attrs.push(`loading=\"${escapeHtml(options.loading)}\"`)\n\n\t\treturn `<img ${attrs.join(\" \")} />`\n\t}\n\n\t/** Formats the value as a boolean using the configured booleanFormat labels. */\n\tpublic asBoolean(value: unknown): string {\n\t\tif (isNullish(value)) return this.nullDisplay\n\t\treturn value ? this.booleanFormat[1] : this.booleanFormat[0]\n\t}\n\n\t// ─── Number & Currency ────────────────────────────────────────────\n\n\t/** Formats the value as an integer by removing decimal digits without rounding. */\n\tpublic asInteger(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst intVal = Math.trunc(num)\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tmaximumFractionDigits: 0,\n\t\t\tminimumFractionDigits: 0,\n\t\t}).format(intVal)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a decimal number. */\n\tpublic asDecimal(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a percent number with \"%\" sign. */\n\tpublic asPercent(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 0\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"percent\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a currency number using ISO 4217 codes. */\n\tpublic asCurrency(value: unknown, currency?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst code = currency ?? this.currencyCode\n\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tstyle: \"currency\",\n\t\t\tcurrency: code,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.currencyDecimalSeparator ?? this.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Formats the value as a scientific number (e-notation). */\n\tpublic asScientific(value: unknown, decimals?: number): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\tnotation: \"scientific\",\n\t\t\tminimumFractionDigits: digits,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\t}\n\n\t/**\n\t * Formats the value as a number spellout (e.g. 42 -> \"forty-two\").\n\t * Supports multiple locales via the locales/ registry.\n\t */\n\tpublic asSpellout(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\n\t\tconst spellout = getSpellout(this.locale)\n\n\t\tif (num === 0) return spellout.zeroWord\n\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\t\tconst intPart = Math.trunc(absNum)\n\t\tconst decPart = absNum - intPart\n\n\t\tlet result = spellout.integerToWords(intPart)\n\n\t\tif (decPart > 0) {\n\t\t\tconst decStr = String(absNum).split(\".\")[1] ?? \"\"\n\t\t\tconst decDigits = decStr.split(\"\").map((d) => spellout.digitToWord(d))\n\t\t\tresult += ` ${spellout.pointWord} ${decDigits.join(\" \")}`\n\t\t}\n\n\t\treturn isNegative ? `${spellout.negativePrefix} ${result}` : result\n\t}\n\n\t/**\n\t * Formats the value as an ordinal number (e.g. 1 -> \"1st\", 2 -> \"2nd\").\n\t * Supports multiple locales via built-in suffix maps and custom overrides\n\t * through `Formatter.registerOrdinalSuffixes()`.\n\t */\n\tpublic asOrdinal(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = Math.trunc(normalizeNumber(value))\n\n\t\ttry {\n\t\t\tconst pr = new Intl.PluralRules(this.locale, { type: \"ordinal\" })\n\t\t\tconst rule = pr.select(num)\n\n\t\t\tconst lang = this.locale.split(\"-\")[0]\n\t\t\tconst enSuffixes = Formatter.ordinalSuffixes.en ?? { other: \"th\" }\n\t\t\tconst langSuffixes = Formatter.ordinalSuffixes[lang] ?? enSuffixes\n\t\t\tconst suffix = langSuffixes[rule] ?? langSuffixes.other ?? \"\"\n\n\t\t\treturn `${new Intl.NumberFormat(this.locale).format(num)}${suffix}`\n\t\t} catch {\n\t\t\treturn `${num}${this.getOrdinalSuffixEn(num)}`\n\t\t}\n\t}\n\n\t/** Built-in ordinal suffix registry. Extensible at runtime. */\n\tprivate static ordinalSuffixes: Record<string, OrdinalSuffixMap> = {\n\t\ten: { one: \"st\", two: \"nd\", few: \"rd\", other: \"th\" },\n\t\tvi: { other: \"\" },\n\t\tfr: { one: \"er\", other: \"e\" },\n\t\tde: { other: \".\" },\n\t\tes: { other: \".\" },\n\t\tpt: { other: \".\" },\n\t\tit: { other: \".\" },\n\t\tja: { other: \"\" },\n\t\tko: { other: \"\" },\n\t\tzh: { other: \"\" },\n\t}\n\n\t/**\n\t * Register ordinal suffixes for a language at runtime.\n\t * Keys are Intl.PluralRules ordinal categories: \"one\", \"two\", \"few\", \"other\".\n\t */\n\tpublic static registerOrdinalSuffixes(\n\t\tlang: string,\n\t\tsuffixes: OrdinalSuffixMap,\n\t): void {\n\t\tFormatter.ordinalSuffixes[lang] = suffixes\n\t}\n\n\t// ─── Date & Time ──────────────────────────────────────────────────\n\n\t/** Formats the value as a date. */\n\tpublic asDate(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.dateFormat,\n\t\t\t\"medium\",\n\t\t\t\"date\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a time. */\n\tpublic asTime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.timeFormat,\n\t\t\t\"medium\",\n\t\t\t\"time\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Formats the value as a datetime. */\n\tpublic asDatetime(\n\t\tvalue: unknown,\n\t\tformat?: string | Intl.DateTimeFormatOptions,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\tconst resolved = resolveDateFormat(\n\t\t\tformat ?? this.datetimeFormat,\n\t\t\t\"medium\",\n\t\t\t\"datetime\",\n\t\t)\n\n\t\treturn new Intl.DateTimeFormat(this.locale, {\n\t\t\t...resolved,\n\t\t\ttimeZone: this.timeZone,\n\t\t}).format(date)\n\t}\n\n\t/** Returns the value as a UNIX timestamp (seconds since epoch). */\n\tpublic asTimestamp(value: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst date = normalizeDate(value)\n\t\treturn String(Math.floor(date.getTime() / 1000))\n\t}\n\n\t/**\n\t * Formats the value as the time interval between a date and now in human readable form.\n\t * Uses Intl.RelativeTimeFormat (built-in in Node.js / browsers).\n\t */\n\tpublic asRelativeTime(value: unknown, referenceTime?: unknown): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\n\t\tconst date = normalizeDate(value)\n\t\tconst ref = referenceTime ? normalizeDate(referenceTime) : new Date()\n\t\tconst diffMs = date.getTime() - ref.getTime()\n\t\tconst diffSec = Math.round(diffMs / 1000)\n\n\t\tconst rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: \"auto\" })\n\n\t\tconst absSec = Math.abs(diffSec)\n\t\tif (absSec < 60) return rtf.format(diffSec, \"second\")\n\t\tif (absSec < 3600) return rtf.format(Math.round(diffSec / 60), \"minute\")\n\t\tif (absSec < 86400) return rtf.format(Math.round(diffSec / 3600), \"hour\")\n\t\tif (absSec < 2592000) return rtf.format(Math.round(diffSec / 86400), \"day\")\n\t\tif (absSec < 31536000)\n\t\t\treturn rtf.format(Math.round(diffSec / 2592000), \"month\")\n\t\treturn rtf.format(Math.round(diffSec / 31536000), \"year\")\n\t}\n\n\t/**\n\t * Represents the value as duration in human readable format.\n\t * Example: 5400 -> \"1 hour, 30 minutes\"\n\t */\n\tpublic asDuration(value: unknown, implode?: string): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet seconds = Math.abs(normalizeNumber(value))\n\t\tconst separator = implode ?? \", \"\n\n\t\tif (seconds === 0) return this.getDurationLabel(\"second\", 0)\n\n\t\tconst units: Array<{ unit: string; divisor: number }> = [\n\t\t\t{ unit: \"year\", divisor: 31536000 },\n\t\t\t{ unit: \"month\", divisor: 2592000 },\n\t\t\t{ unit: \"day\", divisor: 86400 },\n\t\t\t{ unit: \"hour\", divisor: 3600 },\n\t\t\t{ unit: \"minute\", divisor: 60 },\n\t\t\t{ unit: \"second\", divisor: 1 },\n\t\t]\n\n\t\tconst parts: string[] = []\n\t\tfor (const { unit, divisor } of units) {\n\t\t\tif (seconds >= divisor) {\n\t\t\t\tconst count = Math.floor(seconds / divisor)\n\t\t\t\tseconds %= divisor\n\t\t\t\tparts.push(this.getDurationLabel(unit, count))\n\t\t\t}\n\t\t}\n\n\t\treturn parts.join(separator)\n\t}\n\n\t// ─── Size & Measurement ──────────────────────────────────────────\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kilobytes\"). */\n\tpublic asSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"long\")\n\t}\n\n\t/** Formats the value in bytes as a size in human readable form (e.g. \"12 kB\"). */\n\tpublic asShortSize(value: unknown, decimals?: number): string {\n\t\treturn this.formatBytes(value, decimals, \"short\")\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 meters\"). */\n\tpublic asLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a length in human readable form (e.g. \"12 m\"). */\n\tpublic asShortLength(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"length\", \"short\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kilograms\"). */\n\tpublic asWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"long\", decimals)\n\t}\n\n\t/** Formats the value as a weight in human readable form (e.g. \"12 kg\"). */\n\tpublic asShortWeight(value: unknown, decimals?: number): string {\n\t\treturn this.formatMeasure(value, \"mass\", \"short\", decimals)\n\t}\n\n\t// ─── Utility Methods ─────────────────────────────────────────────\n\n\t/**\n\t * Abbreviate a large number with locale-aware suffixes.\n\t * e.g. 1500000 -> \"1.5 Million\" (en) or \"1,5 Trieu\" (vi).\n\t */\n\tpublic asNumberShort(value: unknown, options?: NumberShortOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst absNum = Math.abs(num)\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst fallback = options?.fallback ?? \"currency\"\n\t\tconst addSpace = options?.spaceBefore ?? false\n\t\tconst config = getNumberShortConfig(this.locale)\n\n\t\tfor (const { value: threshold, suffix } of config.thresholds) {\n\t\t\tif (absNum >= threshold) {\n\t\t\t\tconst short =\n\t\t\t\t\tMath.round((num / threshold) * 10 ** decimals) / 10 ** decimals\n\t\t\t\tconst sep = addSpace && !suffix.startsWith(\" \") ? \" \" : \"\"\n\t\t\t\treturn `${this.asDecimal(short, decimals)}${sep}${suffix}`\n\t\t\t}\n\t\t}\n\n\t\tswitch (fallback) {\n\t\t\tcase \"decimal\":\n\t\t\t\treturn this.asDecimal(num, decimals)\n\t\t\tcase \"integer\":\n\t\t\t\treturn this.asInteger(num)\n\t\t\tdefault:\n\t\t\t\treturn this.asCurrency(num)\n\t\t}\n\t}\n\n\t/**\n\t * Format the GPS (great-circle) distance between two coordinates.\n\t * Returns a human-readable string with unit suffix.\n\t */\n\tpublic asGpsDistance(\n\t\tlatFrom: number,\n\t\tlonFrom: number,\n\t\tlatTo: number,\n\t\tlonTo: number,\n\t\toptions?: GpsDistanceOptions,\n\t): string {\n\t\tconst earthRadius = options?.earthRadius ?? 6_371_000\n\t\tconst decimals = options?.decimals ?? 1\n\t\tconst rawUnit = options?.unit ?? \"auto\"\n\t\tconst meters = Formatter.gpsDistance(\n\t\t\tlatFrom,\n\t\t\tlonFrom,\n\t\t\tlatTo,\n\t\t\tlonTo,\n\t\t\tearthRadius,\n\t\t)\n\n\t\tlet value: number\n\t\tlet unit: string\n\t\tif (rawUnit === \"mi\") {\n\t\t\tvalue = meters / 1609.344\n\t\t\tunit = \"mi\"\n\t\t} else if (rawUnit === \"km\") {\n\t\t\tvalue = meters / 1000\n\t\t\tunit = \"km\"\n\t\t} else if (rawUnit === \"m\") {\n\t\t\tvalue = meters\n\t\t\tunit = \"m\"\n\t\t} else {\n\t\t\t// auto: use km if >= 1000m, otherwise m\n\t\t\tif (meters >= 1000) {\n\t\t\t\tvalue = meters / 1000\n\t\t\t\tunit = \"km\"\n\t\t\t} else {\n\t\t\t\tvalue = meters\n\t\t\t\tunit = \"m\"\n\t\t\t}\n\t\t}\n\n\t\treturn `${this.asDecimal(value, decimals)} ${unit}`\n\t}\n\n\t/** Haversine formula: returns distance in meters between two GPS coordinates. */\n\tprivate static gpsDistance(\n\t\tlatitudeFrom: number,\n\t\tlongitudeFrom: number,\n\t\tlatitudeTo: number,\n\t\tlongitudeTo: number,\n\t\tearthRadius = 6_371_000,\n\t): number {\n\t\tconst toRad = (deg: number) => (deg * Math.PI) / 180\n\n\t\tconst latFrom = toRad(latitudeFrom)\n\t\tconst latTo = toRad(latitudeTo)\n\t\tconst deltaLat = toRad(latitudeTo - latitudeFrom)\n\t\tconst deltaLon = toRad(longitudeTo - longitudeFrom)\n\n\t\tconst a =\n\t\t\tMath.sin(deltaLat / 2) ** 2 +\n\t\t\tMath.cos(latFrom) * Math.cos(latTo) * Math.sin(deltaLon / 2) ** 2\n\t\tconst c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))\n\n\t\treturn earthRadius * c\n\t}\n\n\t/**\n\t * Mask a string value, showing only the first and last N characters.\n\t * Instance method with options support.\n\t */\n\tpublic asMaskedValue(value: unknown, options?: MaskOptions): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst str = String(value)\n\t\treturn Formatter.getMaskedValue(\n\t\t\tstr,\n\t\t\toptions?.startVisible ?? 4,\n\t\t\toptions?.endVisible ?? 3,\n\t\t\toptions?.maskChar ?? \"X\",\n\t\t)\n\t}\n\n\t/** Core masking logic used by asMaskedValue(). */\n\tprivate static getMaskedValue(\n\t\tvalue: string,\n\t\tstartVisible = 4,\n\t\tendVisible = 3,\n\t\tmaskChar = \"X\",\n\t): string {\n\t\tif (!value) return \"\"\n\t\tconst len = value.length\n\n\t\tif (len <= startVisible + endVisible) return value\n\n\t\tconst start = value.slice(0, startVisible)\n\t\tconst end = value.slice(len - endVisible)\n\t\tconst masked = maskChar.repeat(len - startVisible - endVisible)\n\n\t\treturn `${start}${masked}${end}`\n\t}\n\n\t// ─── Private helpers ──────────────────────────────────────────────\n\n\t/**\n\t * Format bytes into the most appropriate size unit.\n\t * Supports both base-1024 (binary) and base-1000 (decimal).\n\t */\n\tprivate formatBytes(\n\t\tvalue: unknown,\n\t\tdecimals: number | undefined,\n\t\twidth: FormatWidth,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tlet bytes = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\t\tconst base = this.sizeFormatBase\n\n\t\tconst isNegative = bytes < 0\n\t\tbytes = Math.abs(bytes)\n\n\t\ttype SizeUnit = {\n\t\t\tthreshold: number\n\t\t\tlong: string\n\t\t\tshort: string\n\t\t}\n\n\t\tconst units1024: SizeUnit[] = [\n\t\t\t{ threshold: 1099511627776, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1073741824, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1048576, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1024, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units1000: SizeUnit[] = [\n\t\t\t{ threshold: 1000000000000, long: \"terabytes\", short: \"TB\" },\n\t\t\t{ threshold: 1000000000, long: \"gigabytes\", short: \"GB\" },\n\t\t\t{ threshold: 1000000, long: \"megabytes\", short: \"MB\" },\n\t\t\t{ threshold: 1000, long: \"kilobytes\", short: \"KB\" },\n\t\t\t{ threshold: 0, long: \"bytes\", short: \"B\" },\n\t\t]\n\n\t\tconst units = base === 1024 ? units1024 : units1000\n\n\t\tfor (const unit of units) {\n\t\t\tif (bytes >= unit.threshold && unit.threshold > 0) {\n\t\t\t\tconst val = bytes / unit.threshold\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? unit.long : unit.short\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\tconst label = width === \"long\" ? \"bytes\" : \"B\"\n\t\treturn `${sign}${Math.round(bytes)} ${label}`\n\t}\n\n\t/**\n\t * Format a measurement value (length or mass).\n\t * Automatically selects the most appropriate unit based on value magnitude.\n\t */\n\tprivate formatMeasure(\n\t\tvalue: unknown,\n\t\ttype: \"length\" | \"mass\",\n\t\twidth: FormatWidth,\n\t\tdecimals?: number,\n\t): string {\n\t\tif (isBlank(value)) return this.nullDisplay\n\t\tconst num = normalizeNumber(value)\n\t\tconst digits = decimals ?? this.defaultDecimalDigits ?? 2\n\n\t\tconst configs = this.getMeasureUnits(type)\n\t\tconst isNegative = num < 0\n\t\tconst absNum = Math.abs(num)\n\n\t\t// Find the best-fitting unit (largest unit where value >= 1)\n\t\tfor (let i = configs.length - 1; i >= 0; i--) {\n\t\t\tconst config = configs[i]\n\t\t\tif (!config) continue\n\t\t\tif (absNum >= config.factor || i === 0) {\n\t\t\t\tconst val = absNum / config.factor\n\t\t\t\tconst sign = isNegative ? \"-\" : \"\"\n\t\t\t\tconst formatted = this.formatNumberPart(val, digits)\n\t\t\t\tconst label = width === \"long\" ? config.longLabel : config.shortLabel\n\t\t\t\treturn `${sign}${formatted} ${label}`\n\t\t\t}\n\t\t}\n\n\t\treturn String(num)\n\t}\n\n\t/** Get measurement unit configs for the configured system (metric/imperial). */\n\tprivate getMeasureUnits(type: \"length\" | \"mass\"): MeasureUnitConfig[] {\n\t\tif (type === \"length\") {\n\t\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\t\treturn [\n\t\t\t\t\t{ factor: 1, longLabel: \"inches\", shortLabel: \"in\" },\n\t\t\t\t\t{ factor: 12, longLabel: \"feet\", shortLabel: \"ft\" },\n\t\t\t\t\t{ factor: 36, longLabel: \"yards\", shortLabel: \"yd\" },\n\t\t\t\t\t{ factor: 63360, longLabel: \"miles\", shortLabel: \"mi\" },\n\t\t\t\t]\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"millimeters\", shortLabel: \"mm\" },\n\t\t\t\t{ factor: 1000, longLabel: \"meters\", shortLabel: \"m\" },\n\t\t\t\t{ factor: 1000000, longLabel: \"kilometers\", shortLabel: \"km\" },\n\t\t\t]\n\t\t}\n\n\t\tif (this.systemOfUnits === \"imperial\") {\n\t\t\treturn [\n\t\t\t\t{ factor: 1, longLabel: \"grains\", shortLabel: \"gr\" },\n\t\t\t\t{ factor: 437.5, longLabel: \"ounces\", shortLabel: \"oz\" },\n\t\t\t\t{ factor: 7000, longLabel: \"pounds\", shortLabel: \"lb\" },\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t{ factor: 1, longLabel: \"grams\", shortLabel: \"g\" },\n\t\t\t{ factor: 1000, longLabel: \"kilograms\", shortLabel: \"kg\" },\n\t\t\t{ factor: 1000000, longLabel: \"tons\", shortLabel: \"t\" },\n\t\t]\n\t}\n\n\t/** Format the numeric part of a result using locale-aware Intl. */\n\tprivate formatNumberPart(num: number, digits: number): string {\n\t\tconst formatted = new Intl.NumberFormat(this.locale, {\n\t\t\tminimumFractionDigits: 0,\n\t\t\tmaximumFractionDigits: digits,\n\t\t}).format(num)\n\n\t\treturn applyCustomSeparators(\n\t\t\tformatted,\n\t\t\tthis.locale,\n\t\t\tthis.decimalSeparator,\n\t\t\tthis.thousandSeparator,\n\t\t)\n\t}\n\n\t/** Create a locale-aware duration label using Intl unit formatting. */\n\tprivate getDurationLabel(unit: string, count: number): string {\n\t\ttry {\n\t\t\tconst intlUnit = unit === \"month\" ? \"month\" : unit\n\t\t\treturn new Intl.NumberFormat(this.locale, {\n\t\t\t\tstyle: \"unit\",\n\t\t\t\tunit: intlUnit,\n\t\t\t\tunitDisplay: \"long\",\n\t\t\t}).format(count)\n\t\t} catch {\n\t\t\tconst plural = count !== 1 ? \"s\" : \"\"\n\t\t\treturn `${count} ${unit}${plural}`\n\t\t}\n\t}\n\n\t/** English ordinal suffix fallback. */\n\tprivate getOrdinalSuffixEn(n: number): string {\n\t\tconst abs = Math.abs(n)\n\t\tconst mod100 = abs % 100\n\t\tif (mod100 >= 11 && mod100 <= 13) return \"th\"\n\t\tswitch (abs % 10) {\n\t\t\tcase 1:\n\t\t\t\treturn \"st\"\n\t\t\tcase 2:\n\t\t\t\treturn \"nd\"\n\t\t\tcase 3:\n\t\t\t\treturn \"rd\"\n\t\t\tdefault:\n\t\t\t\treturn \"th\"\n\t\t}\n\t}\n}\n","import { Formatter } from \"./formatter.js\"\nimport type { FormatterOptions } from \"./types.js\"\n\n/**\n * Global singleton Formatter instance.\n *\n * Usage: import once at app entry, configure once, then use `formatter` everywhere.\n *\n * ```ts\n * // main.ts (once)\n * import { configureFormatter } from \"@template/helpers\"\n * configureFormatter({ locale: \"vi-VN\", currencyCode: \"VND\" })\n *\n * // any-page.tsx (no setup needed)\n * import { formatter } from \"@template/helpers\"\n * formatter.asCurrency(1234567)\n * ```\n */\nlet instance = new Formatter()\n\n/** The global Formatter singleton. Ready to use after `configureFormatter()`. */\nexport const formatter = new Proxy({} as Formatter, {\n\tget(_target, prop, receiver) {\n\t\treturn Reflect.get(instance, prop, receiver)\n\t},\n})\n\n/**\n * Configure the global formatter once (typically at app bootstrap).\n * Replaces the internal instance - all existing `formatter` references\n * automatically pick up the new config via the proxy.\n */\nexport function configureFormatter(options: FormatterOptions): void {\n\tinstance = new Formatter(options)\n}\n"],"mappings":"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"}
@@ -1 +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;;;;;;;;;;;qBCrIY;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;;;;;qBCh3BI,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"}
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"}
@@ -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;;;;;;;;;;;qBCrIY;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;;;;;qBCh3BI,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"}
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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 === null || value === void 0) return this.nullDisplay;
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);
@@ -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, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\")\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, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#039;\")\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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wikytam/helpers",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "TypeScript utility library ported from yii\\i18n\\Formatter - zero dependencies, built-in Intl APIs only",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",