前端国际化 (i18n) 最佳实践:从零到一打造多语言应用

By | 2026年9月2日

引言

在当今全球化的互联网环境中,应用程序的国际化(Internationalization,简称 i18n)已成为一项基本要求。无论是面向海外用户的产品,还是国内的多语言需求(如中英文切换),前端开发者都需要掌握一套高效、可维护的国际化方案。

然而,许多项目在初期忽略了国际化设计,导致后期重构成本高昂;或者虽然引入了 i18n 库,却因为配置混乱、语言包管理不善而难以维护。本文将从零开始,带你构建一套完整的前端国际化最佳实践,涵盖技术选型、工程结构、动态切换、自动化工具等核心环节,并分享一些独特的实战经验。

为什么需要国际化?

国际化不仅仅是翻译文本,它还涉及日期、数字、货币、时区、排序规则等文化差异。良好的国际化方案能显著提升用户体验,扩大产品的市场覆盖范围。对于开发者而言,合理的架构能减少重复劳动,提高开发效率。

技术选型:Vue I18n vs React Intl

前端生态中,最流行的 i18n 库分别是 Vue 生态的 vue-i18n 和 React 生态的 react-intl(或 react-i18next)。本文以 Vue 3 + vue-i18n 为例,但核心思想同样适用于 React。

安装与初始化

首先,创建 Vue 3 项目(如果你还没有):


npm create vite@latest my-app -- --template vue
cd my-app
npm install vue-i18n@next

src/main.js 中初始化 vue-i18n


import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
import App from './App.vue'

// 导入语言包
import en from './locales/en.json'
import zh from './locales/zh.json'

const i18n = createI18n({
  legacy: false, // 使用 Composition API 模式
  locale: 'zh',  // 默认语言
  fallbackLocale: 'en', // 回退语言
  messages: {
    en,
    zh
  }
})

const app = createApp(App)
app.use(i18n)
app.mount('#app')

语言包的结构设计

语言包是国际化的核心。一个优秀的语言包结构应当清晰、易于维护。建议按模块划分,而不是将所有文本放在一个扁平对象中。

例如,src/locales/zh.json


{
  "common": {
    "confirm": "确认",
    "cancel": "取消",
    "loading": "加载中..."
  },
  "login": {
    "title": "登录",
    "username": "用户名",
    "password": "密码",
    "submit": "登录"
  },
  "errors": {
    "required": "该字段为必填项",
    "invalidEmail": "邮箱格式不正确"
  }
}

对应的 en.json


{
  "common": {
    "confirm": "Confirm",
    "cancel": "Cancel",
    "loading": "Loading..."
  },
  "login": {
    "title": "Login",
    "username": "Username",
    "password": "Password",
    "submit": "Log in"
  },
  "errors": {
    "required": "This field is required",
    "invalidEmail": "Invalid email format"
  }
}

在组件中使用

在 Vue 组件中,你可以通过 $t 函数(Options API)或 useI18n(Composition API)来访问翻译。

Options API 用法


<template>
  <div>
    <h1>{{ $t('login.title') }}</h1>
    <form>
      <input :placeholder="$t('login.username')" />
      <button>{{ $t('login.submit') }}</button>
    </form>
  </div>
</template>

Composition API 用法


<template>
  <div>
    <h1>{{ t('login.title') }}</h1>
    <p>{{ t('common.loading') }}</p>
  </div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'

const { t } = useI18n()
</script>

动态语言切换

语言切换是国际化的核心功能之一。通常,我们会将用户选择的语言存储在本地(如 localStorage),并在应用启动时读取。

实现语言切换

src/composables/useLocale.js 中封装切换逻辑:


import { useI18n } from 'vue-i18n'
import { ref } from 'vue'

const supportedLocales = ['zh', 'en']
const locale = ref(localStorage.getItem('locale') || 'zh')

export function useLocale() {
  const { locale: i18nLocale } = useI18n()

  const setLocale = (newLocale) => {
    if (!supportedLocales.includes(newLocale)) {
      console.warn(`Unsupported locale: ${newLocale}`)
      return
    }
    locale.value = newLocale
    i18nLocale.value = newLocale
    localStorage.setItem('locale', newLocale)
    // 可选:更新 html lang 属性
    document.querySelector('html').setAttribute('lang', newLocale)
  }

  return {
    locale,
    setLocale
  }
}

在组件中使用:


<template>
  <select :value="locale" @change="onChange">
    <option value="zh">中文</option>
    <option value="en">English</option>
  </select>
</template>

<script setup>
import { useLocale } from '../composables/useLocale'

const { locale, setLocale } = useLocale()

const onChange = (event) => {
  setLocale(event.target.value)
}
</script>

初始化时读取本地存储

main.js 中,初始化 vue-i18n 时从 localStorage 读取语言:


const savedLocale = localStorage.getItem('locale') || 'zh'

const i18n = createI18n({
  legacy: false,
  locale: savedLocale,
  fallbackLocale: 'en',
  messages: { en, zh }
})

处理日期、数字和货币

国际化不仅是文本翻译,还包括日期、数字和货币的本地化。vue-i18n 提供了 d(datetime)和 n(number)函数。

日期格式化

在语言包中定义格式:


{
  "datetime": {
    "short": {
      "year": "numeric",
      "month": "short",
      "day": "numeric"
    }
  }
}

组件中使用:


<template>
  <p>{{ d(new Date(), 'short') }}</p>
</template>

<script setup>
import { useI18n } from 'vue-i18n'

const { d } = useI18n()
</script>

对于中文环境,会输出类似“2023年11月20日”,英文环境则输出“Nov 20, 2023”。

数字和货币


<template>
  <p>{{ n(12345.67, 'currency') }}</p>
</template>

在语言包中定义 currency 格式:


{
  "currency": {
    "style": "currency",
    "currency": "CNY",
    "currencyDisplay": "symbol"
  }
}

这样,中文环境下显示“¥12,345.67”,英文环境下你可能需要定义不同的货币格式。

语言包管理的自动化

随着项目规模增大,手动维护语言包容易出错。我们可以使用工具自动提取代码中的 $t 键,并生成缺失的翻译。

使用 vue-i18n-extract 工具

安装:


npm install -D vue-i18n-extract

package.json 中配置脚本:


{
  "scripts": {
    "i18n:extract": "vue-i18n-extract --vueFiles './src/**/*.vue' --languageFiles './src/locales/*.json'"
  }
}

运行后,它会生成一个报告,列出缺失的键和未使用的键。你可以将其集成到 CI 中,确保语言包完整性。

常见坑与解决方案

1. 键名冲突

避免使用过于通用的键名,如 titlename。建议按模块前缀,如 login.title

2. 动态翻译键

当需要动态拼接键时,使用 $t 的第二个参数:


const key = `errors.${errorCode}`
$t(key)

3. 富文本与 HTML

如果翻译中包含 HTML 标签,使用 v-htmli18n-t 组件,但要注意 XSS 风险。

4. 回退语言未设置

务必设置 fallbackLocale,避免因语言包缺失导致界面空白。

总结

本文从技术选型、语言包设计、动态切换、格式化到自动化工具,系统介绍了前端国际化的最佳实践。关键在于:

  • 语言包按模块划分,保持清晰结构
  • 合理使用 fallbackLocale 和本地存储
  • 利用工具自动化维护语言包

下一步,你可以考虑集成第三方翻译服务(如 Crowdin)或实现基于用户地区的自动语言检测。国际化是一个持续优化的过程,希望本文能为你打下坚实的基础。