MineAdmin Internationalization Configuration Complete Guide
MineAdmin's frontend builds a complete internationalization solution based on Vue i18n v11, supporting multi-language switching, dynamic language pack loading, and modular translation management. This document will detail the system's internationalization implementation mechanism and usage methods.
Overview
Supported Languages
- Simplified Chinese (zh_CN) - Default language
- Traditional Chinese (zh_TW)
- English (en)
Core Features
- 🌐 Multi-language dynamic switching
- 📦 Modular language pack management
- ⚡ Dynamic language pack loading
- 🔧 Development tool integration
- 📱 Global and local scope support
- 🎯 TypeScript type safety
Tech Stack
- Vue i18n: 11.1.2
- Build Plugin: @intlify/unplugin-vue-i18n 6.0.3
- Language Format: YAML (JSON supported)
- Type System: TypeScript
Project Configuration
System Initialization
The system initializes the internationalization configuration in /web/src/bootstrap.ts:
// Source location: web/src/bootstrap.ts:44-67
// GitHub: https://github.com/mineadmin/mineadmin/blob/master/web/src/bootstrap.ts
async function createI18nService(app: App) {
// Auto-scan language pack files
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)
// Process message format
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]
}
})
// Create i18n instance
app.use(createI18n({
legacy: false, // Use Composition API
globalInjection: true, // Global injection
fallbackLocale: 'zh_CN', // Fallback language
locale: useUserStore().getLanguage(), // Current language
silentTranslationWarn: true, // Silent translation warnings
silentFallbackWarn: true, // Silent fallback warnings
messages, // Language pack data
}))
}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
System Architecture
Architecture Diagram
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:#fff3e02
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
File Structure
web/src/
├── locales/ # Core language packs
│ ├── zh_CN[简体中文].yaml
│ ├── en[English].yaml
│ └── zh_TW[繁體中文].yaml
├── modules/ # Module language packs
│ └── base/
│ └── locales/
│ ├── zh_CN[简体中文].yaml
│ ├── en[English].yaml
│ └── zh_TW[繁體中文].yaml
├── plugins/ # Plugin language packs
│ └── mine-admin/
│ └── code-generator/
│ └── web/
│ └── locales/
│ ├── zh_CN[简体中文].yaml
│ ├── en[English].yaml
│ └── zh_TW[繁體中文].yaml
└── hooks/ # Internationalization Hooks
├── auto-imports/
│ └── useTrans.ts # Auto-imported translation Hook
└── useLocalTrans.ts # Local translation Hook2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Language Pack Management
File Naming Convention
Language pack files must follow a specific format: LanguageIdentifier[LanguageNameInThatLanguage].yaml
# Correct naming format
zh_CN[简体中文].yaml
en[English].yaml
zh_TW[繁體中文].yaml
# Incorrect naming format
zh_CN.yaml # Missing language name
en[英文].yaml # Incorrect language name
zh-CN[简体中文].yaml # Incorrect language identifier format2
3
4
5
6
7
8
9
Global Language Packs
The system automatically scans language packs from the following three locations:
- Core Language Packs:
src/locales/ - Module Language Packs:
src/modules/<module_name>/locales/ - Plugin Language Packs:
src/plugins/<plugin_name>/locales/
Important Reminder
Language pack file names under modules and plugins must be exactly the same as those under src/locales. Otherwise, console warnings about missing translation keys will appear.
Language Pack Content Example
Core Language Pack Example (src/locales/zh_CN[简体中文].yaml):
# Login form
loginForm:
loginButton: 登录
passwordPlaceholder: 请输入密码
codeLabel: 验证码
usernameLabel: 账户
# CRUD operations
crud:
cancel: 取消
save: 保存
ok: 确定
add: 新增
edit: 编辑
delete: 删除
createSuccess: 创建成功
updateSuccess: 修改成功
delMessage: 是否删除所选中数据?
# Form validation
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
Local Language Packs
Define local language packs within Vue components:
<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 System Detailed Explanation
useTrans Hook
Source Location: 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 {
// Prioritize global translation lookup, then local translation
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
Source Location: 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
Usage Methods
Basic Usage
1. Global and Local Translation Objects
<script setup lang="ts">
import type { TransType } from '@/hooks/auto-imports/useTrans.ts'
// Get translation objects
const i18n = useTrans() as TransType
const t = i18n.globalTrans // Global translation function
const localT = i18n.localTrans // Local translation function
// Usage example
const title = t('crud.createSuccess') // "创建成功"
const localTitle = localT('welcome') // Component-defined translation
</script>2
3
4
5
6
7
8
9
10
11
12
2. Direct Translation Call
// Intelligent translation: prioritize global, fallback to local
const message = useTrans('crud.updateSuccess') // "修改成功"
// Use only local translation
const localMessage = useLocalTrans('description')2
3
4
5
Real Project Example
Source Location: 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'
// Get internationalization object
const i18n = useTrans() as TransType
const t = i18n.globalTrans
// Use translation in dialog configuration
const maDialog: UseDialogExpose = useDialog({
ok: ({ formType }, okLoadingState: (state: boolean) => void) => {
// Show different messages based on operation type
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
}
},
})
// Use translation in table configuration
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
Translation with Parameters
<template>
<div>
<!-- Displays: 请输入用户名 -->
<el-input :placeholder="t('form.pleaseInput', { msg: '用户名' })" />
<!-- Displays: 用户名必填 -->
<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
Direct Use in Templates
<template>
<div>
<!-- Use global function $t -->
<el-button>{{ $t('crud.save') }}</el-button>
<!-- Use Hook -->
<el-button>{{ t('crud.cancel') }}</el-button>
</div>
</template>2
3
4
5
6
7
8
9
Advanced Features
Dynamic Language Switching
// Switch to English
const { locale } = useI18n()
locale.value = 'en'
// Or use user store
const userStore = useUserStore()
userStore.setLanguage('en')2
3
4
5
6
7
Check if Translation Key Exists
const { te, t } = useI18n()
// Check if key exists
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
Pluralization Handling
# Language pack definition
en:
cart:
items: 'no items | one item | {count} items'
zh_CN:
cart:
items: '{count} 个商品'2
3
4
5
6
7
// Usage
const itemCount = ref(5)
const message = t('cart.items', itemCount.value) // "5 个商品"2
3
Performance Optimization
Lazy Loading Language Packs
// Load language packs on demand
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
Cache Optimization
// Cache translation results
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
Debugging and Troubleshooting
Common Issues
1. Translation Key Not Found
Issue: Console warning Not found 'xxx' key in 'zh_CN' locale messages.
Solution:
// Check if key exists
const { te, t } = useI18n()
const safeT = (key: string, fallback: string = key) => {
return te(key) ? t(key) : fallback
}2
3
4
5
2. Module Language Pack Not Working
Issue: Module or plugin language packs are not being loaded
Solution:
- Ensure the file name format is correct:
zh_CN[简体中文].yaml - Check if the file path is within the scan range
- Verify the YAML syntax is correct
3. Type Errors
Issue: TypeScript type checking fails
Solution:
// Use correct type definitions
import type { TransType } from '@/hooks/auto-imports/useTrans.ts'
const i18n = useTrans() as TransType
const t = i18n.globalTrans // Type-safe translation function2
3
4
5
Development Tools
Recommended IDE Plugins
Debugging Tips
// Enable detailed logs
const i18n = createI18n({
// ... other configurations
silentTranslationWarn: false, // Show translation warnings
silentFallbackWarn: false, // Show fallback warnings
missingWarn: true, // Show missing warnings
fallbackWarn: true, // Show fallback warnings
})2
3
4
5
6
7
8
Testing Strategy
Unit Testing
// 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 Testing
// tests/e2e/i18n.spec.ts
test('language switching works', async ({ page }) => {
await page.goto('/login')
// Check default language
await expect(page.locator('.login-title')).toHaveText('登录')
// Switch language
await page.click('.language-switcher')
await page.click('[data-lang="en"]')
// Check language switch effect
await expect(page.locator('.login-title')).toHaveText('Login')
})2
3
4
5
6
7
8
9
10
11
12
13
14
Extension Development
Adding a New Language
- Create the Language Pack File:
# Create a new language file under src/locales/
touch src/locales/ja[日本語].yaml2
- Add Translation Content:
# ja[日本語].yaml
loginForm:
loginButton: ログイン
passwordPlaceholder: パスワードを入力してください
crud:
save: 保存
cancel: キャンセル2
3
4
5
6
7
8
- Update Language Configuration:
// If necessary, add new language support during initialization
const supportedLocales = ['zh_CN', 'en', 'zh_TW', 'ja']2
Custom Translation Logic
// Create a custom translation Hook
export function useCustomTrans() {
const { t, te } = useI18n()
return {
t: (key: string, fallback?: string) => {
if (te(key)) {
return t(key)
}
// Custom fallback logic
if (fallback) {
return fallback
}
// Log missing translation keys
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
Best Practices
1. Key Naming Convention
# Recommended: Use namespaces and semantic names
user:
profile:
title: 个人资料
edit: 编辑资料
management:
title: 用户管理
create: 创建用户
# Avoid: Overly flat structure
userProfileTitle: 个人资料
userProfileEdit: 编辑资料2
3
4
5
6
7
8
9
10
11
12
2. Component Design
<!-- Recommended: Encapsulate translation logic inside the component -->
<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. Performance Considerations
// Recommended: Cache computed results
const translatedOptions = computed(() => {
return options.map(option => ({
...option,
label: t(`options.${option.key}`)
}))
})
// Avoid: Repeatedly calling translation in render functions
// {{ options.map(opt => t(`options.${opt.key}`)) }}2
3
4
5
6
7
8
9
10