Skip to content

国际化

国际化的概念

国际化(Internationalization,简称 i18n)是指设计和开发软件,使其能够适应不同的语言、地区和文化差异。国际化的目标是让软件能够在不修改代码的情况下,支持多种语言和地区设置。

本地化的概念

本地化(Localization,简称 l10n)是指将国际化的软件适配到特定的语言和地区的过程。本地化包括翻译文本、调整日期和时间格式、调整货币格式等。

国际化的重要性

  1. 扩大用户群体:支持多种语言可以吸引更多的用户
  2. 提高用户体验:用户可以使用自己熟悉的语言和格式
  3. 遵守法律法规:某些国家要求软件必须支持当地语言
  4. 提升品牌形象:支持国际化显示了公司的全球视野

JavaScript 中的国际化 API

1. Intl 对象

Intl 对象是 JavaScript 中用于国际化的内置对象,它提供了以下构造函数:

  • Intl.DateTimeFormat:用于日期和时间的格式化
  • Intl.NumberFormat:用于数字的格式化
  • Intl.Collator:用于字符串的排序和比较
  • Intl.PluralRules:用于复数形式的处理
  • Intl.RelativeTimeFormat:用于相对时间的格式化
  • Intl.ListFormat:用于列表的格式化
  • Intl.Locale:用于表示语言环境

2. Intl.DateTimeFormat

用于格式化日期和时间。

javascript
// 基本用法
const date = new Date('2024-01-01T12:00:00');

// 美国英语格式
const usFormatter = new Intl.DateTimeFormat('en-US');
console.log(usFormatter.format(date)); // 输出: 1/1/2024

// 中国汉语格式
const cnFormatter = new Intl.DateTimeFormat('zh-CN');
console.log(cnFormatter.format(date)); // 输出: 2024/1/1

// 带选项的格式化
const options = {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  timeZoneName: 'short'
};

const detailedFormatter = new Intl.DateTimeFormat('zh-CN', options);
console.log(detailedFormatter.format(date)); // 输出: 2024年1月1日 12:00:00 中国标准时间

3. Intl.NumberFormat

用于格式化数字,包括货币、百分比等。

javascript
// 基本用法
const number = 123456.789;

// 美国英语格式
const usFormatter = new Intl.NumberFormat('en-US');
console.log(usFormatter.format(number)); // 输出: 123,456.79

// 中国汉语格式
const cnFormatter = new Intl.NumberFormat('zh-CN');
console.log(cnFormatter.format(number)); // 输出: 123,456.79

// 货币格式化
const currencyFormatter = new Intl.NumberFormat('zh-CN', {
  style: 'currency',
  currency: 'CNY'
});
console.log(currencyFormatter.format(number)); // 输出: ¥123,456.79

// 百分比格式化
const percentFormatter = new Intl.NumberFormat('zh-CN', {
  style: 'percent',
  minimumFractionDigits: 2
});
console.log(percentFormatter.format(0.75)); // 输出: 75.00%

4. Intl.Collator

用于字符串的排序和比较。

javascript
// 基本用法
const strings = ['apple', 'Banana', 'cherry', 'date'];

// 不区分大小写的排序
const collator = new Intl.Collator('en-US', {
  usage: 'sort',
  sensitivity: 'base'
});

strings.sort(collator.compare);
console.log(strings); // 输出: ['apple', 'Banana', 'cherry', 'date']

// 区分大小写的排序
const caseSensitiveCollator = new Intl.Collator('en-US', {
  usage: 'sort',
  sensitivity: 'case'
});

strings.sort(caseSensitiveCollator.compare);
console.log(strings); // 输出: ['Banana', 'apple', 'cherry', 'date']

5. Intl.PluralRules

用于处理复数形式。

javascript
// 基本用法
const pluralRules = new Intl.PluralRules('en-US');

console.log(pluralRules.select(0)); // 输出: zero
console.log(pluralRules.select(1)); // 输出: one
console.log(pluralRules.select(2)); // 输出: other

// 带选项的复数规则
const pluralRulesWithOptions = new Intl.PluralRules('zh-CN', {
  type: 'ordinal'
});

console.log(pluralRulesWithOptions.select(1)); // 输出: one
console.log(pluralRulesWithOptions.select(2)); // 输出: two
console.log(pluralRulesWithOptions.select(3)); // 输出: few
console.log(pluralRulesWithOptions.select(4)); // 输出: other

6. Intl.RelativeTimeFormat

用于格式化相对时间。

javascript
// 基本用法
const rtf = new Intl.RelativeTimeFormat('zh-CN');

console.log(rtf.format(1, 'day')); // 输出: 1天后
console.log(rtf.format(-1, 'day')); // 输出: 1天前
console.log(rtf.format(1, 'hour')); // 输出: 1小时后
console.log(rtf.format(-1, 'hour')); // 输出: 1小时前

// 带选项的相对时间格式化
const rtfWithOptions = new Intl.RelativeTimeFormat('zh-CN', {
  numeric: 'auto'
});

console.log(rtfWithOptions.format(1, 'day')); // 输出: 明天
console.log(rtfWithOptions.format(-1, 'day')); // 输出: 昨天
console.log(rtfWithOptions.format(0, 'day')); // 输出: 今天

7. Intl.ListFormat

用于格式化列表。

javascript
// 基本用法
const list = ['苹果', '香蕉', '橙子'];

const listFormatter = new Intl.ListFormat('zh-CN');
console.log(listFormatter.format(list)); // 输出: 苹果、香蕉和橙子

// 带选项的列表格式化
const listFormatterWithOptions = new Intl.ListFormat('zh-CN', {
  type: 'disjunction', // 'conjunction' | 'disjunction' | 'unit'
  style: 'short' // 'long' | 'short' | 'narrow'
});
console.log(listFormatterWithOptions.format(list)); // 输出: 苹果、香蕉或橙子

8. Intl.Locale

用于表示语言环境。

javascript
// 基本用法
const locale = new Intl.Locale('zh-CN');
console.log(locale.language); // 输出: zh
console.log(locale.region); // 输出: CN
console.log(locale.calendar); // 输出: gregory

// 带选项的语言环境
const localeWithOptions = new Intl.Locale('zh-CN', {
  calendar: 'chinese',
  numberingSystem: 'hanidec'
});
console.log(localeWithOptions.calendar); // 输出: chinese
console.log(localeWithOptions.numberingSystem); // 输出: hanidec

国际化的实现策略

1. 资源文件

将所有需要翻译的文本存储在资源文件中,每个语言对应一个资源文件。

en.json

json
{
  "greeting": "Hello",
  "welcome": "Welcome to our website",
  "button": {
    "submit": "Submit",
    "cancel": "Cancel"
  }
}

zh-CN.json

json
{
  "greeting": "你好",
  "welcome": "欢迎访问我们的网站",
  "button": {
    "submit": "提交",
    "cancel": "取消"
  }
}

2. 翻译函数

创建一个翻译函数,根据当前语言环境返回对应的翻译文本。

javascript
// 加载资源文件
const translations = {
  'en-US': {
    greeting: 'Hello',
    welcome: 'Welcome to our website'
  },
  'zh-CN': {
    greeting: '你好',
    welcome: '欢迎访问我们的网站'
  }
};

// 当前语言环境
let currentLocale = 'en-US';

// 翻译函数
function t(key) {
  const keys = key.split('.');
  let result = translations[currentLocale];
  
  for (const k of keys) {
    if (result && result[k]) {
      result = result[k];
    } else {
      return key; // 如果没有找到翻译,返回原始键
    }
  }
  
  return result;
}

// 使用翻译函数
console.log(t('greeting')); // 输出: Hello
console.log(t('welcome')); // 输出: Welcome to our website

// 切换语言
currentLocale = 'zh-CN';
console.log(t('greeting')); // 输出: 你好
console.log(t('welcome')); // 输出: 欢迎访问我们的网站

3. 国际化库

使用成熟的国际化库来简化国际化的实现。

i18next

i18next 是一个功能强大的国际化库,支持多种框架和环境。

javascript
// 安装: npm install i18next

import i18next from 'i18next';

// 初始化
const i18n = i18next.init({
  lng: 'en',
  resources: {
    en: {
      translation: {
        greeting: 'Hello',
        welcome: 'Welcome to our website'
      }
    },
    zh: {
      translation: {
        greeting: '你好',
        welcome: '欢迎访问我们的网站'
      }
    }
  }
});

// 使用
console.log(i18n.t('greeting')); // 输出: Hello

// 切换语言
i18n.changeLanguage('zh');
console.log(i18n.t('greeting')); // 输出: 你好

react-i18next

react-i18next 是 i18next 的 React 绑定。

javascript
// 安装: npm install react-i18next i18next

import React from 'react';
import { useTranslation } from 'react-i18next';

function Greeting() {
  const { t, i18n } = useTranslation();
  
  const changeLanguage = (lng) => {
    i18n.changeLanguage(lng);
  };
  
  return (
    <div>
      <h1>{t('greeting')}</h1>
      <p>{t('welcome')}</p>
      <button onClick={() => changeLanguage('en')}>English</button>
      <button onClick={() => changeLanguage('zh')}>中文</button>
    </div>
  );
}

国际化的最佳实践

1. 设计时考虑国际化

  • 使用相对单位(如 em、rem)而不是绝对单位
  • 预留足够的空间,因为不同语言的文本长度可能不同
  • 避免使用硬编码的文本
  • 考虑从右到左(RTL)的语言

2. 文本处理

  • 将所有用户可见的文本存储在资源文件中
  • 使用键值对的方式管理翻译,而不是直接在代码中硬编码
  • 考虑复数形式和性别差异
  • 避免使用可能在不同文化中具有不同含义的隐喻和习语

3. 日期和时间

  • 使用 Intl.DateTimeFormat 格式化日期和时间
  • 考虑不同地区的日期格式(如 MM/DD/YYYY vs DD/MM/YYYY)
  • 考虑时区问题

4. 数字和货币

  • 使用 Intl.NumberFormat 格式化数字和货币
  • 考虑不同地区的数字分隔符和小数点
  • 考虑不同货币的符号和位置

5. 测试

  • 在不同语言环境下测试应用
  • 检查文本是否正确显示
  • 检查布局是否因文本长度变化而破坏
  • 检查日期、时间、数字和货币的格式是否正确

国际化的挑战

  1. 翻译质量:确保翻译准确、自然
  2. 维护成本:随着应用的增长,翻译资源需要不断更新
  3. 性能:加载多个语言的资源可能会影响性能
  4. 复杂性:处理复数形式、性别差异等复杂情况
  5. 文化差异:不同文化对同一事物可能有不同的理解

国际化工具

1. 翻译管理工具

  • Lokalise:翻译管理平台
  • Crowdin:本地化管理平台
  • Transifex:翻译协作平台

2. 自动翻译工具

  • Google Translate API:自动翻译服务
  • DeepL API:高质量的机器翻译

3. 国际化检查工具

  • i18n-lint:检查国际化相关的问题
  • eslint-plugin-i18n:ESLint 插件,检查未国际化的文本

总结

国际化是构建全球应用的重要组成部分。通过使用 JavaScript 内置的 Intl 对象和成熟的国际化库,我们可以实现对多种语言和地区的支持。

有效的国际化策略包括:

  1. 使用资源文件管理翻译文本
  2. 使用翻译函数或国际化库处理文本翻译
  3. 使用 Intl 对象格式化日期、时间、数字和货币
  4. 设计时考虑国际化的需求
  5. 测试不同语言环境下的应用表现

通过合理的国际化实现,我们可以构建更加用户友好、全球适用的应用程序。