MineAdmin 国际化配置完整指南
MineAdmin 前端基于 Vue i18n v11 构建了完整的国际化解决方案,支持多语言切换、动态语言包加载和模块化翻译管理。本文档将详细介绍系统的国际化实现机制和使用方法。
概述
支持的语言
- 简体中文 (zh_CN) - 默认语言
- 繁体中文 (zh_TW)
- 英文 (en)
核心特性
- 🌐 多语言动态切换
- 📦 模块化语言包管理
- ⚡ 动态语言包加载
- 🔧 开发工具集成
- 📱 全局和局部作用域支持
- 🎯 TypeScript 类型安全
技术栈
- Vue i18n: 11.1.2
- 构建插件: @intlify/unplugin-vue-i18n 6.0.3
- 语言格式: YAML (支持 JSON)
- 类型系统: TypeScript
项目配置
系统初始化
系统在 /web/src/bootstrap.ts
中初始化国际化配置:
// 源码位置: web/src/bootstrap.ts:44-67
// GitHub: https://github.com/mineadmin/mineadmin/blob/master/web/src/bootstrap.ts
async function createI18nService(app: App) {
// 自动扫描语言包文件
const locales: any[] = Object.entries(import.meta.glob('./locales/*.y(a)?ml')).map(([key]: any) => {
const [, value, label] = key.match(/^.\/locales\/(\w+)\[([^[\]]+)\]\.yaml$/)
return { label, value }
})
useUserStore().setLocales(locales)
// 处理消息格式
Object.keys(messages as any).map((name: string) => {
const matchValue = name.match(/(\w+)/) as RegExpMatchArray | null
if (messages && matchValue) {
messages[matchValue[1]] = messages[name]
delete messages[name]
}
})
// 创建 i18n 实例
app.use(createI18n({
legacy: false, // 使用 Composition API
globalInjection: true, // 全局注入
fallbackLocale: 'zh_CN', // 回退语言
locale: useUserStore().getLanguage(), // 当前语言
silentTranslationWarn: true, // 静默翻译警告
silentFallbackWarn: true, // 静默回退警告
messages, // 语言包数据
}))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
系统架构
架构图
graph TB
A[Vue App] --> B[Bootstrap]
B --> C[I18n Service]
C --> D[createI18n]
D --> E[Global Messages]
D --> F[Local Messages]
E --> G[Core Locales]
E --> H[Module Locales]
E --> I[Plugin Locales]
F --> J[Component i18n blocks]
K[useTrans Hook] --> L[Global Translation]
K --> M[Local Translation]
N[useLocalTrans Hook] --> M
G --> O[/src/locales/*.yaml]
H --> P[/src/modules/*/locales/*.yaml]
I --> Q[/src/plugins/*/locales/*.yaml]
style A fill:#e1f5fe
style C fill:#f3e5f5
style K fill:#fff3e0
style N fill:#fff3e0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
文件结构
web/src/
├── locales/ # 核心语言包
│ ├── zh_CN[简体中文].yaml
│ ├── en[English].yaml
│ └── zh_TW[繁體中文].yaml
├── modules/ # 模块语言包
│ └── base/
│ └── locales/
│ ├── zh_CN[简体中文].yaml
│ ├── en[English].yaml
│ └── zh_TW[繁體中文].yaml
├── plugins/ # 插件语言包
│ └── mine-admin/
│ └── code-generator/
│ └── web/
│ └── locales/
│ ├── zh_CN[简体中文].yaml
│ ├── en[English].yaml
│ └── zh_TW[繁體中文].yaml
└── hooks/ # 国际化 Hooks
├── auto-imports/
│ └── useTrans.ts # 自动导入的翻译 Hook
└── useLocalTrans.ts # 局部翻译 Hook
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
语言包管理
文件命名规范
语言包文件必须遵循特定格式:语言标识符[本语言名称].yaml
# 正确的命名格式
zh_CN[简体中文].yaml
en[English].yaml
zh_TW[繁體中文].yaml
# 错误的命名格式
zh_CN.yaml # 缺少语言名称
en[英文].yaml # 语言名称错误
zh-CN[简体中文].yaml # 语言标识符格式错误
2
3
4
5
6
7
8
9
全局语言包
系统自动扫描以下三个位置的语言包:
- 核心语言包:
src/locales/
- 模块语言包:
src/modules/<模块名>/locales/
- 插件语言包:
src/plugins/<插件名>/locales/
重要提醒
模块 和 插件 下的语言包文件名必须与 src/locales
下的文件名完全一致,否则会出现找不到翻译键的控制台警告。
语言包内容示例
核心语言包示例 (src/locales/zh_CN[简体中文].yaml
):
# 登录表单
loginForm:
loginButton: 登录
passwordPlaceholder: 请输入密码
codeLabel: 验证码
usernameLabel: 账户
# CRUD 操作
crud:
cancel: 取消
save: 保存
ok: 确定
add: 新增
edit: 编辑
delete: 删除
createSuccess: 创建成功
updateSuccess: 修改成功
delMessage: 是否删除所选中数据?
# 表单验证
form:
pleaseInput: 请输入{msg}
pleaseSelect: 请选择{msg}
requiredInput: '{msg}必填'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
局部语言包
在 Vue 组件中定义局部语言包:
<template>
<div>
<h1>{{ t('welcome') }}</h1>
<p>{{ t('description') }}</p>
</div>
</template>
<script setup lang="ts">
const { t } = useLocalTrans()
</script>
<i18n lang="yaml">
zh_CN:
welcome: 欢迎使用
description: 这是一个示例页面
zh_TW:
welcome: 歡迎使用
description: 這是一個示例頁面
en:
welcome: Welcome
description: This is a sample page
</i18n>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Hook 系统详解
useTrans Hook
源码位置: web/src/hooks/auto-imports/useTrans.ts
GitHub: https://github.com/mineadmin/mineadmin/blob/master/web/src/hooks/auto-imports/useTrans.ts
import { useI18n } from 'vue-i18n'
import type { ComposerTranslation } from 'vue-i18n'
export interface TransType {
globalTrans: ComposerTranslation
localTrans: ComposerTranslation
}
export function useTrans(key: any | null = null): TransType | string | any {
const global = useI18n()
const local = useI18n({
inheritLocale: true,
useScope: 'local',
})
if (key === null) {
return {
localTrans: local.t,
globalTrans: global.t,
}
}
else {
// 优先查找全局翻译,然后查找局部翻译
return global.te(key) ? global.t(key) : local.te(key) ? local.t(key) : key
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
useLocalTrans Hook
源码位置: web/src/hooks/useLocalTrans.ts
GitHub: https://github.com/mineadmin/mineadmin/blob/master/web/src/hooks/useLocalTrans.ts
import { useI18n } from 'vue-i18n'
import type { ComposerTranslation } from 'vue-i18n'
export function useLocalTrans(key: any | null = null): string | ComposerTranslation | any {
const { t } = useI18n({
inheritLocale: true,
useScope: 'local',
})
return key === null ? t as ComposerTranslation : t(key) as string
}
2
3
4
5
6
7
8
9
10
使用方法
基础用法
1. 全局和局部翻译对象
<script setup lang="ts">
import type { TransType } from '@/hooks/auto-imports/useTrans.ts'
// 获取翻译对象
const i18n = useTrans() as TransType
const t = i18n.globalTrans // 全局翻译函数
const localT = i18n.localTrans // 局部翻译函数
// 使用示例
const title = t('crud.createSuccess') // "创建成功"
const localTitle = localT('welcome') // 组件内定义的翻译
</script>
2
3
4
5
6
7
8
9
10
11
12
2. 直接调用翻译
// 智能翻译:优先全局,回退到局部
const message = useTrans('crud.updateSuccess') // "修改成功"
// 仅使用局部翻译
const localMessage = useLocalTrans('description')
2
3
4
5
实际项目案例
源码位置: web/src/modules/base/views/permission/user/index.vue
GitHub: https://github.com/mineadmin/mineadmin/blob/master/web/src/modules/base/views/permission/user/index.vue
<script setup lang="tsx">
import type { TransType } from '@/hooks/auto-imports/useTrans.ts'
// 获取国际化对象
const i18n = useTrans() as TransType
const t = i18n.globalTrans
// 弹窗配置中使用翻译
const maDialog: UseDialogExpose = useDialog({
ok: ({ formType }, okLoadingState: (state: boolean) => void) => {
// 根据操作类型显示不同消息
switch (formType) {
case 'add':
formRef.value.add().then((res: any) => {
res.code === ResultCode.SUCCESS
? msg.success(t('crud.createSuccess')) // "创建成功"
: msg.error(res.message)
})
break
case 'edit':
formRef.value.edit().then((res: any) => {
res.code === 200
? msg.success(t('crud.updateSuccess')) // "修改成功"
: msg.error(res.message)
})
break
}
},
})
// 表格配置中使用翻译
const options = ref<MaProTableOptions>({
header: {
mainTitle: () => t('baseUserManage.mainTitle'),
subTitle: () => t('baseUserManage.subTitle'),
},
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
带参数的翻译
<template>
<div>
<!-- 显示:请输入用户名 -->
<el-input :placeholder="t('form.pleaseInput', { msg: '用户名' })" />
<!-- 显示:用户名必填 -->
<span class="error">{{ t('form.requiredInput', { msg: '用户名' }) }}</span>
</div>
</template>
<script setup lang="ts">
const { globalTrans: t } = useTrans()
</script>
2
3
4
5
6
7
8
9
10
11
12
13
模板中直接使用
<template>
<div>
<!-- 使用全局函数 $t -->
<el-button>{{ $t('crud.save') }}</el-button>
<!-- 使用 Hook -->
<el-button>{{ t('crud.cancel') }}</el-button>
</div>
</template>
2
3
4
5
6
7
8
9
高级功能
动态语言切换
// 切换到英文
const { locale } = useI18n()
locale.value = 'en'
// 或使用用户存储
const userStore = useUserStore()
userStore.setLanguage('en')
2
3
4
5
6
7
检查翻译键是否存在
const { te, t } = useI18n()
// 检查键是否存在
if (te('some.key')) {
const translation = t('some.key')
} else {
console.warn('Translation key not found: some.key')
}
2
3
4
5
6
7
8
复数处理
# 语言包定义
en:
cart:
items: 'no items | one item | {count} items'
zh_CN:
cart:
items: '{count} 个商品'
2
3
4
5
6
7
// 使用
const itemCount = ref(5)
const message = t('cart.items', itemCount.value) // "5 个商品"
2
3
性能优化
懒加载语言包
// 按需加载语言包
const loadLanguage = async (locale: string) => {
try {
const messages = await import(`../locales/${locale}[${getLocaleName(locale)}].yaml`)
const { global } = useI18n()
global.setLocaleMessage(locale, messages.default)
global.locale.value = locale
} catch (error) {
console.error(`Failed to load language pack: ${locale}`, error)
}
}
2
3
4
5
6
7
8
9
10
11
缓存优化
// 缓存翻译结果
const translationCache = new Map<string, string>()
const cachedT = (key: string, ...args: any[]) => {
const cacheKey = `${key}-${JSON.stringify(args)}`
if (translationCache.has(cacheKey)) {
return translationCache.get(cacheKey)
}
const result = t(key, ...args)
translationCache.set(cacheKey, result)
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
调试和故障排除
常见问题
1. 找不到翻译键
问题: 控制台出现警告 Not found 'xxx' key in 'zh_CN' locale messages.
解决方案:
// 检查键是否存在
const { te, t } = useI18n()
const safeT = (key: string, fallback: string = key) => {
return te(key) ? t(key) : fallback
}
2
3
4
5
2. 模块语言包不生效
问题: 模块或插件的语言包没有被加载
解决方案:
- 确保文件名格式正确:
zh_CN[简体中文].yaml
- 检查文件路径是否在扫描范围内
- 验证 YAML 语法是否正确
3. 类型错误
问题: TypeScript 类型检查失败
解决方案:
// 使用正确的类型定义
import type { TransType } from '@/hooks/auto-imports/useTrans.ts'
const i18n = useTrans() as TransType
const t = i18n.globalTrans // 类型安全的翻译函数
2
3
4
5
开发工具
IDE 插件推荐
调试技巧
// 启用详细日志
const i18n = createI18n({
// ... 其他配置
silentTranslationWarn: false, // 显示翻译警告
silentFallbackWarn: false, // 显示回退警告
missingWarn: true, // 显示缺失警告
fallbackWarn: true, // 显示回退警告
})
2
3
4
5
6
7
8
测试策略
单元测试
// tests/i18n.spec.ts
import { mount } from '@vue/test-utils'
import { createI18n } from 'vue-i18n'
describe('i18n Component', () => {
const i18n = createI18n({
legacy: false,
locale: 'zh_CN',
messages: {
zh_CN: { hello: '你好' },
en: { hello: 'Hello' }
}
})
it('renders correct translation', () => {
const wrapper = mount(TestComponent, {
global: { plugins: [i18n] }
})
expect(wrapper.text()).toContain('你好')
})
it('switches language correctly', async () => {
i18n.global.locale.value = 'en'
await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('Hello')
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
E2E 测试
// tests/e2e/i18n.spec.ts
test('language switching works', async ({ page }) => {
await page.goto('/login')
// 检查默认语言
await expect(page.locator('.login-title')).toHaveText('登录')
// 切换语言
await page.click('.language-switcher')
await page.click('[data-lang="en"]')
// 检查语言切换效果
await expect(page.locator('.login-title')).toHaveText('Login')
})
2
3
4
5
6
7
8
9
10
11
12
13
14
扩展开发
添加新语言
- 创建语言包文件:
# 在 src/locales/ 下创建新语言文件
touch src/locales/ja[日本語].yaml
2
- 添加翻译内容:
# ja[日本語].yaml
loginForm:
loginButton: ログイン
passwordPlaceholder: パスワードを入力してください
crud:
save: 保存
cancel: キャンセル
2
3
4
5
6
7
8
- 更新语言配置:
// 如果需要,在初始化时添加新语言支持
const supportedLocales = ['zh_CN', 'en', 'zh_TW', 'ja']
2
自定义翻译逻辑
// 创建自定义翻译 Hook
export function useCustomTrans() {
const { t, te } = useI18n()
return {
t: (key: string, fallback?: string) => {
if (te(key)) {
return t(key)
}
// 自定义回退逻辑
if (fallback) {
return fallback
}
// 记录缺失的翻译键
console.warn(`Missing translation: ${key}`)
return key
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
最佳实践
1. 键名命名规范
# 推荐:使用命名空间和语义化命名
user:
profile:
title: 个人资料
edit: 编辑资料
management:
title: 用户管理
create: 创建用户
# 避免:过于扁平的结构
userProfileTitle: 个人资料
userProfileEdit: 编辑资料
2
3
4
5
6
7
8
9
10
11
12
2. 组件设计
<!-- 推荐:将翻译逻辑封装在组件内部 -->
<template>
<div class="user-card">
<h3>{{ title }}</h3>
<p>{{ description }}</p>
</div>
</template>
<script setup lang="ts">
interface Props {
userId: string
}
const props = defineProps<Props>()
const { t } = useLocalTrans()
const title = computed(() => t('title'))
const description = computed(() => t('description', { id: props.userId }))
</script>
<i18n lang="yaml">
zh_CN:
title: 用户信息
description: 用户ID:{id}
en:
title: User Info
description: User ID: {id}
</i18n>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
3. 性能考虑
// 推荐:缓存计算结果
const translatedOptions = computed(() => {
return options.map(option => ({
...option,
label: t(`options.${option.key}`)
}))
})
// 避免:在渲染函数中重复调用翻译
// {{ options.map(opt => t(`options.${opt.key}`)) }}
2
3
4
5
6
7
8
9
10