yh-i18n 1.0.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/index.d.ts +8 -0
- package/index.js +232 -0
- package/lang/th.js +314 -0
- package/lang/tr.js +314 -0
- package/lang/vi.js +314 -0
- package/language.vue +50 -0
- package/list.vue +401 -0
- package/package.json +20 -0
- package/readme.md +73 -0
package/list.vue
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="yh-i18n-list-container">
|
|
3
|
+
<div class="yh-i18n-list-actions">
|
|
4
|
+
<el-button type="primary" @click="addOne">
|
|
5
|
+
<i class="iconfont icon-add"></i>
|
|
6
|
+
{{ ct("新增") }}
|
|
7
|
+
</el-button>
|
|
8
|
+
<div class="yh-i18n-list-conditions">
|
|
9
|
+
<el-form inline>
|
|
10
|
+
<el-form-item :label="ct('翻译键名')">
|
|
11
|
+
<el-input
|
|
12
|
+
v-model="listForm.key"
|
|
13
|
+
:placeholder="ct('键入以筛选键名')"
|
|
14
|
+
></el-input>
|
|
15
|
+
</el-form-item>
|
|
16
|
+
</el-form>
|
|
17
|
+
</div>
|
|
18
|
+
<el-button type="primary" @click="getDataList">{{
|
|
19
|
+
ct("搜索")
|
|
20
|
+
}}</el-button>
|
|
21
|
+
<el-button @click="resetList">{{ ct("重置") }}</el-button>
|
|
22
|
+
</div>
|
|
23
|
+
<el-table :data="dataList" row-key="adTranslateId">
|
|
24
|
+
<el-table-column
|
|
25
|
+
type="index"
|
|
26
|
+
label="#"
|
|
27
|
+
width="60"
|
|
28
|
+
align="center"
|
|
29
|
+
fixed="left"
|
|
30
|
+
></el-table-column>
|
|
31
|
+
<el-table-column
|
|
32
|
+
:label="ct('操作')"
|
|
33
|
+
width="140"
|
|
34
|
+
align="center"
|
|
35
|
+
fixed="left"
|
|
36
|
+
>
|
|
37
|
+
<template #default="{ row, $index }">
|
|
38
|
+
<el-button link type="primary" @click="editOne(row, $index)">{{
|
|
39
|
+
ct("编辑")
|
|
40
|
+
}}</el-button>
|
|
41
|
+
<el-button link type="danger" @click="delOne(row.adTranslateId)">{{
|
|
42
|
+
ct("删除")
|
|
43
|
+
}}</el-button>
|
|
44
|
+
</template>
|
|
45
|
+
</el-table-column>
|
|
46
|
+
<el-table-column prop="key" :label="ct('翻译键名')"></el-table-column>
|
|
47
|
+
<el-table-column
|
|
48
|
+
v-for="column in listColumns"
|
|
49
|
+
:prop="column.field"
|
|
50
|
+
:label="ct(column.title)"
|
|
51
|
+
:minWidth="column.minWidth"
|
|
52
|
+
show-overflow-tooltip
|
|
53
|
+
></el-table-column>
|
|
54
|
+
</el-table>
|
|
55
|
+
<el-pagination
|
|
56
|
+
small
|
|
57
|
+
background
|
|
58
|
+
:page-size="listForm.pageSize"
|
|
59
|
+
:total="listForm.total"
|
|
60
|
+
v-model:current-page="listForm.pageNum"
|
|
61
|
+
layout="prev,pager,next,total"
|
|
62
|
+
></el-pagination>
|
|
63
|
+
</div>
|
|
64
|
+
<el-dialog
|
|
65
|
+
class="form-drawer"
|
|
66
|
+
v-model="formShow"
|
|
67
|
+
@close="cancelForm"
|
|
68
|
+
draggable
|
|
69
|
+
:title="formData.adTranslateId ? ct('编辑翻译') : ct('新增翻译')"
|
|
70
|
+
>
|
|
71
|
+
<vxe-form
|
|
72
|
+
title-align="right"
|
|
73
|
+
title-width="100px"
|
|
74
|
+
ref="vxeFormRef"
|
|
75
|
+
title-colon
|
|
76
|
+
:data="formData"
|
|
77
|
+
:items="formItems"
|
|
78
|
+
:rules="fromRules"
|
|
79
|
+
></vxe-form>
|
|
80
|
+
<template #footer>
|
|
81
|
+
<div class="yh-i18n-form-actions">
|
|
82
|
+
<el-button
|
|
83
|
+
@click="prevOne"
|
|
84
|
+
text
|
|
85
|
+
:disabled="!isNaN(formDataIndex) && formDataIndex <= 0"
|
|
86
|
+
>{{ ct("上一个") }}(←)</el-button
|
|
87
|
+
>
|
|
88
|
+
<el-button type="primary" plain @click="cancelForm">
|
|
89
|
+
{{ ct("取消") }}
|
|
90
|
+
</el-button>
|
|
91
|
+
<el-button type="primary" @click="saveOne">
|
|
92
|
+
{{ ct("保存") }}
|
|
93
|
+
</el-button>
|
|
94
|
+
<el-button
|
|
95
|
+
@click="nextOne"
|
|
96
|
+
text
|
|
97
|
+
:disabled="
|
|
98
|
+
!isNaN(formDataIndex) && formDataIndex >= dataList.leng - 1
|
|
99
|
+
"
|
|
100
|
+
>{{ ct("下一个") }}(→)</el-button
|
|
101
|
+
>
|
|
102
|
+
</div>
|
|
103
|
+
</template>
|
|
104
|
+
</el-dialog>
|
|
105
|
+
</template>
|
|
106
|
+
<script setup lang="ts">
|
|
107
|
+
import { reactive, ref, onMounted, watch } from "vue";
|
|
108
|
+
import { ElMessage, ElMessageBox } from "element-plus";
|
|
109
|
+
import { useI18nStore, ct } from "yh-i18n";
|
|
110
|
+
import http from "@/libs/api.request";
|
|
111
|
+
import { VxeFormInstance, VxeFormPropTypes } from "vxe-table";
|
|
112
|
+
import { nextTick } from "vue";
|
|
113
|
+
|
|
114
|
+
const i18nStore = useI18nStore();
|
|
115
|
+
const vxeFormRef = ref<VxeFormInstance>();
|
|
116
|
+
|
|
117
|
+
const insertUrl = "/translate/insert";
|
|
118
|
+
const updateUrl = "/translate/edit";
|
|
119
|
+
const deleteUrl = "/translate/deleteTranslate";
|
|
120
|
+
|
|
121
|
+
const listForm = reactive({
|
|
122
|
+
key: "",
|
|
123
|
+
pageNum: 1,
|
|
124
|
+
pageSize: 100,
|
|
125
|
+
total: 0,
|
|
126
|
+
});
|
|
127
|
+
const listColumns = reactive<any>([]);
|
|
128
|
+
const dataList = ref<any>([]);
|
|
129
|
+
function getDataList() {
|
|
130
|
+
let { key, pageNum, pageSize } = listForm;
|
|
131
|
+
http
|
|
132
|
+
.request({
|
|
133
|
+
url: "/translate/select",
|
|
134
|
+
method: "post",
|
|
135
|
+
data: {
|
|
136
|
+
key,
|
|
137
|
+
pageNum,
|
|
138
|
+
pageSize,
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
.then((res) => {
|
|
142
|
+
let localKeys = i18nStore.localList.map((item) => item.value);
|
|
143
|
+
let { records, total } = res.data.data;
|
|
144
|
+
records = records.map((item) => {
|
|
145
|
+
try {
|
|
146
|
+
let content = JSON.parse(item.content);
|
|
147
|
+
let keys = Object.keys(content);
|
|
148
|
+
|
|
149
|
+
keys.forEach((key) => {
|
|
150
|
+
let val = content[key];
|
|
151
|
+
item[key] = val;
|
|
152
|
+
});
|
|
153
|
+
localKeys.forEach((k) => {
|
|
154
|
+
if (!keys.includes(k)) {
|
|
155
|
+
item[k] = "";
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {}
|
|
159
|
+
return item;
|
|
160
|
+
});
|
|
161
|
+
dataList.value = records;
|
|
162
|
+
listForm.total = total;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function resetList() {
|
|
166
|
+
listForm.key = "";
|
|
167
|
+
listForm.pageNum = 1;
|
|
168
|
+
listForm.total = 0;
|
|
169
|
+
getDataList();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const langList = Object.keys(i18nStore.langList);
|
|
173
|
+
const formDataIndex = ref();
|
|
174
|
+
const formData = reactive<any>({});
|
|
175
|
+
const formShow = ref(false);
|
|
176
|
+
let inputs: HTMLInputElement[] = [];
|
|
177
|
+
function cancelForm() {
|
|
178
|
+
vxeFormRef?.value?.reset();
|
|
179
|
+
vxeFormRef?.value?.clearValidate();
|
|
180
|
+
formDataIndex.value = void 0;
|
|
181
|
+
formData.id = null;
|
|
182
|
+
formShow.value = false;
|
|
183
|
+
}
|
|
184
|
+
function addOne() {
|
|
185
|
+
formShow.value = true;
|
|
186
|
+
}
|
|
187
|
+
function prevOne() {
|
|
188
|
+
if (formDataIndex.value > 0) {
|
|
189
|
+
let index = formDataIndex.value - 1;
|
|
190
|
+
let item = dataList.value[index];
|
|
191
|
+
editOne(item, index);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function editOne(item, index) {
|
|
195
|
+
formDataIndex.value = index;
|
|
196
|
+
for (const key in item) {
|
|
197
|
+
if (Object.prototype.hasOwnProperty.call(item, key)) {
|
|
198
|
+
const val = item[key];
|
|
199
|
+
formData[key] = val;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
formShow.value = true;
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
(
|
|
205
|
+
document.querySelector("#i18nFormItem1 input") as HTMLInputElement
|
|
206
|
+
)?.focus();
|
|
207
|
+
inputs = Array.from(document.querySelectorAll(".i18n-form-item input"));
|
|
208
|
+
}, 500);
|
|
209
|
+
}
|
|
210
|
+
function nextOne() {
|
|
211
|
+
if (formDataIndex.value < dataList.value.length - 1) {
|
|
212
|
+
let index = formDataIndex.value + 1;
|
|
213
|
+
let item = dataList.value[index];
|
|
214
|
+
editOne(item, index);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function saveOne() {
|
|
218
|
+
let url = updateUrl;
|
|
219
|
+
vxeFormRef?.value?.validate().then((errMap) => {
|
|
220
|
+
if (!errMap) {
|
|
221
|
+
let data = {
|
|
222
|
+
id: formData.adTranslateId,
|
|
223
|
+
key: formData.key,
|
|
224
|
+
content: {},
|
|
225
|
+
};
|
|
226
|
+
for (const key in formData) {
|
|
227
|
+
if (key !== "key" && langList.includes(key)) {
|
|
228
|
+
const val = formData[key];
|
|
229
|
+
data.content[key] = val;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
data.content = JSON.stringify(data.content);
|
|
233
|
+
http
|
|
234
|
+
.request({
|
|
235
|
+
url,
|
|
236
|
+
method: "POST",
|
|
237
|
+
data,
|
|
238
|
+
})
|
|
239
|
+
.then((res) => {
|
|
240
|
+
if (res?.data?.status === 200) {
|
|
241
|
+
ElMessage.success(res.data.msg);
|
|
242
|
+
dataList.value[formDataIndex.value] = {
|
|
243
|
+
...formData,
|
|
244
|
+
};
|
|
245
|
+
nextOne();
|
|
246
|
+
} else {
|
|
247
|
+
ElMessage.error(res.data.msg);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function delOne(id) {
|
|
254
|
+
ElMessageBox.confirm("确认删除这条翻译记录吗?").then(() => {
|
|
255
|
+
http
|
|
256
|
+
.request({
|
|
257
|
+
url: deleteUrl,
|
|
258
|
+
method: "POST",
|
|
259
|
+
data: {
|
|
260
|
+
id,
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
.then((res) => {
|
|
264
|
+
if (res?.data?.status === 200) {
|
|
265
|
+
ElMessage.success(res.data.msg);
|
|
266
|
+
resetList();
|
|
267
|
+
} else {
|
|
268
|
+
ElMessage.error(res.data.msg);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const formItems = reactive<VxeFormPropTypes.Items>([
|
|
275
|
+
{
|
|
276
|
+
field: "key",
|
|
277
|
+
span: 24,
|
|
278
|
+
title: "翻译键值",
|
|
279
|
+
itemRender: {
|
|
280
|
+
name: "$input",
|
|
281
|
+
props: { class: "i18n-form-item", id: `i18nFormItem${1}` },
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
]);
|
|
285
|
+
const fromRules = reactive<VxeFormPropTypes.Rules>({
|
|
286
|
+
key: [{ required: true, type: "string", message: "请输入翻译键值" }],
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
let needInit = true;
|
|
290
|
+
watch(
|
|
291
|
+
() => i18nStore.localList,
|
|
292
|
+
(val) => {
|
|
293
|
+
if (val && val.length && needInit) {
|
|
294
|
+
needInit = false;
|
|
295
|
+
val.forEach((item, index) => {
|
|
296
|
+
listColumns.push({
|
|
297
|
+
field: item.value,
|
|
298
|
+
title: item.label,
|
|
299
|
+
minWidth: "200",
|
|
300
|
+
});
|
|
301
|
+
formItems.push({
|
|
302
|
+
field: item.value,
|
|
303
|
+
span: 24,
|
|
304
|
+
title: item.label,
|
|
305
|
+
itemRender: {
|
|
306
|
+
name: "$input",
|
|
307
|
+
props: { class: "i18n-form-item", id: `i18nFormItem${index + 2}` },
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
fromRules[item.value] = [
|
|
311
|
+
{ required: true, type: "string", message: `请输入${item.label}` },
|
|
312
|
+
];
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
deep: true,
|
|
318
|
+
immediate: true,
|
|
319
|
+
}
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
onMounted(() => {
|
|
323
|
+
getDataList();
|
|
324
|
+
window.addEventListener("keydown", (e) => {
|
|
325
|
+
let { key, ctrlKey, altKey } = e;
|
|
326
|
+
if (key === "s" && ctrlKey) {
|
|
327
|
+
e.preventDefault();
|
|
328
|
+
e.stopPropagation();
|
|
329
|
+
saveOne();
|
|
330
|
+
}
|
|
331
|
+
if (key === "ArrowRight" && (ctrlKey || altKey)) {
|
|
332
|
+
e.preventDefault();
|
|
333
|
+
e.stopPropagation();
|
|
334
|
+
nextOne();
|
|
335
|
+
}
|
|
336
|
+
if (key === "ArrowLeft" && (ctrlKey || altKey)) {
|
|
337
|
+
e.preventDefault();
|
|
338
|
+
e.stopPropagation();
|
|
339
|
+
prevOne();
|
|
340
|
+
}
|
|
341
|
+
if (key === "Tab") {
|
|
342
|
+
e.preventDefault();
|
|
343
|
+
e.stopPropagation();
|
|
344
|
+
if (
|
|
345
|
+
document.activeElement ||
|
|
346
|
+
// @ts-ignore
|
|
347
|
+
inputs.includes(document.activeElement)
|
|
348
|
+
) {
|
|
349
|
+
// @ts-ignore
|
|
350
|
+
let index = inputs.indexOf(document.activeElement);
|
|
351
|
+
|
|
352
|
+
if (index === inputs.length) {
|
|
353
|
+
index = 0;
|
|
354
|
+
} else {
|
|
355
|
+
index++;
|
|
356
|
+
}
|
|
357
|
+
inputs[index].focus();
|
|
358
|
+
} else {
|
|
359
|
+
setTimeout(() => {
|
|
360
|
+
if (
|
|
361
|
+
!document.activeElement ||
|
|
362
|
+
!inputs.includes(document.activeElement as HTMLInputElement)
|
|
363
|
+
) {
|
|
364
|
+
(
|
|
365
|
+
document.querySelector("#i18nFormItem1 input") as HTMLInputElement
|
|
366
|
+
)?.focus();
|
|
367
|
+
}
|
|
368
|
+
}, 100);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
</script>
|
|
374
|
+
<style lang="scss">
|
|
375
|
+
.yh-i18n-list-container {
|
|
376
|
+
height: 100%;
|
|
377
|
+
display: flex;
|
|
378
|
+
flex-direction: column;
|
|
379
|
+
.yh-i18n-list-actions {
|
|
380
|
+
padding: 16px;
|
|
381
|
+
box-sizing: border-box;
|
|
382
|
+
display: flex;
|
|
383
|
+
.yh-i18n-list-conditions {
|
|
384
|
+
padding: 0 8px;
|
|
385
|
+
flex: 1;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
.el-table {
|
|
389
|
+
flex: 1;
|
|
390
|
+
}
|
|
391
|
+
.el-pagination {
|
|
392
|
+
padding: 16px 16px 0 16px;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
.yh-i18n-form-actions {
|
|
396
|
+
display: flex;
|
|
397
|
+
.el-button {
|
|
398
|
+
flex: 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
</style>
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yh-i18n",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "对于国际化的封装",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"pub:npm": "npm publish --registry https://registry.npmjs.org/ --no-git-checks",
|
|
8
|
+
"pub:aliyun": "npm publish --registry https://packages.aliyun.com/60765e0161a945067837bb5f/npm/npm-registry/ --no-git-checks"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"vue-i18n": "9.8.0"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"vue": "3.2.47",
|
|
15
|
+
"pinia": "2.0.35",
|
|
16
|
+
"vxe-table": "4.3.10",
|
|
17
|
+
"element-plus": "2.4.4"
|
|
18
|
+
},
|
|
19
|
+
"author": "Liubin"
|
|
20
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 国际化多语言切换
|
|
2
|
+
|
|
3
|
+
**此包依赖最新的 后端基础Jar包**
|
|
4
|
+
|
|
5
|
+
**此包依赖最新,前端包**
|
|
6
|
+
- vue 3.2.47
|
|
7
|
+
- pinia 2.0.35
|
|
8
|
+
- vxe-table 4.3.10
|
|
9
|
+
- element-plus 2.4.4
|
|
10
|
+
|
|
11
|
+
## 基础配置
|
|
12
|
+
|
|
13
|
+
- **config 增加语言配置属性**,这里支持五中语言的配置:简体中文(zh_CN),英语(en_US),泰语(th),越南语(vi),土耳其语(tr),填入其中任意两种或者多种就可以了
|
|
14
|
+
`src/config/index`
|
|
15
|
+
```js
|
|
16
|
+
export default {
|
|
17
|
+
...
|
|
18
|
+
+ i18nList: ["zh_CN", "en_US", "th","vi","tr"],
|
|
19
|
+
...
|
|
20
|
+
};
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **main 文件中添加国际化初始内容**,因为要同步加载翻译数据所以要先将main文件里面的逻辑放到 一个 async 函数中
|
|
24
|
+
`src/main`
|
|
25
|
+
```js
|
|
26
|
+
...
|
|
27
|
+
+import { i18n, initI18n, useI18nStore, ct, addI18nPage } from "yh-i18n";
|
|
28
|
+
+import Config from "@/config";
|
|
29
|
+
...
|
|
30
|
+
async functino main(){
|
|
31
|
+
+ addI18nPage(router);
|
|
32
|
+
+ await initI18n(Config.i18nList);
|
|
33
|
+
const app = createApp(App);
|
|
34
|
+
+ app.use(i18n);
|
|
35
|
+
app.use(createPinia());
|
|
36
|
+
app.use(router);
|
|
37
|
+
app.use(vant);
|
|
38
|
+
app.use(plugin);
|
|
39
|
+
+ const i18nStore = useI18nStore();
|
|
40
|
+
+ i18nStore.setLocalList(Config.i18nList);
|
|
41
|
+
+ VXETable.setup({
|
|
42
|
+
+ i18n: (key, args) => ct(key, args),
|
|
43
|
+
+ translate: (key, args) => ct(key, args),
|
|
44
|
+
+ });
|
|
45
|
+
router.isReady().then(() => {
|
|
46
|
+
app.mount("#app");
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
main()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 业务使用
|
|
53
|
+
|
|
54
|
+
业务中入股有字段是编码需要翻译的,举例如下:
|
|
55
|
+
原代码
|
|
56
|
+
```html
|
|
57
|
+
<template>
|
|
58
|
+
<div>中国</div>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
组合式接口组件的代码,选项式组件请自行研究或咨询前端
|
|
62
|
+
```html
|
|
63
|
+
<template>
|
|
64
|
+
<div>{{ ct("中国") }}</div>
|
|
65
|
+
</template>
|
|
66
|
+
<script setup>
|
|
67
|
+
import { ct } from "yh-i18n-m";
|
|
68
|
+
</script>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 翻译管理
|
|
72
|
+
在开发环境中,可以自动收集要翻译的字段,
|
|
73
|
+
并且可以通过页面(`http://{host}:{port}/#/translate` 这个路径)对翻译内容进行管理
|