| 1 | const DEFAULT_CURRENCY_SYMBOL = "\u00A5"; |
| 2 | |
| 3 | export function currencySymbol(currency?: string): string { |
| 4 | const value = (currency || DEFAULT_CURRENCY_SYMBOL).trim(); |
| 5 | const lower = value.toLowerCase(); |
| 6 | |
| 7 | if (/^(cny|rmb|yuan|renminbi|cnh)$/.test(lower)) return DEFAULT_CURRENCY_SYMBOL; |
| 8 | if (/^(usd|dollar|dollars|us dollar|us dollars|us\$)$/.test(lower)) return "$"; |
| 9 | if (/^(eur|euro|euros)$/.test(lower)) return "\u20AC"; |
| 10 | if (/^(gbp|pound|pounds|sterling)$/.test(lower)) return "\u00A3"; |
| 11 | if (/^(jpy|yen)$/.test(lower)) return DEFAULT_CURRENCY_SYMBOL; |
| 12 | if (value === "\uFFE5" || value === "\u00A5") return DEFAULT_CURRENCY_SYMBOL; |
| 13 | // contains, not equals \u2014 keep multi-char symbols (A$, HK$) off the \u00A5 default. |
| 14 | if (/\p{Sc}/u.test(value)) return value; |
| 15 | if (/^[a-z]{3}$/i.test(value)) return `${value.toUpperCase()} `; |
| 16 | |
| 17 | return DEFAULT_CURRENCY_SYMBOL; |
| 18 | } |
| 19 | |
| 20 | export function formatMoney(amount?: number, currency?: string, empty: "zero" | "dash" = "zero"): string { |
| 21 | const symbol = currencySymbol(currency); |
| 22 | if (typeof amount !== "number" || amount <= 0) { |
| 23 | return empty === "dash" ? "-" : `${symbol}0.0000`; |
| 24 | } |
| 25 | return `${symbol}${amount < 1 ? amount.toFixed(4) : amount.toFixed(2)}`; |
| 26 | } |
| 27 | |
| 28 | interface MoneyFormatOptions { |
| 29 | locale?: string; |
| 30 | empty?: "zero" | "dash"; |
| 31 | } |
| 32 | |
| 33 | function isoCurrencyCode(currency?: string): string | null { |
| 34 | const value = (currency || "").trim(); |
| 35 | if (!/^[a-z]{3}$/i.test(value)) return null; |
| 36 | const code = value.toUpperCase(); |
| 37 | try { |
| 38 | new Intl.NumberFormat("en", { style: "currency", currency: code }).format(0); |
| 39 | return code; |
| 40 | } catch { |
| 41 | return null; |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | export function formatMoneyLocalized(amount?: number, currency?: string, options: MoneyFormatOptions = {}): string { |
| 46 | const empty = options.empty ?? "zero"; |
| 47 | if (typeof amount !== "number" || amount <= 0) { |
| 48 | return empty === "dash" ? "-" : formatMoney(0, currency, empty); |
| 49 | } |
| 50 | |
| 51 | const code = isoCurrencyCode(currency); |
| 52 | if (!code) return formatMoney(amount, currency, empty); |
| 53 | |
| 54 | const digits = amount < 1 ? 4 : 2; |
| 55 | return new Intl.NumberFormat(options.locale, { |
| 56 | style: "currency", |
| 57 | currency: code, |
| 58 | minimumFractionDigits: digits, |
| 59 | maximumFractionDigits: digits, |
| 60 | }).format(amount); |
| 61 | } |
| 62 |