欢迎光临
我们一直在努力

前端国际化:数字与货币格式化实战指南

前端国际化:数字与货币格式化实战指南

前言

各位前端小伙伴,今天咱们来聊聊数字和货币的国际化问题。想象一下这些场景:

  • 你写了 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')); // true

Q2: 如何处理超大数?

解决方案:使用紧凑表示法

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来帮你搞定!


    如果这篇文章对你有帮助,欢迎点赞、收藏、转发!你的支持是我最大的动力~

    赞(0)
    未经允许不得转载:171主机测评 » 前端国际化:数字与货币格式化实战指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址