返回 DeepSeek-Reasonix
money.ts
根目录 / desktop / frontend / src / lib / money.ts
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 // Prefer dash over a fake zero when cost cannot be estimated.
24 return empty === "dash" ? "-" : empty === "zero" ? `${symbol}0.0000` : "-";
25 }
26 return `${symbol}${amount < 1 ? amount.toFixed(4) : amount.toFixed(2)}`;
27 }
28
29 /** Format an estimated cost with the product-wide "≈" cue. */
30 export function formatEstimatedCost(amount?: number, currency?: string, incomplete?: boolean): string {
31 if (incomplete || typeof amount !== "number" || amount <= 0) {
32 return "-";
33 }
34 return `≈${formatMoney(amount, currency, "dash")}`;
35 }
36
37 export function formatMoneyAmount(amount?: string, currency?: string, empty: "zero" | "dash" = "dash"): string {
38 if (!amount || amount === "0") {
39 return empty === "dash" ? "-" : formatMoney(0, currency, empty);
40 }
41 const n = Number(amount);
42 if (!Number.isFinite(n) || n <= 0) {
43 return empty === "dash" ? "-" : formatMoney(0, currency, empty);
44 }
45 return formatMoney(n, currency, empty);
46 }
47
48 interface MoneyFormatOptions {
49 locale?: string;
50 empty?: "zero" | "dash";
51 fractionDigits?: 2 | 4;
52 }
53
54 function isoCurrencyCode(currency?: string): string | null {
55 const value = (currency || "").trim();
56 if (!/^[a-z]{3}$/i.test(value)) return null;
57 const code = value.toUpperCase();
58 try {
59 new Intl.NumberFormat("en", { style: "currency", currency: code }).format(0);
60 return code;
61 } catch {
62 return null;
63 }
64 }
65
66 export function formatMoneyLocalized(amount?: number, currency?: string, options: MoneyFormatOptions = {}): string {
67 const empty = options.empty ?? "zero";
68 if (typeof amount !== "number" || amount <= 0) {
69 return empty === "dash" ? "-" : options.fractionDigits === undefined
70 ? formatMoney(0, currency, empty) : `${currencySymbol(currency)}${(0).toFixed(options.fractionDigits)}`;
71 }
72
73 const code = isoCurrencyCode(currency);
74 if (!code) return options.fractionDigits === undefined ? formatMoney(amount, currency, empty)
75 : `${currencySymbol(currency)}${amount.toFixed(options.fractionDigits)}`;
76
77 const digits = options.fractionDigits ?? (amount < 1 ? 4 : 2);
78 return new Intl.NumberFormat(options.locale, {
79 style: "currency",
80 currency: code,
81 minimumFractionDigits: digits,
82 maximumFractionDigits: digits,
83 }).format(amount);
84 }
85
85 lines TYPESCRIPT