前端国际化方案
通用型前端国际化方案,在Vue、React中都适用,已落地实践。
一、常见的国际化方案
1.1 通过 i18n 插件配置语种
这是最传统的国际化方案,使用成熟的国际化插件(如 vue-i18n、react-i18next 等)进行配置。
核心流程:
- 安装并配置国际化插件
- 创建语言包文件(如
zh-CN.json、en-US.json) - 在代码中使用 key 调用翻译函数
示例配置:
javascript
// i18n.js
import { createI18n } from 'vue-i18n'
import zhCN from './locales/zh-CN.json'
import enUS from './locales/en-US.json'
const i18n = createI18n({
locale: 'zh-CN',
fallbackLocale: 'en-US',
messages: {
'zh-CN': zhCN,
'en-US': enUS
}
})
export default i18n
语言包示例:
json
// locales/zh-CN.json
{
"nav.home": "首页",
"nav.about": "关于我们",
"form.submit": "提交"
}
json
// locales/en-US.json
{
"nav.home": "Home",
"nav.about": "About Us",
"form.submit": "Submit"
}
代码中使用:
vue
<template>
<div>
<h1>{{ $t('nav.home') }}</h1>
<button>{{ $t('form.submit') }}</button>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
console.log(t('nav.home'))
</script>
优点:
- 生态成熟,文档完善
- 支持复数、日期格式化等高级功能
- 社区活跃,问题容易解决
缺点:
- 需要手动维护所有 key
- 新增文本时需要同时修改代码和语言包
- 容易出现 key 命名冲突
1.2 使用云文档维护语言包
通过在线文档(如腾讯文档、飞书文档、Google Sheets)维护语言包,通过 API 请求动态加载。
核心流程:
- 在云文档中创建多语言对照表
- 开发前置请求接口,获取语言包数据
- 注册到国际化系统中
- 代码中使用 key 调用
云文档结构示例:
| key | zh-CN | en-US | ja-JP |
|---|---|---|---|
| nav.home | 首页 | Home | ホーム |
| nav.about | 关于我们 | About Us | 私たちについて |
代码实现:
javascript
// i18n-loader.js
export async function loadLocaleFromCloud(locale) {
const response = await fetch(`/api/i18n/${locale}`)
const messages = await response.json()
i18n.setLocaleMessage(locale, messages)
i18n.locale = locale
}
// 应用启动时加载
await loadLocaleFromCloud('zh-CN')
优点:
- 非技术人员可参与翻译维护
- 支持实时更新的翻译内容
- 便于多语言协作翻译
缺点:
- 依赖网络请求,首屏加载可能变慢
- 需要处理加载状态和错误情况
- 云文档的稳定性影响应用
1.3 使用自动国际化插件
通过 AST(抽象语法树)分析代码中的中文文本,自动生成 hash 值作为 key,并建立多语言映射。
核心原理:
- 在构建阶段扫描代码中的中文字符串
- 对中文内容进行 hash 计算生成唯一 key
- 自动调用翻译 API 生成多语言版本
- 运行时根据 hash 值映射对应语种
工作流程图:
源代码(含中文)
↓
AST 语法树分析
↓
提取中文字符串 → 生成 hash 值
↓
调用翻译 API → 生成多语言包
↓
替换代码中的中文为 hash key
↓
构建输出
示例:
javascript
// 原始代码
const title = '药物研发'
const placeholder = '输入药品名称'
// 构建后代码
const title = $t('h24lnb4') // hash 值
const placeholder = $t('k25g8h4')
自动生成的语言包:
json
{
"h24lnb4": {
"zh-CN": "药物研发",
"en": "drug development"
},
"k25g8h4": {
"zh-CN": "输入药品名称",
"en": "Enter drug name"
}
}
优点:
- 开发体验好,直接写中文
- 自动化程度高,减少人工成本
- 不需要手动维护 key 命名
缺点:
- 机翻质量可能不准确
- 无法处理上下文相关的翻译
- 需要处理 hash 冲突
二、自动化插件 + 自定义 key 结合
结合自动化和手动配置的优势,先通过自动化插件扫描翻译,再对特殊内容进行自定义配置。
2.1 自动化翻译流程
开发过程中 95% 的场景可以直接写中文,构建时自动翻译:
配置文件中使用:
javascript
export const baseConfig = [{
id: "mixed",
title: "综合检索",
group: "基础信息",
type: "single",
fieldKey: "mixed",
placeholder: "输入药品名称/研发企业/适应症/靶点/工艺技术/药物类型/药物标签"
}]
模板中使用:
vue
<a-checkbox
v-if="current === 1"
:checked="indicationProgressChina"
@change="handleChinaChange"
>仅看全球、中国研发阶段</a-checkbox>
自动生成的语言包:
构建工具会自动扫描并生成 src/lang/index.json:
json
{
"h24lnb4": {
"zh-CN": "药物研发",
"en": "drug development"
},
"k25g8h4": {
"zh-CN": "高级检索",
"en": "Advanced Search"
},
"ag1z3r4": {
"zh-CN": "下载记录",
"en": "Download History"
}
}
注意: 自动生成的语言包文件无需手动修改,在多人协作中应避免直接修改,防止合并冲突。
2.2 重置机翻内容
对于机翻不准确的内容,通过自定义文件进行覆盖:
创建 src/lang/locales/custom.json:
json
{
"h24lnb4": {
"zh-CN": "药物研发",
"en": "Drug R&D"
},
"k25g8h4": {
"zh-CN": "高级检索",
"en": "Advanced Search"
},
"phz52": {
"zh-CN": "重置",
"en": "Reset"
}
}
建议:
- 由专人负责维护自定义翻译内容
- 保持术语的一致性
- 定期审查和更新翻译质量
2.3 自定义语言包配置
当自动解析未被识别或识别错误时,采用手动配置方式。
(1)业务模块级配置
在任何业务模块目录下创建语言包文件,会被自动注册:
src/views/database/developdb/
├── locales/
│ ├── zh-CN.ts
│ └── en-US.ts
└── components/
zh-CN.ts:
typescript
export default {
"developdb.list.title": "药物研发",
"developdb.list.des": "描述信息",
"developdb.btn.add": "添加记录"
}
en-US.ts:
typescript
export default {
"developdb.list.title": "Drug Development",
"developdb.list.des": "Description",
"developdb.btn.add": "Add Record"
}
建议: 定义 key 时带上模块前缀,避免与其他模块冲突。
(2)通用组件级配置
将通用组件、工具函数的语言包配置在统一目录:
src/lang/locales/
├── zh-CN.ts
├── en-US.ts
└── locales/
├── zh-CN.ts
└── en-US.ts
src/lang/locales/locales/zh-CN.ts:
typescript
export default {
"component.title": "组件名称",
"common.confirm": "确认",
"common.cancel": "取消"
}
(3)拆分文件管理
当语言包内容较多时,可以拆分到多个文件:
src/lang/locales/locales/zh-CN/
├── index.ts
├── common.ts
├── component.ts
└── hooks.ts
index.ts:
typescript
import common from './common'
import component from './component'
import hooks from './hooks'
export default {
common,
component,
hooks,
"component.title": "组件名称"
}
2.4 使用方式
在代码中使用 $t 函数调用自定义 key:
在 script setup 中:
vue
<script setup lang="ts">
const title = $t("developdb.list.title")
const confirmText = $t("common.confirm")
</script>
在模板中:
vue
<template>
<div>
<h1>{{ $t('component.title') }}</h1>
<button>{{ $t('common.confirm') }}</button>
</div>
</template>
2.5 优先级规则
系统按照以下优先级加载语言包:
-
自定义语言包优先级最高
src/lang/locales/locales目录- 业务模块
locales目录
-
自动生成语言包优先级较低
- 构建时扫描生成的 key
-
同级别后定义的覆盖先定义的
- 配置相同 key 时,后面的会覆盖前面的
优先级示意图:
┌─────────────────────────┐
│ 自定义语言包 (最高) │
│ src/lang/locales/ │
├─────────────────────────┤
│ 业务模块语言包 │
│ views/*/locales/ │
├─────────────────────────┤
│ 自动生成语言包 (最低) │
│ src/lang/index.json │
└─────────────────────────┘
三、封装通用组件、hook
3.1 语种状态管理 Hook
封装语种管理的通用逻辑,提供统一的 API:
src/store/lang.ts:
typescript
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useLangModel = defineStore('lang', () => {
// 当前语种
const currentLang = ref<'zh-CN' | 'en-US'>('zh-CN')
// 语种列表
const langList = [
{ label: '中文', value: 'zh-CN' },
{ label: 'English', value: 'en-US' }
]
// 切换语种
const setLang = (lang: 'zh-CN' | 'en-US') => {
currentLang.value = lang
localStorage.setItem('lang', lang)
// 通知 i18n 更新
i18n.global.locale = lang
}
// 获取存储的语种
const initLang = () => {
const saved = localStorage.getItem('lang') as 'zh-CN' | 'en-US'
if (saved) {
currentLang.value = saved
}
}
return {
currentLang,
langList,
setLang,
initLang
}
})
使用示例:
vue
<script setup lang="ts">
import { useLangModel } from '@/store/lang'
const langStore = useLangModel()
// 获取当前语种
console.log(langStore.currentLang)
// 切换语种
langStore.setLang('en-US')
</script>
<template>
<a-select v-model:value="langStore.currentLang" @change="langStore.setLang">
<a-select-option
v-for="lang in langStore.langList"
:key="lang.value"
:value="lang.value"
>
{{ lang.label }}
</a-select-option>
</a-select>
</template>
3.2 LangProvider 组件
封装语种条件渲染组件,用于不同语种显示不同内容:
src/components/LangProvider/Index.vue:
vue
<script setup lang="ts">
import { computed } from 'vue'
import { useLangModel } from '@/store/lang'
const langStore = useLangModel()
const currentLang = computed(() => langStore.currentLang)
</script>
<template>
<slot :name="currentLang"></slot>
<slot v-if="!$slots[currentLang]"></slot>
</template>
使用示例:
vue
<script setup>
import LangProvider from '@/components/LangProvider/Index.vue'
</script>
<template>
<LangProvider>
<template #zh-CN>
<img src="/imgs/logo.svg" alt="中文 Logo">
</template>
<template #en-US>
<img src="/imgs/logo-en.svg" alt="English Logo">
</template>
</LangProvider>
</template>
适用场景:
- 不同语种使用不同的图片资源
- 不同语种布局差异较大
- 特定语种的特殊内容
3.3 通用翻译 Hook
封装翻译相关的通用函数:
src/hooks/useTranslation.ts:
typescript
import { useI18n } from 'vue-i18n'
export function useTranslation() {
const { t, locale } = useI18n()
// 翻译文本
const translate = (key: string, params?: Record<string, any>) => {
return t(key, params)
}
// 判断是否为中文
const isZhCN = computed(() => locale.value === 'zh-CN')
// 判断是否为英文
const isEnUS = computed(() => locale.value === 'en-US')
// 获取当前语种标签
const currentLangLabel = computed(() => {
return isZhCN.value ? '中文' : 'English'
})
return {
translate,
t,
locale,
isZhCN,
isEnUS,
currentLangLabel
}
}
使用示例:
vue
<script setup lang="ts">
import { useTranslation } from '@/hooks/useTranslation'
const { t, isZhCN, currentLangLabel } = useTranslation()
</script>
<template>
<div>
<h1>{{ t('page.title') }}</h1>
<p>当前语言:{{ currentLangLabel }}</p>
<div v-if="isZhCN">
<!-- 仅中文显示的内容 -->
</div>
</div>
</template>
四、涉及的范围
4.1 字符长度适配
英文文本长度通常比中文长 30%-50%,需要特别注意 UI 适配:
常见问题:
图:中文"药物研发"与英文"Drug Research and Development"的长度对比
解决方案:
-
使用弹性布局
vue<template> <button class="i18n-btn">{{ $t('action.submit') }}</button> </template> <style scoped> .i18n-btn { min-width: 80px; padding: 0 16px; white-space: nowrap; } </style> -
设置最大宽度 + 换行
css.i18n-text { max-width: 200px; word-break: break-word; } -
使用 CSS Grid 自适应
css.i18n-grid { display: grid; grid-template-columns: auto 1fr; gap: 16px; } -
针对不同语种调整样式
vue<template> <div :class="['container', `lang-${currentLang}`]"> <!-- 内容 --> </div> </template> <style> .container.lang-zh-CN { font-size: 14px; } .container.lang-en-US { font-size: 12px; } </style>
4.2 三方包的国际化
检查引入的三方包是否内置中文,是否提供切换语种的 API。
已适配的常见三方包:
javascript
// 1. Ant Design Vue
import zhCN from 'ant-design-vue/es/locale/zh_CN'
import enUS from 'ant-design-vue/es/locale/en_US'
const locale = computed(() => currentLang.value === 'zh-CN' ? zhCN : enUS)
// 2. Element Plus
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
const locale = computed(() => currentLang.value === 'zh-CN' ? zhCn : en)
// 3. Day.js
import 'dayjs/locale/zh-cn'
import 'dayjs/locale/en'
watch(currentLang, (lang) => {
dayjs.locale(lang === 'zh-CN' ? 'zh-cn' : 'en')
})
// 4. 绘图库(如 tqsk-draw)
import { setLocale } from 'tqsk-draw'
setLocale(currentLang.value)
检查清单:
- 日期时间库(Day.js、Moment.js)
- UI 组件库(Ant Design、Element Plus)
- 图表库(ECharts、AntV)
- 富文本编辑器
- 表格组件
- 表单验证库
未内置国际化的处理:
javascript
// 通过全局配置覆盖
const customLocale = {
...defaultLocale,
lang: {
...defaultLocale.lang,
locale: currentLang.value,
// 自定义翻译
submitText: $t('form.submit'),
cancelText: $t('form.cancel')
}
}
4.3 图片资源国际化
图片上的文字也需要进行国际化处理。
处理方案:
-
使用 SVG Icon 替代图片文字
vue<template> <!-- 不推荐:图片包含文字 --> <img src="/imgs/banner-cn.png" /> <!-- 推荐:使用图标 + 文字分离 --> <div class="banner"> <IconLogo /> <span>{{ $t('banner.title') }}</span> </div> </template> -
分语种提供图片资源
vue<template> <LangProvider> <template #zh-CN> <img src="/imgs/guide-cn.png" /> </template> <template #en-US> <img src="/imgs/guide-en.png" /> </template> </LangProvider> </template> -
使用 CSS 动态替换
css.logo { background-image: url('/imgs/logo.svg'); } html[lang="en"] .logo { background-image: url('/imgs/logo-en.svg'); }
检查清单:
- Logo 图片
- Banner 图
- 引导页图片
- 帮助文档截图
- 流程图、架构图
- 空状态插图
4.4 其他注意事项
1. 日期时间格式
不同地区的日期格式不同:
javascript
// 中国:2024 年 1 月 15 日
// 美国:01/15/2024
// 欧洲:15/01/2024
const formatDate = (date) => {
return new Intl.DateTimeFormat(currentLang.value).format(date)
}
2. 数字格式
javascript
// 中国:1,234,567.89
// 德国:1.234.567,89
// 法国:1 234 567,89
const formatNumber = (num) => {
return new Intl.NumberFormat(currentLang.value).format(num)
}
3. 货币格式
javascript
const formatCurrency = (amount) => {
return new Intl.NumberFormat(currentLang.value, {
style: 'currency',
currency: currentLang.value === 'zh-CN' ? 'CNY' : 'USD'
}).format(amount)
}
4. 文本方向(RTL 支持)
css
/* 支持从右到左的语言 */
html[dir="rtl"] {
direction: rtl;
}
五、vite-auto-i18n-plugin
5.1 插件介绍
vite-auto-i18n-plugin 是专为 Vite 构建工具设计的自动化国际化插件,通过 AST 分析实现中文自动翻译。
5.2 安装
bash
npm install vite-auto-i18n-plugin -D
5.3 基础配置
vite.config.ts:
typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vitePluginsAutoI18n, { YoudaoTranslator } from 'vite-auto-i18n-plugin'
export default defineConfig({
plugins: [
vue(),
vitePluginsAutoI18n({
translator: new YoudaoTranslator({
appId: '4cdb9baea8066fef',
appKey: 'ONI6AerZnGRyDqr3w7UM730mPuF8mB3j'
})
})
]
})
六、webpack-auto-i18n-plugin
6.1 插件介绍
webpack-auto-i18n-plugin 是为 Webpack 构建工具设计的自动化国际化插件,功能与 Vite 插件类似。
6.2 安装
bash
npm install webpack-auto-i18n-plugin -D
6.3 基础配置
webpack.config.js:
javascript
const path = require('path')
const webpackPluginsAutoI18n = require('webpack-auto-i18n-plugin')
const i18nPlugin = new webpackPluginsAutoI18n.default({
translator: new YoudaoTranslator({
appId: 'appId',
appKey: 'appKey'
})
})
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
plugins: [
i18nPlugin
],
}
总结
本文介绍了前端国际化的完整方案,从常见的三种方案对比,到自动化插件与自定义 key 的结合使用,再到通用组件和 Hook 的封装,最后详细说明了在 Vite 和 Webpack 中的具体实现,并重点介绍了在 React 项目中的落地实践。
核心要点:
- 选择合适的方案:根据项目规模和技术栈选择最适合的国际化方案
- 自动化优先:优先使用自动化工具减少人工成本
- 自定义补充:对特殊内容进行手动配置保证翻译质量
- 组件化封装:通过通用组件和 Hook 提高代码复用
- 全面考虑:注意字符长度、三方包、图片等细节问题
最佳实践:
- 开发阶段直接写中文,构建时自动翻译
- 由专人维护自定义翻译,保证术语一致性
- 使用 Hash 映射避免 key 命名冲突
- 针对不同语种进行 UI 适配
- 检查并适配三方包的国际化配置
技术栈无关:
- ✅ Vue 2/3 + Vite/Webpack
- ✅ React + Webpack
- ✅ 其他支持 AST 分析的前端框架
通过这套方案,可以高效地实现前端国际化,并在长期维护中保持代码的可读性和可维护性。
核心要点:
- 选择合适的方案:根据项目规模和技术栈选择最适合的国际化方案
- 自动化优先:优先使用自动化工具减少人工成本
- 自定义补充:对特殊内容进行手动配置保证翻译质量
- 组件化封装:通过通用组件和 Hook 提高代码复用
- 全面考虑:注意字符长度、三方包、图片等细节问题
最佳实践:
- 开发阶段直接写中文,构建时自动翻译
- 由专人维护自定义翻译,保证术语一致性
- 使用 Hash 映射避免 key 命名冲突
- 针对不同语种进行 UI 适配
- 检查并适配三方包的国际化配置
通过这套方案,可以高效地实现前端国际化,并在长期维护中保持代码的可读性和可维护性。