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/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` 这个路径)对翻译内容进行管理