(
price: number | string | null | undefined,
options: { currency?: string; locale?: string; decimals?: number } = {},
)
| 59 | * Format price with currency |
| 60 | */ |
| 61 | export function formatPrice( |
| 62 | price: number | string | null | undefined, |
| 63 | options: { currency?: string; locale?: string; decimals?: number } = {}, |
| 64 | ): string { |
| 65 | const { currency = 'USD', locale = 'en-US', decimals = 2 } = options; |
| 66 | |
| 67 | if (price == null) return ''; |
| 68 | |
| 69 | const num = typeof price === 'string' ? parseFloat(price) : price; |
| 70 | if (isNaN(num)) return ''; |
| 71 | |
| 72 | // Currency-specific defaults |
| 73 | const currencyDecimals = { JPY: 0, KRW: 0, VND: 0 }[currency] ?? decimals; |
| 74 | const finalDecimals = decimals !== 2 ? decimals : currencyDecimals; |
| 75 | |
| 76 | if (num === 0) { |
| 77 | return new Intl.NumberFormat(locale, { |
| 78 | style: 'currency', |
| 79 | currency, |
| 80 | minimumFractionDigits: 0, |
| 81 | maximumFractionDigits: 0, |
| 82 | }).format(0); |
| 83 | } |
| 84 | |
| 85 | // Handle tiny amounts |
| 86 | const threshold = 1 / Math.pow(10, finalDecimals); |
| 87 | if (num > 0 && num < threshold) { |
| 88 | return `< ${new Intl.NumberFormat(locale, { |
| 89 | style: 'currency', |
| 90 | currency, |
| 91 | minimumFractionDigits: finalDecimals, |
| 92 | maximumFractionDigits: finalDecimals, |
| 93 | }).format(threshold)}`; |
| 94 | } |
| 95 | |
| 96 | return new Intl.NumberFormat(locale, { |
| 97 | style: 'currency', |
| 98 | currency, |
| 99 | minimumFractionDigits: Math.min(finalDecimals, 6), |
| 100 | maximumFractionDigits: Math.min(finalDecimals, 6), |
| 101 | }).format(num); |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Format large prices with abbreviations |
no test coverage detected
searching dependent graphs…