前端国际化:数字与货币格式化实战指南
前端国际化:数字与货币格式化实战指南
前言
各位前端小伙伴,今天咱们来聊聊数字和货币的国际化问题。想象一下这些场景:
- 你写了
1234567.89,美国人看到是1,234,567.89 - 德国人看到应该是
1.234.567,89 - 中国人看到可能是
1,234,567.89或1234567.89 - 印度人看到会是
12,34,567.89(对,印度的千分位是两位!)
再说说货币:
- 人民币:
¥1,234.56 - 美元:
$1,234.56 - 欧元:
1.234,56 €(欧元符号在后面!) - 日元:
¥1,234(日元没有小数!)
是不是很头疼?别急,今天咱们就用JavaScript的Intl.NumberFormat来搞定这一切!
Intl.NumberFormat 基础
创建数字格式化器
// 中文数字格式化 const chineseNumber = new Intl.NumberFormat('zh-CN'); console.log(chineseNumber.format(1234567.89)); // 输出:1,234,567.89 // 德国数字格式化 const germanNumber = new Intl.NumberFormat('de-DE'); console.log(germanNumber.format(1234567.89)); // 输出:1.234.567,89 // 印度数字格式化 const indianNumber = new Intl.NumberFormat('en-IN'); console.log(indianNumber.format(1234567.89)); // 输出:12,34,567.89常用配置选项
const options = { style: 'decimal', // 'decimal' | 'currency' | 'percent' | 'unit' minimumIntegerDigits: 1, minimumFractionDigits: 0, maximumFractionDigits: 2, minimumSignificantDigits: 1, maximumSignificantDigits: 21, useGrouping: true, // 是否使用千分位分隔符 notation: 'standard', // 'standard' | 'scientific' | 'engineering' | 'compact' compactDisplay: 'short' // 'short' | 'long' (配合compact notation使用) }; const formatter = new Intl.NumberFormat('zh-CN', options);货币格式化实战
基础货币格式化
function formatCurrency(amount, currency, locale = 'zh-CN') { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency, minimumFractionDigits: 2 }).format(amount); } console.log(formatCurrency(1234.56, 'CNY')); // ¥1,234.56 console.log(formatCurrency(1234.56, 'USD', 'en-US')); // $1,234.56 console.log(formatCurrency(1234.56, 'EUR', 'de-DE')); // 1.234,56 € console.log(formatCurrency(1234, 'JPY', 'ja-JP')); // ¥1,234动态货币符号
function getCurrencySymbol(currency, locale = 'zh-CN') { const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: currency, minimumFractionDigits: 0 }); const parts = formatter.formatToParts(1); return parts.find(p => p.type === 'currency').value; } console.log(getCurrencySymbol('CNY')); // ¥ console.log(getCurrencySymbol('USD')); // $ console.log(getCurrencySymbol('EUR')); // €货币转换显示
class CurrencyDisplay { constructor(baseCurrency = 'CNY') { this.baseCurrency = baseCurrency; this.exchangeRates = { CNY: 1, USD: 0.14, EUR: 0.13, JPY: 21.5, GBP: 0.11 }; } format(amount, targetCurrency, locale = 'zh-CN') { const rate = this.exchangeRates[targetCurrency]; const convertedAmount = amount * rate; return { original: formatCurrency(amount, this.baseCurrency, locale), converted: new Intl.NumberFormat(locale, { style: 'currency', currency: targetCurrency }).format(convertedAmount), rate: rate }; } } // 使用示例 const display = new CurrencyDisplay('CNY'); console.log(display.format(100, 'USD', 'en-US')); // { original: '¥100.00', converted: '$14.00', rate: 0.14 }数字格式化进阶
紧凑数字格式
// 大数字简化显示 const compactFormatter = new Intl.NumberFormat('zh-CN', { notation: 'compact', compactDisplay: 'short' }); console.log(compactFormatter.format(1000)); // 1K console.log(compactFormatter.format(10000)); // 1万 console.log(compactFormatter.format(1000000)); // 100万 console.log(compactFormatter.format(10000000)); // 1000万科学计数法
const sciFormatter = new Intl.NumberFormat('zh-CN', { notation: 'scientific' }); console.log(sciFormatter.format(1234567)); // 1.234567E6 console.log(sciFormatter.format(0.000123)); // 1.23E-4百分比格式化
const percentFormatter = new Intl.NumberFormat('zh-CN', { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 }); console.log(percentFormatter.format(0.4567)); // 45.67% console.log(percentFormatter.format(1.23)); // 123.00%单位格式化
const unitFormatter = new Intl.NumberFormat('zh-CN', { style: 'unit', unit: 'kilometer', unitDisplay: 'long' // 'long' | 'short' | 'narrow' }); console.log(unitFormatter.format(123.45)); // 123.45 千米 // 面积单位 const areaFormatter = new Intl.NumberFormat('zh-CN', { style: 'unit', unit: 'square-meter', unitDisplay: 'short' }); console.log(areaFormatter.format(150)); // 150 平方米数字解析与验证
解析本地化数字
function parseLocalizedNumber(str, locale = 'zh-CN') { const formatter = new Intl.NumberFormat(locale); const parts = formatter.formatToParts(1234.56); let decimalSeparator = '.'; let groupSeparator = ','; for (const part of parts) { if (part.type === 'decimal') { decimalSeparator = part.value; } else if (part.type === 'group') { groupSeparator = part.value; } } const normalized = str .replace(new RegExp(`\\${groupSeparator}`, 'g'), '') .replace(new RegExp(`\\${decimalSeparator}`), '.'); return parseFloat(normalized); } console.log(parseLocalizedNumber('1.234.567,89', 'de-DE')); // 1234567.89 console.log(parseLocalizedNumber('12,34,567.89', 'en-IN')); // 1234567.89数字输入验证
function isValidNumber(input, locale = 'zh-CN') { const formatter = new Intl.NumberFormat(locale); const parts = formatter.formatToParts(1234.56); // 获取本地化的数字模式 const decimalSep = parts.find(p => p.type === 'decimal')?.value || '.'; const groupSep = parts.find(p => p.type === 'group')?.value || ','; // 构建正则表达式 const pattern = new RegExp( `^-?\\d{1,3}(${groupSep}\\d{3})*(${decimalSep}\\d+)?$` ); return pattern.test(input); }实战案例:国际化仪表盘
数据可视化组件
class InternationalStats { constructor(locale = 'zh-CN') { this.locale = locale; this.numberFormatter = new Intl.NumberFormat(locale); this.currencyFormatter = new Intl.NumberFormat(locale, { style: 'currency', currency: 'CNY' }); this.percentFormatter = new Intl.NumberFormat(locale, { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 }); } renderStats(data) { return { revenue: this.currencyFormatter.format(data.revenue), users: this.numberFormatter.format(data.users), growth: this.percentFormatter.format(data.growth), conversion: this.percentFormatter.format(data.conversion) }; } } // 使用示例 const stats = new InternationalStats('en-US'); console.log(stats.renderStats({ revenue: 1234567.89, users: 98765, growth: 0.234, conversion: 0.056 })); // { revenue: '$1,234,567.89', users: '98,765', growth: '23.4%', conversion: '5.6%' }最佳实践总结
1. 统一数字存储
// 始终存储原始数字 const rawValue = 1234567.89; // 只在展示时格式化 function displayNumber(value, locale) { return new Intl.NumberFormat(locale).format(value); }2. 支持用户自定义格式
function createCustomFormatter(locale, options) { const defaultOptions = { minimumFractionDigits: 2, maximumFractionDigits: 2, useGrouping: true }; return new Intl.NumberFormat(locale, { ...defaultOptions, ...options }); }3. 优雅降级处理
function safeFormatNumber(value, locale, options) { try { return new Intl.NumberFormat(locale, options).format(value); } catch (e) { console.warn('Number formatting failed:', e); return String(value); } }常见问题与解决方案
Q1: 某些货币符号显示为代码?
解决方案:确保使用正确的货币代码
const supportedCurrencies = Intl.supportedValuesOf('currency'); console.log(supportedCurrencies.includes('CNY')); // trueQ2: 如何处理超大数?
解决方案:使用紧凑表示法
function formatLargeNumber(value, locale) { if (Math.abs(value) >= 1000000000) { return new Intl.NumberFormat(locale, { notation: 'compact', compactDisplay: 'short' }).format(value); } return new Intl.NumberFormat(locale).format(value); }Q3: 如何对齐数字显示?
解决方案:使用CSS或固定宽度
function formatAlignedNumber(value, locale, width = 12) { const formatted = new Intl.NumberFormat(locale).format(value); return formatted.padStart(width, ' '); }总结
数字和货币的国际化虽然看起来复杂,但只要掌握了Intl.NumberFormat,就能轻松应对各种场景。记住这几点:
- 存储用原始值:不要存储格式化后的字符串
- 显示用本地化:根据用户语言环境动态格式化
- 货币要明确:始终指定货币代码和显示样式
下次遇到数字显示问题,别再手动拼接了,让Intl API来帮你搞定!
如果这篇文章对你有帮助,欢迎点赞、收藏、转发!你的支持是我最大的动力~
