yh-i18n 0.0.1-security → 2.3.15

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/excelTool.ts ADDED
@@ -0,0 +1,270 @@
1
+ import ExcelJS from 'exceljs';
2
+ import Config from '@/config';
3
+ import {ElMessage, ElLoading} from 'element-plus';
4
+ import http from '@/libs/api.request';
5
+ import {useI18nStore} from './index';
6
+
7
+ const verificationCode = 'lkyhtranslateexcel';
8
+
9
+ /**
10
+ * 解析翻译内容,将 JSON 字符串解析并合并到 item 对象中
11
+ * @param item 翻译项对象
12
+ * @param localKeys 本地语言键列表
13
+ * @returns 处理后的翻译项对象
14
+ */
15
+ export function parseTranslateContent(item: any, localKeys: string[]): any {
16
+ try {
17
+ let content = JSON.parse(item.content);
18
+ let keys = Object.keys(content);
19
+
20
+ keys.forEach((key) => {
21
+ let val = content[key];
22
+ item[key] = val;
23
+ });
24
+
25
+ localKeys.forEach((k) => {
26
+ if (!keys.includes(k)) {
27
+ item[k] = '';
28
+ }
29
+ });
30
+ } catch (error) {
31
+ console.error('解析翻译内容失败:', error);
32
+ }
33
+ return item;
34
+ }
35
+ export async function exportExcel(total) {
36
+ const loading = ElLoading.service({
37
+ lock: true,
38
+ text: '正在获取所有翻译字段……',
39
+ background: 'rgba(255, 255, 255, 0.2)',
40
+ });
41
+ const i18nStore = useI18nStore();
42
+ let records = await http
43
+ .request({
44
+ url: '/translate/select',
45
+ method: 'post',
46
+ data: {
47
+ pageNum: 1,
48
+ pageSize: total,
49
+ },
50
+ })
51
+ .then((res) => {
52
+ let localKeys = i18nStore.localList.map((item) => item.value);
53
+ let {records} = res.data.data;
54
+ records = records.map((item) => parseTranslateContent(item, localKeys));
55
+ return records;
56
+ });
57
+ loading.setText('正在序列化表格数据……');
58
+ const workbook = new ExcelJS.Workbook();
59
+ workbook.creator = '力控元海';
60
+ workbook.lastModifiedBy = '力控元海技术中心前端';
61
+ workbook.keywords = verificationCode;
62
+ workbook.created = new Date();
63
+ workbook.modified = new Date();
64
+ const sheet = workbook.addWorksheet(`${Config.title}-系统待翻译文字`);
65
+
66
+ let columns: any[] = [
67
+ {width: 0, hidden: true},
68
+ {width: 0, hidden: true},
69
+ ];
70
+ let title: string[] = ['', ''];
71
+ let titleObj = {
72
+ zh_CN: '中文',
73
+ en_US: '英语',
74
+ th: '泰语',
75
+ vi: '越南语',
76
+ tr: '土耳其语',
77
+ fa_IR: '波斯语',
78
+ };
79
+ Config.i18nList.forEach((item) => {
80
+ columns.push({
81
+ width: 30,
82
+ alignment: {vertical: 'middle', horizontal: 'center', wrapText: true},
83
+ value: item,
84
+ });
85
+ title.push(titleObj[item]);
86
+ });
87
+
88
+ sheet.columns = columns;
89
+ sheet.addRow(title);
90
+ sheet.addRows(
91
+ records.map((item) => {
92
+ let {id, name} = item;
93
+ let row = [id, name];
94
+ Config.i18nList.forEach((valKey) => {
95
+ row.push(item[valKey]);
96
+ });
97
+ return row;
98
+ })
99
+ );
100
+ sheet.getRows(1, 1)?.forEach((row, index) => {
101
+ row.height = 25;
102
+ row.eachCell({includeEmpty: true}, (cell, cellIndex) => {
103
+ cell.font = {bold: true};
104
+ cell.alignment = {
105
+ vertical: 'middle',
106
+ horizontal: 'center',
107
+ wrapText: true,
108
+ };
109
+ cell.border = {
110
+ top: {style: 'thin'},
111
+ left: {style: 'thin'},
112
+ bottom: {style: 'thin'},
113
+ right: {style: 'thin'},
114
+ };
115
+ });
116
+ });
117
+ sheet.getRows(2, records.length)?.forEach((row, index) => {
118
+ row.height = 25;
119
+ row.eachCell({includeEmpty: true}, (cell, cellIndex) => {
120
+ cell.alignment = {
121
+ vertical: 'middle',
122
+ horizontal: 'left',
123
+ wrapText: true,
124
+ };
125
+ cell.border = {
126
+ top: {style: 'thin'},
127
+ left: {style: 'thin'},
128
+ bottom: {style: 'thin'},
129
+ right: {style: 'thin'},
130
+ };
131
+ });
132
+ });
133
+ sheet.views = [{state: 'frozen', xSplit: 0, ySplit: 1, activeCell: 'C2'}];
134
+ loading.setText('正在生成表格文件……');
135
+ workbook.xlsx.writeBuffer().then((data) => {
136
+ const blob = new Blob([data], {
137
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8',
138
+ });
139
+ let downloadA = document.createElement('a');
140
+ downloadA.download = `${Config.title}-系统待翻译文字.xlsx`;
141
+ downloadA.href = window.URL.createObjectURL(blob);
142
+ downloadA.click();
143
+ loading.close();
144
+ ElMessage.success('导出成功,请到下载目录查看');
145
+ });
146
+ }
147
+
148
+ async function getFile() {
149
+ // @ts-ignore
150
+ let fileHandle = await window.showOpenFilePicker({
151
+ id: 'importTranslateExcel',
152
+ types: [
153
+ {
154
+ description: '请选择之前导出的文件',
155
+ accept: {
156
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx', '.xls'],
157
+ },
158
+ },
159
+ ],
160
+ });
161
+ if (Array.isArray(fileHandle)) {
162
+ fileHandle = fileHandle[0];
163
+ }
164
+ let state = await fileHandle.queryPermission({mode: 'read'});
165
+ if (state === 'denied') {
166
+ return false;
167
+ } else {
168
+ if (state === 'prompt') {
169
+ fileHandle.requestPermission({
170
+ mode: 'read',
171
+ });
172
+ }
173
+ return await fileHandle.getFile();
174
+ }
175
+ }
176
+
177
+ async function saveData(dataList) {
178
+ return await http
179
+ .request({
180
+ url: '/translate/save',
181
+ type: 'json',
182
+ method: 'POST',
183
+ data: dataList,
184
+ })
185
+ .then((res) => {
186
+ return res?.data?.status === 200;
187
+ });
188
+ }
189
+
190
+ export async function importExcel(e, getDataList) {
191
+ // if (!window["showOpenFilePicker"]) {
192
+ // ElMessage({
193
+ // message: "请使用Chrome(Edge) 102 及以上版本的浏览器",
194
+ // type: "warning",
195
+ // });
196
+ // return false;
197
+ // }
198
+ // let file = await getFile();
199
+ const file = e.target.files[0];
200
+ if (!(file instanceof File)) {
201
+ ElMessage({
202
+ message: '请选择文件',
203
+ type: 'warning',
204
+ });
205
+ return false;
206
+ } else {
207
+ try {
208
+ const workbook = new ExcelJS.Workbook();
209
+ // @ts-ignore
210
+ await workbook.xlsx.load(file);
211
+ if (workbook.keywords !== verificationCode) {
212
+ ElMessage({
213
+ message: '您选择的文件不是系统导出的文件',
214
+ type: 'warning',
215
+ });
216
+ e.target.value = '';
217
+ return false;
218
+ }
219
+ const loading = ElLoading.service({
220
+ lock: true,
221
+ text: '开始读取已翻译的字段',
222
+ background: 'rgba(255, 255, 255, 0.5)',
223
+ customClass: 'yh-i18n-loading',
224
+ });
225
+ let sheet = workbook.worksheets[0];
226
+ // @ts-ignore
227
+ let len = sheet._rows.length - 1;
228
+ let dataList: any[] = [];
229
+ sheet.getRows(2, len)?.forEach((row) => {
230
+ let id = row.getCell(1).value;
231
+ let name = row.getCell(2).value;
232
+ let content: any = {};
233
+ Config.i18nList.forEach((item, index) => {
234
+ content[item] = row.getCell(index + 3).value;
235
+ });
236
+ dataList.push({
237
+ id,
238
+ name,
239
+ content: JSON.stringify(content),
240
+ });
241
+ });
242
+ loading.setText('读取完成,正在更新');
243
+
244
+ let dataLen = dataList.length;
245
+ let flag = true;
246
+ let saveLength = 200;
247
+ while (flag) {
248
+ if (dataList.length > saveLength) {
249
+ let perCent = (dataLen - dataList.length) / dataLen;
250
+ let fillIndex = parseInt(((perCent / 0.5) * 10).toFixed(0));
251
+ let loadingText = new Array(20);
252
+ loadingText.fill('◇', 0, 20 - fillIndex);
253
+ loadingText.fill('◆', 20 - fillIndex);
254
+ loading.setText(`${(perCent * 100).toFixed(2)}% ${loadingText.join('')} 正在更新…………`);
255
+ await saveData(dataList.splice(0, saveLength));
256
+ } else {
257
+ await saveData(dataList);
258
+ flag = false;
259
+ }
260
+ }
261
+ loading.close();
262
+ ElMessage.success('导入数据保存成功');
263
+ getDataList();
264
+ } catch (error) {
265
+ ElMessage.error('导入保存翻译数据遇到错误:', error.message);
266
+ } finally {
267
+ e.target.value = '';
268
+ }
269
+ }
270
+ }
package/index.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import {I18n} from 'vue-i18n';
2
+ import {Plugin, Component} from 'vue';
3
+ import {StoreDefinition} from 'pinia';
4
+
5
+ /**
6
+ * zh_CN 简体中文
7
+ * en_US 英语(美国)
8
+ * th 泰语(泰国)
9
+ * vi 越南语(越南)
10
+ * tr 土耳其语(土耳其)
11
+ */
12
+ type I18nList = 'zh_CN' | 'en_US' | 'th' | 'vi' | 'tr' | 'fa_IR';
13
+
14
+ declare global {
15
+ interface Window {
16
+ /** 国际化翻译组件是否准备好 */
17
+ translateReady: boolean;
18
+ }
19
+ }
20
+ /** 国际化组件实例 */
21
+ export const i18n: I18n;
22
+
23
+ /** 自定义的翻译函数,这个函数包装了 t 函数,在翻译的前提下,可以帮忙手机键 */
24
+ export const ct: (key: string, args?: any[]) => void;
25
+
26
+ /** 国际化相关 Pinia Store 实例 */
27
+ export const useI18nStore: StoreDefinition;
28
+
29
+ /** 力控元海 vue-i18n 插件封装 */
30
+ export const yhI18n: Plugin;
31
+
32
+ /** 多语言选择插件 */
33
+ export const Language: Component;
package/index.js ADDED
@@ -0,0 +1,352 @@
1
+ import { reactive, ref, computed } from "vue";
2
+ import { defineStore } from "pinia";
3
+ import { createI18n } from "vue-i18n";
4
+ import languageVue from "./language.vue";
5
+ import axios from "@/libs/api.request";
6
+
7
+ // base
8
+ import zhCNBase from "./lang/baseZhCn.js";
9
+ import enUSBase from "./lang/baseEnUS.js";
10
+ import thBase from "./lang/baseTh.js";
11
+ import viBase from "./lang/baseVi.js";
12
+ import trBase from "./lang/baseTr.js";
13
+ import faBase from "./lang/baseFa.js"
14
+ // vxe table
15
+ import zhCNVXETable from "./lang/zh.js";
16
+ import enUSVXETable from "./lang/en.js";
17
+ import thVXETable from "./lang/th.js";
18
+ import viVXETable from "./lang/vi.js";
19
+ import trVXETable from "./lang/tr.js";
20
+ import faVXETable from "./lang/fa.js"
21
+
22
+ // element-plus
23
+ import el_zh_CN from "element-plus/dist/locale/zh-cn.mjs";
24
+ import el_en_US from "element-plus/dist/locale/en.mjs";
25
+ import el_th from "element-plus/dist/locale/th.mjs";
26
+ import el_vi from "element-plus/dist/locale/vi.mjs";
27
+ import el_tr from "element-plus/dist/locale/tr.mjs";
28
+ import el_fa from "element-plus/dist/locale/fa.mjs";
29
+
30
+ // 自动根据浏览器系统语言设置语言
31
+ let navLang = navigator.language.replace("-", "_");
32
+ if (navLang.indexOf("en") > -1 && navLang !== "en_US") {
33
+ navLang = "en_US";
34
+ }
35
+
36
+ if (navLang.indexOf("zh") > -1 && navLang !== "zh_CN") {
37
+ navLang = "zh_CN";
38
+ }
39
+ const initlang = localStorage.translateLanguage || navLang || "zh_CN";
40
+
41
+ export let i18n = null;
42
+ window.translateReady = localStorage.translateReady ? localStorage.translateReady === "1" : import.meta.env.DEV;
43
+ let contentReady = false;
44
+
45
+ const keySet = new Set();
46
+ window.keySet = keySet;
47
+ Object.keys(zhCNBase).forEach((key) => {
48
+ keySet.add(key);
49
+ });
50
+
51
+ const unHandle = [];
52
+
53
+ function addTranslate (name) {
54
+ if (name && name.indexOf("vxe") === -1) {
55
+ try {
56
+ axios.request({
57
+ url: "/translate/insert",
58
+ method: "POST",
59
+ data: {
60
+ name,
61
+ content: `{"zh_CN":"${name}"}`,
62
+ },
63
+ });
64
+ } catch (error) {
65
+ console.error("insert error:");
66
+ }
67
+ }
68
+ }
69
+
70
+ let recursionAddTimer = null;
71
+ function recursionAddTranslate () {
72
+ if (recursionAddTimer !== null) {
73
+ return false;
74
+ }
75
+ // 当触发 content 加载完成触发递归方法后,判断是否有需要处理的键,有则处理,没有则忽略
76
+ if (unHandle.length && contentReady) {
77
+ let key = unHandle.pop();
78
+ if (!keySet.has(key) && window.translateReady) {
79
+ keySet.add(key);
80
+ addTranslate(key);
81
+ }
82
+ // 通过延时降低任务密度,避免挤兑正常逻辑的运算资源
83
+ recursionAddTimer = setTimeout(() => {
84
+ recursionAddTimer = null;
85
+ recursionAddTranslate();
86
+ }, 150);
87
+ }
88
+ }
89
+
90
+ export const ct = (key, args = []) => {
91
+ if (key && window.translateReady && window.translateCollect) {
92
+ // 将 键的处理全部放入队列中,避免挤兑正常逻辑的运算资源
93
+ if (!keySet.has(key) && !unHandle.includes(key)) {
94
+ unHandle.push(key);
95
+ recursionAddTranslate();
96
+ }
97
+ }
98
+ if (key && i18n) {
99
+ return i18n.global.t(key, args);
100
+ } else {
101
+ return key;
102
+ }
103
+ };
104
+
105
+ /** i18n 相关的 store */
106
+ export const useI18nStore = defineStore("i18nStore", () => {
107
+ const lang = ref(initlang);
108
+ /**
109
+ * @param {I18nList} l 要设置的新语言
110
+ */
111
+ function setLang (l) {
112
+ lang.value = l;
113
+ localStorage.translateLanguage = l;
114
+ if (i18n) {
115
+ i18n.global.locale = l;
116
+ }
117
+ }
118
+
119
+ const langList = reactive({
120
+ zh_CN: "中文",
121
+ en_US: "English",
122
+ th: "ไทย",
123
+ vi: "Việt nam",
124
+ tr: "Türkçe",
125
+ fa_IR: "فارسی",
126
+ });
127
+
128
+ const localList = reactive([]);
129
+ function setLocalList (list) {
130
+ list.forEach((item) => {
131
+ switch (item) {
132
+ case "zh_CN":
133
+ localList.push({
134
+ label: "中文简体",
135
+ value: "zh_CN",
136
+ });
137
+ break;
138
+ case "en_US":
139
+ localList.push({
140
+ label: "English",
141
+ value: "en_US",
142
+ });
143
+ break;
144
+ case "th":
145
+ localList.push({
146
+ label: "ไทย",
147
+ value: "th",
148
+ });
149
+ break;
150
+ case "vi":
151
+ localList.push({
152
+ label: "Việt nam",
153
+ value: "vi",
154
+ });
155
+ break;
156
+ case "tr":
157
+ localList.push({
158
+ label: "Türkçe",
159
+ value: "tr",
160
+ });
161
+ break;
162
+ case "fa_IR":
163
+ localList.push({
164
+ label: "فارسی",
165
+ value: "fa_IR",
166
+ });
167
+ break;
168
+ }
169
+ });
170
+ }
171
+
172
+ const title = computed(() => {
173
+ return langList[lang.value];
174
+ });
175
+
176
+ const eleLang = computed(() => {
177
+ switch (lang.value) {
178
+ case "zh_CN":
179
+ return el_zh_CN;
180
+ case "en_US":
181
+ return el_en_US;
182
+ case "th":
183
+ return el_th;
184
+ case "vi":
185
+ return el_vi;
186
+ case "tr":
187
+ return el_tr;
188
+ case "fa_IR":
189
+ return el_fa;
190
+ }
191
+ });
192
+
193
+ return {
194
+ lang,
195
+ setLang,
196
+ langList,
197
+ localList,
198
+ setLocalList,
199
+ title,
200
+ eleLang,
201
+ };
202
+ });
203
+
204
+ async function getRemoteMessage (list) {
205
+ try {
206
+ let messages = {
207
+ zh_CN: {},
208
+ en_US: {},
209
+ th: {},
210
+ vi: {},
211
+ tr: {},
212
+ fa_IR: {},
213
+ };
214
+ let {
215
+ data: { data },
216
+ } = await axios.request({
217
+ url: "/translate/getContent",
218
+ method: "POST",
219
+ data: {
220
+ pageNum: 1,
221
+ pageSize: 10,
222
+ },
223
+ });
224
+ data.forEach((item) => {
225
+ try {
226
+ let itemObj = JSON.parse(item);
227
+ let mKey = itemObj.zh_CN;
228
+ if (window.translateReady && !keySet.has(mKey)) {
229
+ keySet.add(mKey);
230
+ }
231
+ Object.entries(itemObj).forEach(([key, val]) => {
232
+ if (!messages[key][mKey]) {
233
+ messages[key][mKey] = val;
234
+ }
235
+ });
236
+ } catch (err) {
237
+ if (window.translateReady) {
238
+ // console.count("翻译数据格式不对");
239
+ }
240
+ }
241
+ });
242
+ list.forEach((key) => {
243
+ let m = i18n.global.getLocaleMessage(key);
244
+ i18n.global.setLocaleMessage(key, Object.assign(m, messages[key]));
245
+ });
246
+ // 翻译内容加载完成,打开开关并运行递归函数,处理累积的数据
247
+ contentReady = true;
248
+ recursionAddTranslate();
249
+ } catch (error) {}
250
+ }
251
+
252
+ function createLocalMessage (list) {
253
+ let messages = {};
254
+ list.forEach((item) => {
255
+ switch (item) {
256
+ case "zh_CN":
257
+ messages["zh_CN"] = Object.assign(zhCNBase, zhCNVXETable);
258
+ break;
259
+ case "en_US":
260
+ messages["en_US"] = Object.assign(enUSBase, enUSVXETable);
261
+ break;
262
+ case "th":
263
+ messages["th"] = Object.assign(thBase, thVXETable);
264
+ break;
265
+
266
+ case "vi":
267
+ messages["vi"] = Object.assign(viBase, viVXETable);
268
+ break;
269
+
270
+ case "tr":
271
+ messages["tr"] = Object.assign(trBase, trVXETable);
272
+ break;
273
+ case "fa_IR":
274
+ messages["fa_IR"] = Object.assign(faBase, faVXETable);
275
+ break;
276
+ default:
277
+ break;
278
+ }
279
+ });
280
+ return messages;
281
+ }
282
+
283
+ export function addI18nPage (router) {
284
+ router.addRoute("Index", {
285
+ path: "translate",
286
+ name: "翻译管理",
287
+ meta: {
288
+ icon: "md-notifications",
289
+ title: "翻译管理",
290
+ },
291
+ component: () => import("yh-i18n/list.vue"),
292
+ });
293
+ }
294
+
295
+ export function cLog (string, isError = false) {
296
+ if (isError) {
297
+ console.error("%cyhI18n:%c", "font-size: 16px;font-weight: bold;color: #00ffff", "font-size: 16px;font-weight: bold;color: #ccccc", string);
298
+ } else {
299
+ console.log("%cYh-i18n%c " + string, "font-size: 18px;font-weight: bold;color: #61AFEF", "font-size: 12px;color: #999");
300
+ }
301
+ }
302
+
303
+ export const yhI18n = {
304
+ install (app, config) {
305
+ let { list, router, pinia, VXETable } = config;
306
+ if (!list) {
307
+ cLog("请设置 Config.i18nList ,并将其设置到 yhI18n 的 congfig.list 上,否则 国际化插件将失效");
308
+ return false;
309
+ }
310
+ let messages = createLocalMessage(list);
311
+ i18n = createI18n({
312
+ locale: initlang,
313
+ messages,
314
+ });
315
+ app.use(i18n);
316
+ app.config.globalProperties.$T = ct;
317
+
318
+ if (!window.translateReady) {
319
+ cLog("没有开启翻译管理,可以在开发环境或者在控制台运行后面的代码再刷新页面: `localStorage.translateReady = '1'`");
320
+ }
321
+ if (router) {
322
+ addI18nPage(router);
323
+ } else {
324
+ if (!router) {
325
+ cLog("没有传递 router 对象,所以无法将翻译管理页面添加到应用中", true);
326
+ }
327
+ }
328
+
329
+ if (pinia) {
330
+ const i18nStore = useI18nStore(pinia);
331
+ i18nStore.setLocalList(list);
332
+ } else {
333
+ if (!pinia) {
334
+ cLog("没有传递 router 对象,所以无法为国际化插件设置语言列表", true);
335
+ }
336
+ }
337
+ if (VXETable) {
338
+ VXETable.setup({
339
+ i18n: (key, args) => ct(key, args),
340
+ translate: (key, args) => ct(key, args),
341
+ });
342
+ } else {
343
+ if (!pinia) {
344
+ cLog("没有传递 VXETable 对象,所以无法为 SLW 和 VXETable 设置国际化", true);
345
+ }
346
+ }
347
+
348
+ getRemoteMessage(list);
349
+ },
350
+ };
351
+
352
+ export const Language = languageVue;
@@ -0,0 +1,27 @@
1
+ export default {
2
+ 翻译键名: "Translate key names",
3
+ 翻译管理: "Translation management",
4
+ 键入以筛选键名: "Filter key names",
5
+ 搜索: "Search",
6
+ 新增: "New addition",
7
+ 批量删除: "Batch deletion",
8
+ 重置: "Reset",
9
+ 编辑: "Edit",
10
+ 删除: "Delete",
11
+ 操作: "Operation",
12
+ 导出: "Export",
13
+ 导入: "Import",
14
+ 翻译键值: "Translation Key Values",
15
+ 新增翻译: "Add translation",
16
+ 编辑翻译: "Edit translation",
17
+ 请输入翻译键值: "Please enter the translation key value",
18
+ 上一个: "Previous",
19
+ 下一个: "Next",
20
+ 取消: "Cancel",
21
+ 中文简体: "Simplified Chinese",
22
+ English: "English",
23
+ ไทย: "Thailand.",
24
+ "Việt nam": "Vietnamese",
25
+ Türkçe: "Turkish",
26
+ "فارسی": "Persian"
27
+ };
@@ -0,0 +1,27 @@
1
+ export default {
2
+ 翻译键名: "翻译键名",
3
+ 翻译管理: "翻译管理",
4
+ 键入以筛选键名: "键入以筛选键名",
5
+ 搜索: "搜索",
6
+ 新增: "新增",
7
+ 批量删除: "批量删除",
8
+ 重置: "重置",
9
+ 编辑: "编辑",
10
+ 删除: "删除",
11
+ 操作: "操作",
12
+ 导出: "导出",
13
+ 导入: "导入",
14
+ 翻译键值: "翻译键值",
15
+ 新增翻译: "新增翻译",
16
+ 编辑翻译: "编辑翻译",
17
+ 请输入翻译键值: "请输入翻译键值",
18
+ 上一个: "上一个",
19
+ 下一个: "下一个",
20
+ 取消: "取消",
21
+ 中文简体: "中文简体",
22
+ English: "英语",
23
+ ไทย: "泰语",
24
+ "Việt nam": "越南语",
25
+ Türkçe: "土耳其语",
26
+ "فارسی": "波斯语",
27
+ };