⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

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 语言正确显示
  • [ ] 符合目标地区法规