国际化开发文档
1. 概述
梵医云前端项目使用 vue-i18n 实现国际化功能,支持多语言切换,目前支持简体中文和英文两种语言。
1.1 国际化特点
- 基于vue-i18n:使用成熟的vue-i18n库实现
- 按需加载:语言包按需加载,减少初始包体积
- 类型安全:完整的TypeScript类型定义
- 持久化存储:语言设置持久化到本地缓存
- Element Plus集成:自动同步Element Plus组件的语言
1.2 支持的语言
| 语言代码 | 语言名称 | 文件名 |
|---|---|---|
| zh-CN | 简体中文 | zh-CN.ts |
| en | English | en.ts |
2. 项目结构
2.1 国际化文件结构
fanyi-cloud-ui/
├── src/
│ ├── locales/ # 语言包目录
│ │ ├── zh-CN.ts # 简体中文语言包
│ │ └── en.ts # 英文语言包
│ ├── plugins/
│ │ └── vueI18n/ # vue-i18n插件
│ │ ├── index.ts # i18n配置
│ │ └── helper.ts # 辅助函数
│ ├── store/
│ │ └── modules/
│ │ └── locale.ts # 语言状态管理
│ ├── hooks/
│ │ └── web/
│ │ ├── useI18n.ts # i18n Hook
│ │ └── useLocale.ts # 语言切换Hook
│ └── layout/
│ └── components/
│ └── LocaleDropdown/ # 语言切换组件3. 配置说明
3.1 vue-i18n配置
配置文件:[src/plugins/vueI18n/index.ts](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/src/plugins/vueI18n/index.ts)
import type { App } from 'vue'
import { createI18n } from 'vue-i18n'
import { useLocaleStoreWithOut } from '@/store/modules/locale'
import type { I18n, I18nOptions } from 'vue-i18n'
import { setHtmlPageLang } from './helper'
export let i18n: ReturnType<typeof createI18n>
const createI18nOptions = async (): Promise<I18nOptions> => {
const localeStore = useLocaleStoreWithOut()
const locale = localeStore.getCurrentLocale
const localeMap = localeStore.getLocaleMap
const defaultLocal = await import(`../../locales/${locale.lang}.ts`)
const message = defaultLocal.default ?? {}
setHtmlPageLang(locale.lang)
localeStore.setCurrentLocale({
lang: locale.lang
})
return {
legacy: false, // 使用Composition API模式
locale: locale.lang, // 当前语言
fallbackLocale: locale.lang, // 回退语言
messages: {
[locale.lang]: message
},
availableLocales: localeMap.map((v) => v.lang), // 可用语言列表
sync: true, // 同步语言
silentTranslationWarn: true, // 静默翻译警告
missingWarn: false,
silentFallbackWarn: true
}
}
export const setupI18n = async (app: App<Element>) => {
const options = await createI18nOptions()
i18n = createI18n(options) as I18n
app.use(i18n)
}3.2 应用初始化
在 [src/main.ts](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/src/main.ts) 中初始化i18n:
import { setupI18n } from '@/plugins/vueI18n'
const setupAll = async () => {
const app = createApp(App)
// 在其他插件之前初始化i18n
await setupI18n(app)
// ... 其他初始化代码
}4. 语言包管理
4.1 语言包结构
语言包文件位于 src/locales/ 目录下,采用模块化结构:
export default {
common: {
// 通用文本
inputText: '请输入',
selectText: '请选择',
query: '查询',
reset: '重置',
// ...
},
login: {
// 登录相关
welcome: '欢迎使用梵医云系统',
username: '用户名',
password: '密码',
// ...
},
sys: {
api: {
// API相关
operationFailed: '操作失败',
// ...
},
// ...
}
// ...
}4.2 语言包分类
| 分类 | 说明 | 示例 |
|---|---|---|
| common | 通用文本 | 按钮、提示、操作等 |
| login | 登录相关 | 登录表单、验证码等 |
| sys | 系统相关 | API错误、异常处理等 |
| profile | 个人中心 | 用户信息、密码修改等 |
| form | 表单相关 | 表单组件、验证等 |
| table | 表格相关 | 表格操作、分页等 |
| action | 操作相关 | 新增、编辑、删除等 |
4.3 添加新的翻译
在语言包文件中添加新的翻译:
// src/locales/zh-CN.ts
export default {
// ... 其他翻译
custom: {
newFeature: '新功能',
welcomeMessage: '欢迎使用新功能'
}
}
// src/locales/en.ts
export default {
// ... other translations
custom: {
newFeature: 'New Feature',
welcomeMessage: 'Welcome to new feature'
}
}5. 使用方法
5.1 在组件中使用
5.1.1 使用useI18n Hook
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
// 简单翻译
const title = computed(() => t('common.query'))
// 带参数的翻译
const message = computed(() => t('common.welcome', { name: '用户' }))
// 带数组的翻译
const items = computed(() => t('common.items', ['项目1', '项目2']))
</script>
<template>
<div>
<h1>{{ t('common.welcome') }}</h1>
<el-button>{{ t('common.query') }}</el-button>
<p>{{ t('common.welcome', { name: '张三' }) }}</p>
</div>
</template>5.1.2 使用命名空间
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
// 使用命名空间
const { t } = useI18n('login')
const usernameLabel = computed(() => t('username'))
const passwordLabel = computed(() => t('password'))
</script>
<template>
<el-form>
<el-form-item :label="t('username')">
<el-input />
</el-form-item>
<el-form-item :label="t('password')">
<el-input type="password" />
</el-form-item>
</el-form>
</template>5.1.3 在模板中直接使用
<template>
<div>
<!-- 直接使用t函数 -->
<h1>{{ t('common.welcome') }}</h1>
<!-- 带参数 -->
<p>{{ t('common.welcome', { name: userName }) }}</p>
<!-- 嵌套key -->
<span>{{ t('sys.api.operationFailed') }}</span>
</div>
</template>5.2 在JS/TS中使用
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
// 在函数中使用
function showMessage() {
const message = t('common.success')
ElMessage.success(message)
}
// 在异步函数中使用
async function fetchData() {
try {
await api.getData()
ElMessage.success(t('common.success'))
} catch (error) {
ElMessage.error(t('sys.api.operationFailed'))
}
}5.3 在路由中使用
// src/router/modules/system.ts
export default {
path: '/system',
name: 'System',
component: Layout,
redirect: '/system/user',
meta: {
title: 'system', // 使用语言包中的key
icon: 'ep:setting'
}
}5.4 在表单验证中使用
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const rules = {
username: [
{ required: true, message: t('common.required'), trigger: 'blur' },
{ min: 3, max: 20, message: t('common.lengthError'), trigger: 'blur' }
],
email: [
{ required: true, message: t('common.required'), trigger: 'blur' },
{ type: 'email', message: t('common.validateEmail'), trigger: ['blur', 'change'] }
]
}6. 语言切换
6.1 LocaleDropdown组件
语言切换组件位于:[src/layout/components/LocaleDropdown/src/LocaleDropdown.vue](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/src/layout/components/LocaleDropdown/src/LocaleDropdown.vue)
<template>
<ElDropdown trigger="click" @command="setLang">
<Icon icon="ion:language-sharp" />
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem
v-for="item in langMap"
:key="item.lang"
:command="item.lang"
>
{{ item.name }}
</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
</template>
<script setup lang="ts">
import { useLocaleStore } from '@/store/modules/locale'
import { useLocale } from '@/hooks/web/useLocale'
const localeStore = useLocaleStore()
const langMap = computed(() => localeStore.getLocaleMap)
const currentLang = computed(() => localeStore.getCurrentLocale)
const setLang = (lang: LocaleType) => {
if (lang === unref(currentLang).lang) return
// 重新加载页面让整个语言多初始化
window.location.reload()
localeStore.setCurrentLocale({ lang })
const { changeLocale } = useLocale()
changeLocale(lang)
}
</script>6.2 使用useLocale Hook
import { useLocale } from '@/hooks/web/useLocale'
const { changeLocale } = useLocale()
// 切换语言
const switchLanguage = async (lang: 'zh-CN' | 'en') => {
await changeLocale(lang)
// 语言切换后会自动刷新页面
}6.3 手动切换语言
import { useLocaleStoreWithOut } from '@/store/modules/locale'
import { useLocale } from '@/hooks/web/useLocale'
const localeStore = useLocaleStoreWithOut()
const { changeLocale } = useLocale()
// 设置语言
const setLanguage = async (lang: 'zh-CN' | 'en') => {
// 更新store
localeStore.setCurrentLocale({ lang })
// 切换i18n语言
await changeLocale(lang)
// 刷新页面
window.location.reload()
}7. 状态管理
7.1 Locale Store
Store文件:[src/store/modules/locale.ts](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/src/store/modules/locale.ts)
import { defineStore } from 'pinia'
import { store } from '../index'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
const elLocaleMap = {
'zh-CN': zhCn,
en: en
}
interface LocaleState {
currentLocale: LocaleDropdownType
localeMap: LocaleDropdownType[]
}
export const useLocaleStore = defineStore('locales', {
state: (): LocaleState => {
return {
currentLocale: {
lang: wsCache.get(CACHE_KEY.LANG) || 'zh-CN',
elLocale: elLocaleMap[wsCache.get(CACHE_KEY.LANG) || 'zh-CN']
},
localeMap: [
{
lang: 'zh-CN',
name: '简体中文'
},
{
lang: 'en',
name: 'English'
}
]
}
},
getters: {
getCurrentLocale(): LocaleDropdownType {
return this.currentLocale
},
getLocaleMap(): LocaleDropdownType[] {
return this.localeMap
}
},
actions: {
setCurrentLocale(localeMap: LocaleDropdownType) {
this.currentLocale.lang = localeMap?.lang
this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
wsCache.set(CACHE_KEY.LANG, localeMap?.lang)
}
}
})7.2 使用Store
import { useLocaleStore } from '@/store/modules/locale'
const localeStore = useLocaleStore()
// 获取当前语言
const currentLang = computed(() => localeStore.getCurrentLocale.lang)
// 获取语言列表
const langList = computed(() => localeStore.getLocaleMap)
// 设置语言
localeStore.setCurrentLocale({ lang: 'en' })8. 添加新语言
8.1 创建语言包文件
在 src/locales/ 目录下创建新的语言包文件:
// src/locales/ja.ts
export default {
common: {
inputText: '入力してください',
selectText: '選択してください',
query: '検索',
reset: 'リセット',
// ...
},
login: {
welcome: '梵医云システムへようこそ',
username: 'ユーザー名',
password: 'パスワード',
// ...
}
// ...
}8.2 更新Locale Store
在 [src/store/modules/locale.ts](file:///e:/git.fanyicloud.com.cn/fanyi-cloud-ui/src/store/modules/locale.ts) 中添加新语言:
import ja from 'element-plus/es/locale/lang/ja'
const elLocaleMap = {
'zh-CN': zhCn,
en: en,
'ja': ja // 添加日语
}
export const useLocaleStore = defineStore('locales', {
state: (): LocaleState => {
return {
currentLocale: {
lang: wsCache.get(CACHE_KEY.LANG) || 'zh-CN',
elLocale: elLocaleMap[wsCache.get(CACHE_KEY.LANG) || 'zh-CN']
},
localeMap: [
{
lang: 'zh-CN',
name: '简体中文'
},
{
lang: 'en',
name: 'English'
},
{
lang: 'ja', // 添加日语
name: '日本語'
}
]
}
}
})8.3 添加类型定义
在类型定义文件中添加新语言类型:
// src/types/localeDropdown.d.ts
export type LocaleType = 'zh-CN' | 'en' | 'ja'9. 最佳实践
9.1 命名规范
- 使用点号分隔的层级结构
- 使用小写字母和下划线
- 保持简洁明了
// 推荐
{
common: {
query: '查询',
reset: '重置'
},
sys: {
api: {
operationFailed: '操作失败'
}
}
}
// 不推荐
{
CommonQuery: '查询',
SYS_API_OperationFailed: '操作失败'
}9.2 翻译一致性
- 同一术语使用相同的翻译
- 保持翻译风格一致
- 注意专业术语的准确性
9.3 性能优化
- 使用按需加载减少初始包体积
- 避免在循环中频繁调用t函数
- 合理使用computed缓存翻译结果
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
// 推荐:使用computed
const title = computed(() => t('common.welcome'))
// 不推荐:在模板中直接调用
</script>
<template>
<div>
<!-- 推荐 -->
<h1>{{ title }}</h1>
<!-- 不推荐 -->
<h1>{{ t('common.welcome') }}</h1>
</div>
</template>9.4 参数化翻译
对于包含动态内容的文本,使用参数化翻译:
// 语言包
{
common: {
welcome: '欢迎,{name}!',
itemCount: '共 {count} 个项目'
}
}
// 使用
t('common.welcome', { name: '张三' })
t('common.itemCount', { count: 10 })10. 常见问题
10.1 翻译不生效
问题原因:
- 语言包文件未正确导入
- 翻译key拼写错误
- 语言未正确切换
解决方案:
// 检查语言包是否正确导入
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
// 检查翻译key是否正确
console.log(t('common.welcome'))
// 检查当前语言
import { useLocaleStore } from '@/store/modules/locale'
const localeStore = useLocaleStore()
console.log(localeStore.getCurrentLocale.lang)10.2 Element Plus组件语言未切换
问题原因:
- Element Plus语言包未正确配置
- 语言切换后未刷新页面
解决方案:
// 确保在store中配置了Element Plus语言包
const elLocaleMap = {
'zh-CN': zhCn,
en: en
}
// 切换语言后刷新页面
const setLang = (lang: LocaleType) => {
localeStore.setCurrentLocale({ lang })
changeLocale(lang)
window.location.reload() // 必须刷新页面
}10.3 翻译key不存在
问题原因:
- 翻译key在语言包中不存在
- 命名空间使用错误
解决方案:
// 检查翻译key是否存在
const { t } = useI18n()
// 使用命名空间时,不需要重复前缀
const { t } = useI18n('login')
t('username') // 正确,相当于 'login.username'
// 不使用命名空间时,需要完整路径
const { t } = useI18n()
t('login.username') // 正确10.4 动态内容翻译
问题:需要翻译包含动态变量的文本
解决方案:
// 语言包
{
user: {
welcome: '欢迎,{name}!',
message: '您有 {count} 条未读消息'
}
}
// 使用
t('user.welcome', { name: userName.value })
t('user.message', { count: unreadCount.value })11. 完整示例
11.1 用户管理页面
<template>
<ContentWrap>
<Search
:schema="searchSchema"
@search="handleSearch"
@reset="handleReset"
>
<template #actionMore>
<el-button type="primary" @click="handleAdd">
{{ t('action.create') }}
</el-button>
</template>
</Search>
</ContentWrap>
<ContentWrap>
<Table
:columns="tableColumns"
:data="tableList"
:loading="loading"
>
<template #action="{ row }">
<el-button link type="primary" @click="handleEdit(row)">
{{ t('action.edit') }}
</el-button>
<el-button link type="danger" @click="handleDelete(row)">
{{ t('action.delete') }}
</el-button>
</template>
</Table>
</ContentWrap>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const searchSchema = computed(() => [
{
field: 'username',
label: t('profile.user.username'),
component: 'Input'
},
{
field: 'status',
label: t('common.status'),
component: 'Select'
}
])
const tableColumns = computed(() => [
{
field: 'username',
label: t('profile.user.username')
},
{
field: 'nickname',
label: t('profile.user.nickname')
},
{
field: 'status',
label: t('common.status')
}
])
const handleAdd = () => {
ElMessage.success(t('common.createSuccess'))
}
const handleEdit = (row: any) => {
console.log(t('action.edit'), row)
}
const handleDelete = (row: any) => {
ElMessageBox.confirm(
t('common.delMessage'),
t('common.confirmTitle'),
{
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
type: 'warning'
}
).then(() => {
ElMessage.success(t('common.delSuccess'))
})
}
</script>11.2 表单验证
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const rules = {
username: [
{
required: true,
message: t('common.required'),
trigger: 'blur'
},
{
min: 3,
max: 20,
message: t('common.lengthError', { min: 3, max: 20 }),
trigger: 'blur'
}
],
email: [
{
required: true,
message: t('common.required'),
trigger: 'blur'
},
{
type: 'email',
message: t('common.validateEmail'),
trigger: ['blur', 'change']
}
],
password: [
{
required: true,
message: t('profile.password.oldPwdMsg'),
trigger: 'blur'
},
{
min: 6,
max: 20,
message: t('profile.password.pwdRules'),
trigger: 'blur'
}
]
}注意:本文档持续更新中,如有问题请及时反馈。
