3.8.5 本地化 l10n
一句话破题
本地化不只是翻译文字,还要适配日期、货币、习惯,甚至颜色的文化含义。
核心价值
本地化 (l10n = localization) 是让产品适应特定地区的文化、法规和用户习惯。直接翻译可能产生文化冲突或误解。
本地化 vs 国际化
| 概念 | 范围 | 示例 |
|---|---|---|
| 国际化 (i18n) | 技术架构 | 文本外置、格式化函数 |
| 本地化 (l10n) | 内容适配 | 翻译、文化调整、法规遵从 |
日期和时间格式
tsx
const date = new Date('2024-01-15T14:30:00')
// 不同地区格式差异巨大
const formats = {
'zh-CN': '2024年1月15日 14:30', // 年月日,24小时制
'en-US': 'January 15, 2024, 2:30 PM', // 月日年,12小时制
'de-DE': '15. Januar 2024, 14:30', // 日.月.年
'ja-JP': '2024年1月15日 14:30', // 同中国
}
// 使用 Intl.DateTimeFormat
function formatDate(date: Date, locale: string) {
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
}).format(date)
}货币和数字
tsx
// 货币符号位置和小数位数
const amount = 1234.56
const currencies = {
'zh-CN': '¥1,234.56',
'en-US': '$1,234.56',
'de-DE': '1.234,56 €', // 注意:逗号和点的用法相反
'ja-JP': '¥1,235', // 日元无小数
}
function formatCurrency(amount: number, locale: string, currency: string) {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
}).format(amount)
}
// 使用
formatCurrency(1234.56, 'zh-CN', 'CNY') // ¥1,234.56
formatCurrency(1234.56, 'en-US', 'USD') // $1,234.56
formatCurrency(1234.56, 'de-DE', 'EUR') // 1.234,56 €地址格式
tsx
// 地址组件顺序因地区而异
interface Address {
street: string
city: string
state: string
postalCode: string
country: string
}
// 中国:国家 省 市 区 街道
// 美国:街道, 城市, 州 邮编
// 日本:邮编 县 市 区 街道
function formatAddress(address: Address, locale: string): string {
switch (locale) {
case 'zh-CN':
return `${address.country}${address.state}${address.city}${address.street}`
case 'en-US':
return `${address.street}, ${address.city}, ${address.state} ${address.postalCode}`
case 'ja-JP':
return `〒${address.postalCode} ${address.state}${address.city}${address.street}`
default:
return `${address.street}, ${address.city}`
}
}姓名顺序
tsx
interface Name {
firstName: string
lastName: string
}
// 西方:firstName lastName
// 中日韩:lastName firstName
function formatName(name: Name, locale: string): string {
const eastAsianLocales = ['zh-CN', 'zh-TW', 'ja-JP', 'ko-KR']
if (eastAsianLocales.includes(locale)) {
return `${name.lastName}${name.firstName}`
}
return `${name.firstName} ${name.lastName}`
}颜色的文化含义
| 颜色 | 中国 | 西方 | 日本 |
|---|---|---|---|
| 红色 | 喜庆、好运 | 危险、警告 | 生命、热情 |
| 白色 | 丧事 | 纯洁、婚礼 | 纯洁 |
| 绿色 | 环保、健康 | 安全、金钱 | 自然 |
| 黄色 | 皇家、高贵 | 警告、懦弱 | 勇气 |
tsx
// 根据地区调整状态颜色
const statusColors = {
'zh-CN': {
success: 'text-red-500', // 红色更积极
error: 'text-gray-500',
},
'en-US': {
success: 'text-green-500',
error: 'text-red-500',
},
}文本长度适配
tsx
// 翻译后文本长度可能差异很大
const translations = {
'submit': {
'zh-CN': '提交', // 2字符
'en-US': 'Submit', // 6字符
'de-DE': 'Einreichen', // 10字符
},
}
// 设计时考虑文本膨胀
// 按钮使用 min-width 而非固定宽度
<button className="min-w-[80px] px-4">
{t('submit')}
</button>
// 使用弹性布局
<div className="flex flex-wrap gap-2">
<button>{t('save')}</button>
<button>{t('cancel')}</button>
</div>RTL 语言支持
tsx
// 阿拉伯语、希伯来语从右到左阅读
// 使用逻辑属性
.container {
/* 物理属性 - RTL 需要翻转 */
margin-left: 1rem; /* 不推荐 */
/* 逻辑属性 - 自动适应 */
margin-inline-start: 1rem; /* 推荐 */
}
// Tailwind 逻辑属性
<div className="ms-4 me-2"> {/* margin-inline-start/end */}
<p className="text-start">内容</p> {/* 自动适应方向 */}
</div>
// HTML dir 属性
<html lang="ar" dir="rtl">
...
</html>法规合规
tsx
// GDPR (欧盟) - 需要 cookie 同意
// CCPA (加州) - 需要"不要出售我的信息"链接
// 中国 - 需要 ICP 备案号
function Footer({ locale }: { locale: string }) {
return (
<footer>
{locale.startsWith('zh') && (
<p>ICP备案号: 京ICP备XXXXXXXX号</p>
)}
{['en-US', 'en-GB', 'de-DE', 'fr-FR'].includes(locale) && (
<CookieConsent />
)}
{locale === 'en-US' && (
<a href="/ccpa">Do Not Sell My Personal Information</a>
)}
</footer>
)
}本地化测试
tsx
// 伪本地化测试 - 用特殊字符替换
// 快速发现硬编码文本和布局问题
function pseudoLocalize(text: string): string {
const map: Record<string, string> = {
'a': 'å', 'b': 'ƀ', 'c': 'ç', 'd': 'ð',
'e': 'é', 'f': 'ƒ', 'g': 'ğ', 'h': 'ĥ',
// ...
}
return '[[ ' + text.split('').map(c => map[c.toLowerCase()] || c).join('') + ' ]]'
}
// "Hello" → "[[ ĥéļļö ]]"
// 容易发现:
// 1. 硬编码文本(没有被替换)
// 2. 文本截断问题(加长了字符串)
// 3. 编码问题(特殊字符显示异常)AI 协作指南
核心意图:让 AI 帮你处理本地化细节。
需求定义公式:
- 目标市场:[国家/地区列表]
- 内容类型:[日期/货币/地址/姓名]
- 特殊要求:[法规合规/RTL 支持]
示例 Prompt:
我的产品需要支持中国和美国市场,请帮我:
1. 设计日期和货币格式化工具函数
2. 创建地址和姓名格式化函数
3. 列出两个市场的法规差异需要注意的点
4. 提供按钮和表单的多语言长度适配方案验收清单
- [ ] 日期/时间按地区格式化
- [ ] 货币按地区显示
- [ ] 地址/姓名顺序正确
- [ ] 布局适应文本长度变化
- [ ] RTL 语言正确显示
- [ ] 符合目标地区法规
