tang-ui-x 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1003 -0
- package/components/TActionSheet/index.uvue +15 -2
- package/components/TCollapse/index.uvue +1 -1
- package/components/TCollapse/type.uts +3 -1
- package/components/TCollapseItem/index.uvue +22 -26
- package/components/TDialog/index.uvue +19 -4
- package/components/TEmpty/index.uvue +28 -14
- package/components/TForm/index.uvue +60 -26
- package/components/TForm/type.uts +4 -0
- package/components/TInput/index.uvue +24 -5
- package/components/TInput/type.uts +10 -0
- package/components/TPicker/index.uvue +26 -6
- package/components/TRadioButton/README.md +117 -0
- package/components/TRadioButton/index.uvue +69 -64
- package/components/TSearchBar/index.uvue +19 -4
- package/composables/i18n/error.uts +82 -0
- package/composables/i18n/index.uts +188 -0
- package/composables/i18n/manager-demo.uts +104 -0
- package/composables/i18n/manager.test.uts +182 -0
- package/composables/i18n/manager.uts +336 -0
- package/composables/i18n/register-demo.uts +125 -0
- package/composables/i18n/task22-verification.uts +198 -0
- package/composables/i18n/task23-verification.uts +343 -0
- package/composables/i18n/task8-demo.uts +93 -0
- package/composables/i18n/task8-verification.uts +98 -0
- package/composables/i18n/test-task23.uts +9 -0
- package/composables/i18n/types.uts +46 -0
- package/composables/i18n/useI18n-verification.uts +105 -0
- package/composables/i18n/validation-demo.uts +45 -0
- package/composables/i18n/validation-test.uts +106 -0
- package/composables/useI18n.uts +77 -0
- package/index.uts +23 -0
- package/locales/cross-platform-verification.uts +510 -0
- package/locales/en-US/actionSheet.json +3 -0
- package/locales/en-US/common.json +10 -0
- package/locales/en-US/dialog.json +5 -0
- package/locales/en-US/empty.json +5 -0
- package/locales/en-US/errorState.json +5 -0
- package/locales/en-US/examplePages.json +1236 -0
- package/locales/en-US/examples.json +218 -0
- package/locales/en-US/form.json +11 -0
- package/locales/en-US/input.json +3 -0
- package/locales/en-US/list.json +5 -0
- package/locales/en-US/loading.json +3 -0
- package/locales/en-US/navBar.json +4 -0
- package/locales/en-US/noticeBar.json +3 -0
- package/locales/en-US/picker.json +5 -0
- package/locales/en-US/searchBar.json +4 -0
- package/locales/en-US/textarea.json +3 -0
- package/locales/en-US/toast.json +6 -0
- package/locales/index.uts +79 -0
- package/locales/init-verification.uts +101 -0
- package/locales/loader.uts +251 -0
- package/locales/run-verification.uts +16 -0
- package/locales/zh-CN/actionSheet.json +3 -0
- package/locales/zh-CN/common.json +10 -0
- package/locales/zh-CN/dialog.json +5 -0
- package/locales/zh-CN/empty.json +5 -0
- package/locales/zh-CN/errorState.json +5 -0
- package/locales/zh-CN/examplePages.json +1236 -0
- package/locales/zh-CN/examples.json +218 -0
- package/locales/zh-CN/form.json +11 -0
- package/locales/zh-CN/input.json +3 -0
- package/locales/zh-CN/list.json +5 -0
- package/locales/zh-CN/loading.json +3 -0
- package/locales/zh-CN/navBar.json +4 -0
- package/locales/zh-CN/noticeBar.json +3 -0
- package/locales/zh-CN/picker.json +5 -0
- package/locales/zh-CN/searchBar.json +4 -0
- package/locales/zh-CN/textarea.json +3 -0
- package/locales/zh-CN/toast.json +6 -0
- package/locales/zh-TW/actionSheet.json +3 -0
- package/locales/zh-TW/common.json +8 -0
- package/locales/zh-TW/dialog.json +5 -0
- package/locales/zh-TW/empty.json +5 -0
- package/locales/zh-TW/errorState.json +5 -0
- package/locales/zh-TW/examplePages.json +705 -0
- package/locales/zh-TW/examples.json +218 -0
- package/locales/zh-TW/form.json +11 -0
- package/locales/zh-TW/input.json +3 -0
- package/locales/zh-TW/list.json +5 -0
- package/locales/zh-TW/loading.json +3 -0
- package/locales/zh-TW/navBar.json +4 -0
- package/locales/zh-TW/noticeBar.json +3 -0
- package/locales/zh-TW/picker.json +5 -0
- package/locales/zh-TW/searchBar.json +4 -0
- package/locales/zh-TW/textarea.json +3 -0
- package/locales/zh-TW/toast.json +6 -0
- package/package.json +49 -47
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I18nManager 功能演示
|
|
3
|
+
* 演示语言切换功能的使用
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { I18nManager } from './manager.uts'
|
|
7
|
+
import type { ModularLocaleMessages } from './types.uts'
|
|
8
|
+
|
|
9
|
+
// 获取管理器实例
|
|
10
|
+
const manager = I18nManager.getInstance()
|
|
11
|
+
|
|
12
|
+
// 注册测试语言包
|
|
13
|
+
const zhCN: ModularLocaleMessages = {
|
|
14
|
+
common: {
|
|
15
|
+
confirm: '确定',
|
|
16
|
+
cancel: '取消',
|
|
17
|
+
hello: '你好,{name}'
|
|
18
|
+
},
|
|
19
|
+
dialog: {
|
|
20
|
+
title: '提示'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const enUS: ModularLocaleMessages = {
|
|
25
|
+
common: {
|
|
26
|
+
confirm: 'Confirm',
|
|
27
|
+
cancel: 'Cancel',
|
|
28
|
+
hello: 'Hello, {name}'
|
|
29
|
+
},
|
|
30
|
+
dialog: {
|
|
31
|
+
title: 'Notice'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const zhTW: ModularLocaleMessages = {
|
|
36
|
+
common: {
|
|
37
|
+
confirm: '確定',
|
|
38
|
+
cancel: '取消',
|
|
39
|
+
hello: '你好,{name}'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 注册语言包
|
|
44
|
+
manager.registerMessages('zh-CN', zhCN)
|
|
45
|
+
manager.registerMessages('en-US', enUS)
|
|
46
|
+
manager.registerMessages('zh-TW', zhTW)
|
|
47
|
+
|
|
48
|
+
console.log('=== 语言切换功能演示 ===\n')
|
|
49
|
+
|
|
50
|
+
// 测试 1: 检查语言包是否已注册
|
|
51
|
+
console.log('1. 检查语言包是否已注册:')
|
|
52
|
+
console.log(' hasLocale("zh-CN"):', manager.hasLocale('zh-CN'))
|
|
53
|
+
console.log(' hasLocale("en-US"):', manager.hasLocale('en-US'))
|
|
54
|
+
console.log(' hasLocale("zh-TW"):', manager.hasLocale('zh-TW'))
|
|
55
|
+
console.log(' hasLocale("ja-JP"):', manager.hasLocale('ja-JP'))
|
|
56
|
+
console.log('')
|
|
57
|
+
|
|
58
|
+
// 测试 2: 切换到有效的语言
|
|
59
|
+
console.log('2. 切换到有效的语言:')
|
|
60
|
+
console.log(' 当前语言:', manager.getCurrentLocale())
|
|
61
|
+
console.log(' 翻译 "common.confirm":', manager.translate('common.confirm'))
|
|
62
|
+
|
|
63
|
+
const success1 = manager.setLocale('en-US')
|
|
64
|
+
console.log(' setLocale("en-US") 返回:', success1)
|
|
65
|
+
console.log(' 当前语言:', manager.getCurrentLocale())
|
|
66
|
+
console.log(' 翻译 "common.confirm":', manager.translate('common.confirm'))
|
|
67
|
+
console.log('')
|
|
68
|
+
|
|
69
|
+
// 测试 3: 切换到另一个有效语言
|
|
70
|
+
console.log('3. 切换到繁体中文:')
|
|
71
|
+
const success2 = manager.setLocale('zh-TW')
|
|
72
|
+
console.log(' setLocale("zh-TW") 返回:', success2)
|
|
73
|
+
console.log(' 当前语言:', manager.getCurrentLocale())
|
|
74
|
+
console.log(' 翻译 "common.confirm":', manager.translate('common.confirm'))
|
|
75
|
+
console.log('')
|
|
76
|
+
|
|
77
|
+
// 测试 4: 尝试切换到无效的语言
|
|
78
|
+
console.log('4. 尝试切换到无效的语言:')
|
|
79
|
+
console.log(' 当前语言:', manager.getCurrentLocale())
|
|
80
|
+
const success3 = manager.setLocale('invalid-locale')
|
|
81
|
+
console.log(' setLocale("invalid-locale") 返回:', success3)
|
|
82
|
+
console.log(' 当前语言(应保持不变):', manager.getCurrentLocale())
|
|
83
|
+
console.log(' 翻译 "common.confirm"(应保持繁体中文):', manager.translate('common.confirm'))
|
|
84
|
+
console.log('')
|
|
85
|
+
|
|
86
|
+
// 测试 5: 验证响应式更新
|
|
87
|
+
console.log('5. 验证响应式状态:')
|
|
88
|
+
console.log(' currentLocale.value:', manager.currentLocale.value)
|
|
89
|
+
manager.setLocale('zh-CN')
|
|
90
|
+
console.log(' 切换后 currentLocale.value:', manager.currentLocale.value)
|
|
91
|
+
console.log('')
|
|
92
|
+
|
|
93
|
+
// 测试 6: 获取可用语言列表
|
|
94
|
+
console.log('6. 获取可用语言列表:')
|
|
95
|
+
const availableLocales = manager.getAvailableLocales()
|
|
96
|
+
console.log(' 可用语言:', availableLocales.value)
|
|
97
|
+
console.log('')
|
|
98
|
+
|
|
99
|
+
console.log('=== 演示完成 ===')
|
|
100
|
+
console.log('✅ setLocale() 方法已实现')
|
|
101
|
+
console.log('✅ hasLocale() 辅助方法已实现')
|
|
102
|
+
console.log('✅ 验证语言代码是否已注册')
|
|
103
|
+
console.log('✅ 更新 currentLocale ref 以触发响应式更新')
|
|
104
|
+
console.log('✅ 返回切换成功/失败状态')
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I18nManager 单元测试
|
|
3
|
+
* 测试核心管理器的基本功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
7
|
+
import { I18nManager } from './manager.uts'
|
|
8
|
+
import type { ModularLocaleMessages } from './types.uts'
|
|
9
|
+
|
|
10
|
+
describe('I18nManager', () => {
|
|
11
|
+
let manager: I18nManager
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// 获取单例实例
|
|
15
|
+
manager = I18nManager.getInstance()
|
|
16
|
+
|
|
17
|
+
// 注册测试语言包
|
|
18
|
+
const zhCN: ModularLocaleMessages = {
|
|
19
|
+
common: {
|
|
20
|
+
confirm: '确定',
|
|
21
|
+
cancel: '取消',
|
|
22
|
+
hello: '你好,{name}'
|
|
23
|
+
},
|
|
24
|
+
dialog: {
|
|
25
|
+
title: '提示'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const enUS: ModularLocaleMessages = {
|
|
30
|
+
common: {
|
|
31
|
+
confirm: 'Confirm',
|
|
32
|
+
cancel: 'Cancel',
|
|
33
|
+
hello: 'Hello, {name}'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
manager.registerMessages('zh-CN', zhCN)
|
|
38
|
+
manager.registerMessages('en-US', enUS)
|
|
39
|
+
manager.setLocale('zh-CN')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('translate()', () => {
|
|
43
|
+
it('should translate simple keys', () => {
|
|
44
|
+
const result = manager.translate('common.confirm')
|
|
45
|
+
expect(result).toBe('确定')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should translate with module name', () => {
|
|
49
|
+
const result = manager.translate('dialog.title')
|
|
50
|
+
expect(result).toBe('提示')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should return key if translation not found', () => {
|
|
54
|
+
const result = manager.translate('common.notexist')
|
|
55
|
+
expect(result).toBe('common.notexist')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should return key if invalid format', () => {
|
|
59
|
+
const result = manager.translate('invalidkey')
|
|
60
|
+
expect(result).toBe('invalidkey')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('interpolate()', () => {
|
|
65
|
+
it('should interpolate parameters', () => {
|
|
66
|
+
const result = manager.translate('common.hello', { name: '张三' })
|
|
67
|
+
expect(result).toBe('你好,张三')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should handle missing parameters', () => {
|
|
71
|
+
const result = manager.translate('common.hello', {})
|
|
72
|
+
expect(result).toBe('你好,{name}')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should handle multiple parameters', () => {
|
|
76
|
+
// 注册带多个参数的消息
|
|
77
|
+
manager.registerMessages('zh-CN', {
|
|
78
|
+
test: {
|
|
79
|
+
multi: '{greeting}, {name}! 你有 {count} 条消息'
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const result = manager.translate('test.multi', {
|
|
84
|
+
greeting: '你好',
|
|
85
|
+
name: '李四',
|
|
86
|
+
count: 5
|
|
87
|
+
})
|
|
88
|
+
expect(result).toBe('你好, 李四! 你有 5 条消息')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('setLocale()', () => {
|
|
93
|
+
it('should switch locale successfully', () => {
|
|
94
|
+
const success = manager.setLocale('en-US')
|
|
95
|
+
expect(success).toBe(true)
|
|
96
|
+
expect(manager.getCurrentLocale()).toBe('en-US')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should translate in new locale after switch', () => {
|
|
100
|
+
manager.setLocale('en-US')
|
|
101
|
+
const result = manager.translate('common.confirm')
|
|
102
|
+
expect(result).toBe('Confirm')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should return false for invalid locale', () => {
|
|
106
|
+
const success = manager.setLocale('invalid-locale')
|
|
107
|
+
expect(success).toBe(false)
|
|
108
|
+
expect(manager.getCurrentLocale()).toBe('zh-CN')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('fallback mechanism', () => {
|
|
113
|
+
it('should fallback to default locale when key not found', () => {
|
|
114
|
+
manager.setLocale('en-US')
|
|
115
|
+
// dialog.title 只在 zh-CN 中存在
|
|
116
|
+
const result = manager.translate('dialog.title')
|
|
117
|
+
expect(result).toBe('提示')
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('registerMessages()', () => {
|
|
122
|
+
it('should register new locale', () => {
|
|
123
|
+
const zhTW: ModularLocaleMessages = {
|
|
124
|
+
common: {
|
|
125
|
+
confirm: '確定'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
manager.registerMessages('zh-TW', zhTW)
|
|
130
|
+
manager.setLocale('zh-TW')
|
|
131
|
+
const result = manager.translate('common.confirm')
|
|
132
|
+
expect(result).toBe('確定')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should merge messages for existing locale', () => {
|
|
136
|
+
const additional: ModularLocaleMessages = {
|
|
137
|
+
common: {
|
|
138
|
+
ok: '好的'
|
|
139
|
+
},
|
|
140
|
+
newModule: {
|
|
141
|
+
test: '测试'
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
manager.registerMessages('zh-CN', additional)
|
|
146
|
+
|
|
147
|
+
// 原有的键应该保留
|
|
148
|
+
expect(manager.translate('common.confirm')).toBe('确定')
|
|
149
|
+
// 新增的键应该可用
|
|
150
|
+
expect(manager.translate('common.ok')).toBe('好的')
|
|
151
|
+
expect(manager.translate('newModule.test')).toBe('测试')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should override existing keys when merging', () => {
|
|
155
|
+
const override: ModularLocaleMessages = {
|
|
156
|
+
common: {
|
|
157
|
+
confirm: '好的'
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
manager.registerMessages('zh-CN', override)
|
|
162
|
+
const result = manager.translate('common.confirm')
|
|
163
|
+
expect(result).toBe('好的')
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('getAvailableLocales()', () => {
|
|
168
|
+
it('should return all registered locales', () => {
|
|
169
|
+
const locales = manager.getAvailableLocales()
|
|
170
|
+
expect(locales.value).toContain('zh-CN')
|
|
171
|
+
expect(locales.value).toContain('en-US')
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('singleton pattern', () => {
|
|
176
|
+
it('should return same instance', () => {
|
|
177
|
+
const instance1 = I18nManager.getInstance()
|
|
178
|
+
const instance2 = I18nManager.getInstance()
|
|
179
|
+
expect(instance1).toBe(instance2)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
})
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I18n 核心管理器
|
|
3
|
+
* 管理语言包、当前语言状态、执行翻译逻辑
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ref, computed } from 'vue'
|
|
7
|
+
import type { Ref, ComputedRef } from 'vue'
|
|
8
|
+
import type { ModularLocaleMessages, TranslateParams } from './types.uts'
|
|
9
|
+
import { I18nError } from './error.uts'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* I18nManager 单例类
|
|
13
|
+
* 提供多语言系统的核心功能
|
|
14
|
+
*/
|
|
15
|
+
export class I18nManager {
|
|
16
|
+
/** 单例实例 */
|
|
17
|
+
private static instance: I18nManager | null = null
|
|
18
|
+
|
|
19
|
+
/** 当前语言代码(响应式) */
|
|
20
|
+
public currentLocale: Ref<string>
|
|
21
|
+
|
|
22
|
+
/** 回退语言代码 */
|
|
23
|
+
public fallbackLocale: string
|
|
24
|
+
|
|
25
|
+
/** 语言包存储 */
|
|
26
|
+
private messages: Map<string, ModularLocaleMessages>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 私有构造函数,确保单例模式
|
|
30
|
+
*/
|
|
31
|
+
private constructor() {
|
|
32
|
+
this.currentLocale = ref<string>('zh-CN')
|
|
33
|
+
this.fallbackLocale = 'zh-CN'
|
|
34
|
+
this.messages = new Map<string, ModularLocaleMessages>()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 获取单例实例
|
|
39
|
+
* @returns I18nManager 实例
|
|
40
|
+
*/
|
|
41
|
+
static getInstance(): I18nManager {
|
|
42
|
+
if (I18nManager.instance === null) {
|
|
43
|
+
I18nManager.instance = new I18nManager()
|
|
44
|
+
}
|
|
45
|
+
return I18nManager.instance
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 翻译函数
|
|
50
|
+
* 支持模块化键查询(moduleName.key)和参数插值
|
|
51
|
+
* @param key 翻译键(格式:moduleName.key)
|
|
52
|
+
* @param params 插值参数(可选)
|
|
53
|
+
* @returns 翻译后的文本
|
|
54
|
+
*/
|
|
55
|
+
translate(key: string, params?: TranslateParams): string {
|
|
56
|
+
// 1. 解析键:moduleName.key
|
|
57
|
+
const parts = key.split('.')
|
|
58
|
+
|
|
59
|
+
if (parts.length < 2) {
|
|
60
|
+
I18nError.warnInvalidKey(key)
|
|
61
|
+
return key
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const moduleName = parts[0]
|
|
65
|
+
const messageKey = parts.slice(1).join('.')
|
|
66
|
+
|
|
67
|
+
if (!moduleName || !messageKey) {
|
|
68
|
+
I18nError.warnInvalidKey(key)
|
|
69
|
+
return key
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. 在当前语言中查找
|
|
73
|
+
let message = this.getModuleMessage(this.currentLocale.value, moduleName, messageKey)
|
|
74
|
+
|
|
75
|
+
// 3. 如果未找到,尝试回退语言
|
|
76
|
+
if (message === undefined) {
|
|
77
|
+
message = this.getModuleMessage(this.fallbackLocale, moduleName, messageKey)
|
|
78
|
+
|
|
79
|
+
// 4. 如果仍未找到,返回键本身
|
|
80
|
+
if (message === undefined) {
|
|
81
|
+
I18nError.warnKeyNotFound(key, this.currentLocale.value)
|
|
82
|
+
return key
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 5. 如果有参数,执行插值
|
|
87
|
+
if (params && typeof message === 'string') {
|
|
88
|
+
return this.interpolate(message, params)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return message as string
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 获取模块中的消息(支持深层嵌套)
|
|
96
|
+
* @param locale 语言代码
|
|
97
|
+
* @param moduleName 模块名
|
|
98
|
+
* @param key 消息键(支持点号分隔的嵌套路径)
|
|
99
|
+
* @returns 消息文本或 undefined
|
|
100
|
+
*/
|
|
101
|
+
private getModuleMessage(locale: string, moduleName: string, key: string): string | undefined {
|
|
102
|
+
const localeMessages = this.messages.get(locale)
|
|
103
|
+
if (!localeMessages) {
|
|
104
|
+
return undefined
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const module = localeMessages[moduleName]
|
|
108
|
+
if (!module) {
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 支持嵌套路径查询(例如:components.TButton.name)
|
|
113
|
+
const keys = key.split('.')
|
|
114
|
+
let current: any = module
|
|
115
|
+
|
|
116
|
+
for (const k of keys) {
|
|
117
|
+
if (current && typeof current === 'object' && k in current) {
|
|
118
|
+
current = current[k]
|
|
119
|
+
} else {
|
|
120
|
+
return undefined
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 确保最终值是字符串
|
|
125
|
+
return typeof current === 'string' ? current : undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 字符串插值
|
|
130
|
+
* 支持 {key} 占位符格式
|
|
131
|
+
* @param template 模板字符串(使用 {key} 作为占位符)
|
|
132
|
+
* @param params 参数对象
|
|
133
|
+
* @returns 插值后的字符串
|
|
134
|
+
*/
|
|
135
|
+
private interpolate(template: string, params: TranslateParams): string {
|
|
136
|
+
return template.replace(/\{(\w+)\}/g, (match: string, key: string): string => {
|
|
137
|
+
if (key in params) {
|
|
138
|
+
return String(params[key])
|
|
139
|
+
}
|
|
140
|
+
I18nError.warnMissingParam(template, key)
|
|
141
|
+
return match // 保留未匹配的占位符
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 设置当前语言
|
|
147
|
+
* @param locale 语言代码
|
|
148
|
+
* @returns 是否切换成功
|
|
149
|
+
*/
|
|
150
|
+
setLocale(locale: string): boolean {
|
|
151
|
+
if (!this.hasLocale(locale)) {
|
|
152
|
+
I18nError.warnLocaleNotFound(locale)
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 更新 ref 会自动触发所有依赖的重新计算
|
|
157
|
+
this.currentLocale.value = locale
|
|
158
|
+
return true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 检查语言包是否已注册
|
|
163
|
+
* @param locale 语言代码
|
|
164
|
+
* @returns 是否存在
|
|
165
|
+
*/
|
|
166
|
+
hasLocale(locale: string): boolean {
|
|
167
|
+
return this.messages.has(locale)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 验证语言包结构(支持嵌套对象)
|
|
172
|
+
* @param locale 语言代码
|
|
173
|
+
* @param messages 模块化语言包
|
|
174
|
+
* @returns 是否验证通过
|
|
175
|
+
*/
|
|
176
|
+
private validateMessages(locale: string, messages: ModularLocaleMessages): boolean {
|
|
177
|
+
// 1. 验证语言包是否为有效的对象
|
|
178
|
+
if (!messages || typeof messages !== 'object' || Array.isArray(messages)) {
|
|
179
|
+
I18nError.errorMessagesNotObject(locale)
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. 验证每个模块是否为对象
|
|
184
|
+
for (const moduleName in messages) {
|
|
185
|
+
const module = messages[moduleName]
|
|
186
|
+
|
|
187
|
+
if (!module || typeof module !== 'object' || Array.isArray(module)) {
|
|
188
|
+
I18nError.errorModuleNotObject(locale, moduleName)
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 3. 递归验证模块内容(支持嵌套对象)
|
|
193
|
+
if (!this.validateModuleContent(locale, moduleName, module)) {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 递归验证模块内容(支持嵌套对象)
|
|
203
|
+
* @param locale 语言代码
|
|
204
|
+
* @param moduleName 模块名
|
|
205
|
+
* @param obj 要验证的对象
|
|
206
|
+
* @returns 是否验证通过
|
|
207
|
+
*/
|
|
208
|
+
private validateModuleContent(locale: string, moduleName: string, obj: any): boolean {
|
|
209
|
+
for (const key in obj) {
|
|
210
|
+
const value = obj[key]
|
|
211
|
+
|
|
212
|
+
if (typeof value === 'string') {
|
|
213
|
+
// 字符串值,验证通过
|
|
214
|
+
continue
|
|
215
|
+
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
216
|
+
// 嵌套对象,递归验证
|
|
217
|
+
if (!this.validateModuleContent(locale, moduleName, value)) {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
// 其他类型(数组、null等),验证失败
|
|
222
|
+
I18nError.errorValueNotString(locale, moduleName, key)
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 注册或更新语言包(默认使用合并模式)
|
|
232
|
+
* @param locale 语言代码
|
|
233
|
+
* @param messages 模块化语言包
|
|
234
|
+
*/
|
|
235
|
+
registerMessages(locale: string, messages: ModularLocaleMessages): void {
|
|
236
|
+
this.registerMessagesWithMode(locale, messages, 'merge')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 注册语言包(指定模式)
|
|
241
|
+
* @param locale 语言代码
|
|
242
|
+
* @param messages 模块化语言包
|
|
243
|
+
* @param mode 注册模式:'merge' 合并,'replace' 替换
|
|
244
|
+
*/
|
|
245
|
+
registerMessagesWithMode(
|
|
246
|
+
locale: string,
|
|
247
|
+
messages: ModularLocaleMessages,
|
|
248
|
+
mode: 'merge' | 'replace'
|
|
249
|
+
): void {
|
|
250
|
+
// 验证语言包结构
|
|
251
|
+
if (!this.validateMessages(locale, messages)) {
|
|
252
|
+
// 验证失败,拒绝注册
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (mode === 'replace') {
|
|
257
|
+
// 替换模式:完全替换现有语言包(直接设置,不再次验证)
|
|
258
|
+
this.messages.set(locale, messages)
|
|
259
|
+
} else {
|
|
260
|
+
// 合并模式:合并现有语言包
|
|
261
|
+
const existingMessages = this.messages.get(locale)
|
|
262
|
+
|
|
263
|
+
if (existingMessages) {
|
|
264
|
+
// 合并现有语言包
|
|
265
|
+
const merged = this.mergeModularMessages(existingMessages, messages)
|
|
266
|
+
this.messages.set(locale, merged)
|
|
267
|
+
} else {
|
|
268
|
+
// 新增语言包
|
|
269
|
+
this.messages.set(locale, messages)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 替换语言包(完全替换)
|
|
276
|
+
* @param locale 语言代码
|
|
277
|
+
* @param messages 模块化语言包
|
|
278
|
+
*/
|
|
279
|
+
replaceMessages(locale: string, messages: ModularLocaleMessages): void {
|
|
280
|
+
// 验证语言包结构
|
|
281
|
+
if (!this.validateMessages(locale, messages)) {
|
|
282
|
+
// 验证失败,拒绝替换
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.messages.set(locale, messages)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 合并模块化语言包
|
|
291
|
+
* @param target 目标语言包
|
|
292
|
+
* @param source 源语言包
|
|
293
|
+
* @returns 合并后的语言包
|
|
294
|
+
*/
|
|
295
|
+
private mergeModularMessages(
|
|
296
|
+
target: ModularLocaleMessages,
|
|
297
|
+
source: ModularLocaleMessages
|
|
298
|
+
): ModularLocaleMessages {
|
|
299
|
+
const result: ModularLocaleMessages = { ...target }
|
|
300
|
+
|
|
301
|
+
// 遍历源语言包的所有模块
|
|
302
|
+
for (const moduleName in source) {
|
|
303
|
+
const sourceModule = source[moduleName]
|
|
304
|
+
const targetModule = result[moduleName]
|
|
305
|
+
|
|
306
|
+
if (targetModule) {
|
|
307
|
+
// 如果目标中已有该模块,合并键值对
|
|
308
|
+
result[moduleName] = {
|
|
309
|
+
...targetModule,
|
|
310
|
+
...sourceModule // 源模块的键覆盖目标模块
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
// 如果目标中没有该模块,直接添加
|
|
314
|
+
result[moduleName] = sourceModule
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return result
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 获取可用语言列表(响应式)
|
|
323
|
+
* @returns 可用语言代码数组
|
|
324
|
+
*/
|
|
325
|
+
getAvailableLocales(): ComputedRef<string[]> {
|
|
326
|
+
return computed(() => Array.from(this.messages.keys()))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 获取当前语言代码
|
|
331
|
+
* @returns 当前语言代码
|
|
332
|
+
*/
|
|
333
|
+
getCurrentLocale(): string {
|
|
334
|
+
return this.currentLocale.value
|
|
335
|
+
}
|
|
336
|
+
}
|