gc_i18n 1.0.0

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/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "gc_i18n",
3
+ "version": "1.0.0",
4
+ "gc_i18n": {
5
+ "appCode": "TEST"
6
+ },
7
+ "type": "module",
8
+ "main": "./lib/gc_i18n.umd.cjs",
9
+ "module": "./lib/gc_i18n.js",
10
+ "scripts": {
11
+ "dev": "vite",
12
+ "build": "vite build",
13
+ "preview": "vite preview"
14
+ },
15
+ "dependencies": {
16
+ "keyboardjs": "^2.7.0",
17
+ "lodash-es": "^4.17.21",
18
+ "store2": "^2.14.4",
19
+ "view-ui-plus": "^1.3.19",
20
+ "vue": "^3.5.13",
21
+ "vue-i18n": "^11.1.1",
22
+ "vue-router": "^4.5.0"
23
+ },
24
+ "devDependencies": {
25
+ "@rollup/plugin-terser": "^0.4.4",
26
+ "@vitejs/plugin-vue": "^5.2.1",
27
+ "@vitejs/plugin-vue-jsx": "^4.1.1",
28
+ "less": "^4.2.2",
29
+ "vite": "^6.1.0",
30
+ "vite-plugin-libcss": "^1.1.1"
31
+ }
32
+ }
@@ -0,0 +1,39 @@
1
+ <script>
2
+ import { getLanguages } from "../libs/service";
3
+ export default {
4
+ name: "LangChange",
5
+ data() {
6
+ return {
7
+ languages: [],
8
+ language: ""
9
+ };
10
+ },
11
+ props: {
12
+ more: {
13
+ type: Boolean,
14
+ default: false
15
+ }
16
+ },
17
+ async mounted() {
18
+ this.language = navigator.language || "zh-CN";
19
+ console.log("language", this.language);
20
+ const res = await getLanguages();
21
+ if (res && res.result === 0) {
22
+ this.languages = res.retVal;
23
+ }
24
+ }
25
+ };
26
+ </script>
27
+ <template>
28
+ <div>
29
+ <div v-if="more">
30
+ <span>切换语种</span>
31
+ </div>
32
+ <Select
33
+ v-model="language"
34
+ v-else
35
+ >
36
+ <Option value="zh-CN">中文</Option>
37
+ </Select>
38
+ </div>
39
+ </template>
@@ -0,0 +1,236 @@
1
+ <script lang="jsx">
2
+ import _ from "lodash-es";
3
+ import { getAllTranslate, getLanguages, saveTranslate } from "../libs/service";
4
+ import { Message, Modal } from "view-ui-plus";
5
+
6
+ export default {
7
+ data() {
8
+ return {
9
+ isModalVisible: false,
10
+ searchText: "",
11
+ languages: [],
12
+ data: []
13
+ };
14
+ },
15
+ computed: {
16
+ firstResult() {
17
+ return this.data.firstResult / this.data.pageSize + 1;
18
+ },
19
+ columns() {
20
+ let arr = [
21
+ {
22
+ title: "关键字",
23
+ fixed: "left",
24
+ width: 200,
25
+ key: "dictKey"
26
+ }
27
+ ];
28
+ _.map(this.languages, (lang) => {
29
+ const key = lang.code;
30
+ return arr.push({
31
+ title: lang.name,
32
+ key,
33
+ width: 200,
34
+ render: (h, { row, index }) => {
35
+ const data = row[key];
36
+ return (
37
+ <Input
38
+ modelValue={data}
39
+ onOnChange={(e) => {
40
+ this.data.datas[index][key] = e.target.value;
41
+ }}
42
+ ></Input>
43
+ );
44
+ }
45
+ });
46
+ });
47
+ return arr;
48
+ }
49
+ },
50
+ props: { appCode: String, name: String, setLanguage: Function },
51
+ methods: {
52
+ closeModal() {
53
+ this.searchText = "";
54
+ this.isModalVisible = false;
55
+ },
56
+ openModal() {
57
+ if (!this.isModalVisible) {
58
+ this.isModalVisible = true; // 打开弹窗
59
+ this.init();
60
+ }
61
+ },
62
+ findShallowStringDiff(obj1, obj2) {
63
+ const { appCode, dictKey, page, unitCode } = obj1;
64
+ const diff = [];
65
+ _.forIn(obj1, (value, key) => {
66
+ if (!_.isEqual(value, obj2[key])) {
67
+ diff.push({
68
+ appCode,
69
+ key: dictKey,
70
+ page,
71
+ unitCode,
72
+ lang: key,
73
+ value
74
+ });
75
+ }
76
+ });
77
+ return diff;
78
+ },
79
+
80
+ async saveData({ msg = "" }) {
81
+ const data = _.differenceWith(
82
+ this.data.datas,
83
+ this.initialData.datas,
84
+ _.isEqual
85
+ );
86
+ let arr = [];
87
+ for (let index = 0; index < data.length; index++) {
88
+ const value = data[index];
89
+ const original = _.find(this.initialData.datas, {
90
+ dictKey: value.dictKey
91
+ });
92
+ const diff = this.findShallowStringDiff(value, original);
93
+ arr = _.concat(arr, diff);
94
+ }
95
+ const res = await saveTranslate({
96
+ appCode: this.appCode,
97
+ language: this.language,
98
+ data: arr
99
+ });
100
+ if (res && res.result === 0) {
101
+ Message.success(msg ? msg : res.msg);
102
+ // 更新到本地缓存
103
+ this.setLanguage();
104
+ this.initialData.datas = _.cloneDeep(this.data.datas);
105
+ }
106
+ },
107
+ async saveNoEqual() {
108
+ if (!_.isEqual(this.initialData, this.data)) {
109
+ this.saveData({ msg: "已为您自动保存" });
110
+ }
111
+ },
112
+ changePage(value) {
113
+ this.saveNoEqual();
114
+ this.search({ firstResult: (value - 1) * this.data.pageSize });
115
+ },
116
+ async search(data) {
117
+ const options = {
118
+ appCode: this.appCode,
119
+ searchBlur: this.searchText,
120
+ page: this.name ? _.toUpper(this.name) : "HOME",
121
+ commaSeparatedLangs: _.map(this.languages, "code").toString(),
122
+ firstResult: 0,
123
+ pageSize: 10,
124
+ ...data
125
+ };
126
+ const res = await getAllTranslate(options);
127
+ if (res) {
128
+ this.initialData = _.cloneDeep(res.retVal);
129
+ this.data = res.retVal;
130
+ }
131
+ },
132
+ async init() {
133
+ const res = await getLanguages();
134
+ if (res && res.result == 0) {
135
+ this.languages = res.retVal;
136
+ this.search();
137
+ } else {
138
+ Message.error("获取语言失败,出错了");
139
+ }
140
+ }
141
+ }
142
+ };
143
+ </script>
144
+
145
+ <template>
146
+ <Modal
147
+ v-model="isModalVisible"
148
+ :mask-closable="false"
149
+ :width="70"
150
+ title="多语言管理中心"
151
+ footer-hide
152
+ >
153
+ <Tabs
154
+ type="card"
155
+ class="gc_i18n_tabs"
156
+ >
157
+ <TabPane label="当前页面">
158
+ <Table
159
+ border
160
+ :height="550"
161
+ :columns="columns"
162
+ :data="data.datas"
163
+ ></Table
164
+ ></TabPane>
165
+ <!-- <TabPane label="当前应用">
166
+ <Table
167
+ border
168
+ :height="550"
169
+ :columns="columns"
170
+ :data="data.currentPageItems"
171
+ ></Table
172
+ ></TabPane> -->
173
+ <template #extra>
174
+ <div class="extra">
175
+ <Input
176
+ prefix="ios-search"
177
+ placeholder="请输入搜索内容"
178
+ v-model="searchText"
179
+ clearable
180
+ @on-enter="search"
181
+ @on-clear="search"
182
+ />
183
+ </div>
184
+ </template>
185
+ </Tabs>
186
+
187
+ <div class="gc_i18n_page">
188
+ <div style="display: flex">
189
+ <Page
190
+ v-model="firstResult"
191
+ :total="data.totalRows"
192
+ :page-size="10"
193
+ simple
194
+ show-total
195
+ @on-change="changePage"
196
+ />
197
+ <div>共 {{ data.totalRows }} 项数据</div>
198
+ </div>
199
+ <div>
200
+ <Button
201
+ style="margin-right: 10px"
202
+ @click="closeModal"
203
+ >关闭</Button
204
+ >
205
+ <Button
206
+ type="primary"
207
+ @click="saveData"
208
+ >保存</Button
209
+ >
210
+ </div>
211
+ </div>
212
+ </Modal>
213
+ </template>
214
+ <style lang="less">
215
+ .gc_i18n_tabs {
216
+ padding-top: 5px;
217
+ .ivu-tabs-bar {
218
+ margin-bottom: 1px !important;
219
+ }
220
+ .ivu-tabs-content {
221
+ position: relative;
222
+ top: -1px;
223
+ }
224
+ .extra {
225
+ position: relative;
226
+ top: -3px;
227
+ }
228
+ }
229
+
230
+ .gc_i18n_page {
231
+ display: flex;
232
+ justify-content: space-between;
233
+ align-items: center;
234
+ margin-top: 10px;
235
+ }
236
+ </style>
@@ -0,0 +1,100 @@
1
+ import keyboardJS from "keyboardjs";
2
+ import iview from "view-ui-plus";
3
+ import { createApp, nextTick } from "vue";
4
+ import { createI18n } from "vue-i18n";
5
+ import configVue from "./components/config.vue";
6
+ import LangChange from "./components/LangChange.vue";
7
+ //
8
+ import { getTranslate } from "./libs/service";
9
+ import { convertArrayToObject } from "./libs/utils";
10
+ import "./libs/http";
11
+ import _ from "lodash-es";
12
+ export default class I18n {
13
+ constructor(options = {}) {
14
+ const { router } = options;
15
+ this.appCode = options.appCode;
16
+ this.router = router;
17
+ this.locale = navigator.language || "zh-CN";
18
+ this.modalLoad = false;
19
+ this.messages = options.messages || {};
20
+ this.i18n = createI18n({
21
+ locale: "zh-CN",
22
+ allowComposition: true,
23
+ globalInjection: true,
24
+ legacy: false,
25
+ silentTranslationWarn: true, // 屏蔽翻译未找到的警告
26
+ silentFallbackWarn: true, // 屏蔽回退语言的警告
27
+ missingWarn: false, // 屏蔽缺失翻译的警告
28
+ fallbackWarn: false, // 屏蔽回退过程中的警告
29
+ ...options
30
+ });
31
+
32
+ // 保存原始的 t 方法
33
+ const originalT = this.i18n.global.t;
34
+ // 自定义 t 方法
35
+ this.i18n.global.t = (key) => {
36
+ const routeName = router?.currentRoute?.value?.name;
37
+ // 使用原始的 t 方法进行翻译
38
+ const originalTranslation = originalT(key);
39
+ // 如果没有找到翻译值,尝试使用路由名.key
40
+ if (originalTranslation === key && routeName) {
41
+ const prefixedKey = `${routeName}.${key}`;
42
+ // 同样使用原始的 t 方法进行翻译
43
+ return originalT(prefixedKey);
44
+ }
45
+ return originalTranslation;
46
+ };
47
+ this.i18n.global.changeLocal = (newLocale) => {
48
+ this.setLanguage(newLocale || this.locale);
49
+ };
50
+
51
+ router.afterEach((to, from) => {
52
+ const { language } = to.query;
53
+ this.setLanguage(language);
54
+
55
+ nextTick(() => {
56
+ if (!this.configInstance) {
57
+ this.createModal(to.name);
58
+ } else {
59
+ this.configInstance.closeModal();
60
+ }
61
+ });
62
+ });
63
+ }
64
+ async setLanguage(language = "zh-CN") {
65
+ this.locale = language;
66
+ const res = await getTranslate({
67
+ appCode: this.appCode,
68
+ language
69
+ });
70
+
71
+ if (res) {
72
+ const messages = convertArrayToObject(res);
73
+ this.i18n.global.setLocaleMessage(
74
+ language,
75
+ _.assign({}, _.get(this.messages, language), messages)
76
+ );
77
+ }
78
+ this.i18n.global.locale.value = language;
79
+ }
80
+ createModal(name) {
81
+ this.modalLoad = true;
82
+ // 创建 config 组件的实例
83
+ this.configInstance = createApp(configVue, {
84
+ appCode: this.appCode,
85
+ setLanguage: this.setLanguage.bind(this),
86
+ name
87
+ })
88
+ .use(iview)
89
+ .mount(document.createElement("div")); // 挂载到一个临时的 div
90
+ keyboardJS.bind("shift > t", (e) => {
91
+ // 打开弹窗
92
+ this.configInstance.openModal(); // 调用 openModal 方法
93
+ });
94
+ }
95
+ install(app, { router }) {
96
+ app.use(this.i18n);
97
+ app.use(iview);
98
+ app.component("LangChange", LangChange);
99
+ }
100
+ }
@@ -0,0 +1,69 @@
1
+ import * as Vue from "vue";
2
+ import axios from "axios";
3
+ import { Message } from "view-ui-plus";
4
+ import * as _ from "lodash-es";
5
+ // Vue.prototype.$http = axios;
6
+
7
+ Message.config({
8
+ duration: 3
9
+ });
10
+ axios.defaults.timeout = 60000;
11
+
12
+ axios.interceptors.request.use(
13
+ (config) => {
14
+ //这边可根据自己的需求设置headers,我司采用basic基本认证
15
+ const authToken = localStorage.getItem("token");
16
+ if (!_.isEmpty(authToken)) {
17
+ config.headers["Authorization"] = authToken;
18
+ }
19
+ config.headers["Content-Type"] = "application/json";
20
+ return config;
21
+ },
22
+ (err) => {
23
+ Message.error({
24
+ content: "请求超时!"
25
+ });
26
+ return Promise.resolve(err);
27
+ }
28
+ );
29
+ axios.interceptors.response.use(
30
+ (res) => {
31
+ const { nomsg } = res.config.headers;
32
+ if (res.status && res.status != 200) {
33
+ if (!nomsg) {
34
+ Message.error({
35
+ content: res.data.message || "未知错误"
36
+ });
37
+ }
38
+ } else {
39
+ res.data.success = true;
40
+ }
41
+ return res.data;
42
+ },
43
+ (err) => {
44
+ if (err.response) {
45
+ if (err.response.status == 504 || err.response.status == 404) {
46
+ Message.error({ content: "服务器被吃了⊙﹏⊙∥" });
47
+ } else if (err.response.status == 403) {
48
+ Message.error({ content: "权限不足,请联系管理员!" });
49
+ } else
50
+ Message.error({
51
+ content: `${err.response.status}:${
52
+ err.response.statusText || err.response.error
53
+ }`
54
+ });
55
+ } else if (
56
+ err.code === "ECONNABORTED" &&
57
+ err.message.indexOf("timeout") !== -1
58
+ ) {
59
+ Message.error({
60
+ content: "请求超时!"
61
+ });
62
+ } else {
63
+ Message.error({ content: err.message || "未知错误!" });
64
+ }
65
+ return Promise.resolve(err);
66
+ }
67
+ );
68
+
69
+ export default { axios };
@@ -0,0 +1,99 @@
1
+ import axios from "axios";
2
+ import store2 from "store2";
3
+ import _ from "lodash-es";
4
+ import { mergeArraysByKey } from "./utils";
5
+ export const service = "https://test.ihotel.cn";
6
+ const i18nStore = store2.namespace("i18n");
7
+ const Authorization =
8
+ "eyJhbGciOiJIUzUxMiJ9.eyJkZXZpY2VUeXBlIjoiQ09NUFVURVIiLCJtYWluQXBwQ29kZSI6IlNTTyIsIm9yZ0NvZGUiOiJHQ0JaRyIsInVjU2VydmVyVXJsIjoiaHR0cHM6Ly90ZXN0Lmlob3RlbC5jbi91Yy13ZWIvIiwiYXBwQ29kZSI6IlNTTyIsInVzZXJUeXBlIjoiTk9STUFMIiwibG9naW5BdCI6MTczODczNTczOTAwMCwicHJpbmNpcGFsVXNlckNvZGUiOiJHQ0JaR19BRE1JTiIsImV4cCI6MzI1MDM2NTEyMDAsInVzZXJDb2RlIjoiR0NCWkdfQURNSU4iLCJzc28iOiJTU09fU0VSVkVSIn0.KQt1YbUdJ7DfqrfXEVYT0Ux-7Zlo2GQBiIoq0rxK0cv1LHqOOMtBkv8kmChM6VavtdnlyXM2GkW6YMvIIHvu0Q";
9
+ export const getLanguages = async () => {
10
+ return axios.get(service + "/i18n-web/sysoption/getsupportedlangs", {
11
+ headers: {
12
+ Authorization
13
+ }
14
+ });
15
+ };
16
+ export const fetchTranslate = async ({
17
+ appCode,
18
+ language = "zh-CN",
19
+ page,
20
+ lastPullDate
21
+ }) => {
22
+ return new Promise(async (resolve, reject) => {
23
+ const serverURI = service + "/i18n-web/kv_translate/kv_translates";
24
+ const url = lastPullDate
25
+ ? serverURI + "?lastPullDate=" + lastPullDate
26
+ : serverURI;
27
+ const res = await axios({
28
+ url,
29
+ method: "GET",
30
+ headers: {
31
+ appCode,
32
+ page,
33
+ language
34
+ }
35
+ });
36
+ if (res && res.result == 0) {
37
+ resolve(res.retVal);
38
+ } else {
39
+ reject(res);
40
+ }
41
+ });
42
+ };
43
+
44
+ export const getAllTranslate = async (data) => {
45
+ console.log("data", data);
46
+
47
+ return axios({
48
+ url: service + "/i18n-web/kv_translate/kv_translates",
49
+ method: "POST",
50
+ data,
51
+ headers: { token: Authorization }
52
+ });
53
+ };
54
+ export const saveTranslate = async (data) => {
55
+ return axios({
56
+ url: service + "/i18n-web/kv_translate/batch",
57
+ method: "POST",
58
+ data,
59
+ headers: { token: Authorization }
60
+ });
61
+ };
62
+ export const getTranslate = async ({ appCode, language = "zh-CN" }) => {
63
+ return new Promise(async (resolve, reject) => {
64
+ // const appCodeStore = i18nStore.get(appCode);
65
+ const languageStore = i18nStore.namespace(appCode);
66
+ const options = {
67
+ appCode,
68
+ language
69
+ };
70
+ const lastData = languageStore.get(language);
71
+
72
+ if (!lastData || !lastData.lastPullDate) {
73
+ // 如果是第一次获取
74
+ const res = await fetchTranslate(options);
75
+ if (res) {
76
+ languageStore.set(language, res);
77
+ resolve(res.translatesDTOs);
78
+ }
79
+ } else {
80
+ const { lastPullDate } = lastData;
81
+ const res = await fetchTranslate({
82
+ ...options,
83
+ lastPullDate
84
+ });
85
+ if (res) {
86
+ // 合并增量数据
87
+ const langData = _.get(lastData, "translatesDTOs");
88
+ if (!_.isEmpty(res.translatesDTOs)) {
89
+ const data = mergeArraysByKey(langData, res.translatesDTOs);
90
+ const saveData = { lastPullDate, translatesDTOs: data };
91
+ i18nStore[appCode].set(language, saveData, ":"); // Only update the specific key
92
+ resolve(data);
93
+ } else {
94
+ resolve(langData);
95
+ }
96
+ }
97
+ }
98
+ });
99
+ };
@@ -0,0 +1,20 @@
1
+ import _ from "lodash-es";
2
+ export const convertArrayToObject = (arr) => {
3
+ const result = {};
4
+ for (const item of arr) {
5
+ result[item.key] = item.value;
6
+ }
7
+ return result;
8
+ };
9
+
10
+ export const mergeArraysByKey = (arr1, arr2) => {
11
+ arr2.forEach((obj2) => {
12
+ const foundObj = _.find(arr1, { key: obj2.key });
13
+ if (foundObj) {
14
+ _.merge(foundObj, obj2);
15
+ } else {
16
+ arr1.push(obj2);
17
+ }
18
+ });
19
+ return arr1;
20
+ };
package/src/App.vue ADDED
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <RouterView></RouterView>
3
+ </template>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
package/src/main.js ADDED
@@ -0,0 +1,21 @@
1
+ import { createApp } from "vue";
2
+ import App from "./App.vue";
3
+ import gc_i18n from "../packages/index.js";
4
+ import router from "./router/index";
5
+ import "view-ui-plus/dist/styles/viewuiplus.css";
6
+
7
+ import zh from "view-ui-plus/dist/locale/zh-CN";
8
+ import en from "view-ui-plus/dist/locale/en-US";
9
+
10
+ const i18n = new gc_i18n({
11
+ appCode: "TEST",
12
+ router,
13
+ messages: {
14
+ "zh-CN": zh,
15
+ "en-US": en
16
+ }
17
+ });
18
+
19
+ const app = createApp(App).use(router).use(i18n, { router });
20
+
21
+ app.mount("#app");
@@ -0,0 +1,17 @@
1
+ import { createRouter, createWebHashHistory } from "vue-router";
2
+
3
+ const routes = [
4
+ {
5
+ path: "/BOOK",
6
+ name: "BOOK",
7
+ component: () => import("../view/Home.vue")
8
+ }
9
+ ];
10
+ const router = createRouter({
11
+ history: createWebHashHistory(),
12
+ routes
13
+ });
14
+ router.beforeEach((to, from, next) => {
15
+ next();
16
+ });
17
+ export default router;
@@ -0,0 +1,51 @@
1
+ <script setup>
2
+ import { useI18n } from "vue-i18n";
3
+ const { locale, changeLocal } = useI18n();
4
+
5
+ defineProps({
6
+ msg: String
7
+ });
8
+ const langs = [
9
+ {
10
+ label: "中文",
11
+ code: "zh-CN"
12
+ },
13
+ {
14
+ label: "English",
15
+ code: "en-US"
16
+ },
17
+ {
18
+ label: "繁体",
19
+ code: "zh-TW"
20
+ }
21
+ ];
22
+ const change = (lang) => {
23
+ changeLocal(lang);
24
+ };
25
+ </script>
26
+
27
+ <template>
28
+ <div>
29
+ <Button
30
+ v-for="item in langs"
31
+ :type="item.code === locale ? 'primary' : 'default'"
32
+ @click="change(item.code)"
33
+ >{{ item.label }}</Button
34
+ >
35
+ <div>
36
+ <div>带路由前缀:</div>
37
+ <div>{{ $t("BOOK.ZHENGJIANLEIXING", { comment: "姓名" }) }}</div>
38
+ </div>
39
+ <div>
40
+ <div>不带前缀:</div>
41
+ <div>{{ $t("ZHENGJIANLEIXING") }}</div>
42
+ </div>
43
+ <Page
44
+ :total="40"
45
+ size="small"
46
+ show-total
47
+ />
48
+
49
+ <!-- <LangChange></LangChange> -->
50
+ </div>
51
+ </template>