gc_i18n 1.0.9 → 1.1.1

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/packages/index.js CHANGED
@@ -4,100 +4,216 @@ import "view-ui-plus/dist/styles/viewuiplus.css";
4
4
  import { createApp, nextTick } from "vue";
5
5
  import { createI18n } from "vue-i18n";
6
6
  import configVue from "./components/config.vue";
7
- import LanguageTPL from "./components/language.vue";
8
- import { getTranslate } from "./libs/service";
7
+ import { getLanguages, getTranslate } from "./libs/service";
9
8
  import { convertArrayToObject } from "./libs/utils";
9
+ import { extractTranslationKeys } from "./libs/i18nUtils";
10
10
  import "./libs/http";
11
11
  import _ from "lodash-es";
12
12
  import store2 from "store2";
13
13
 
14
+ import { jws } from "jsrsasign";
15
+
16
+ // 生成 JWT 的函数
17
+ function generateJWT(orgCode) {
18
+ const header = {
19
+ alg: "HS512" // 算法
20
+ };
21
+ const payload = {
22
+ orgCode
23
+ };
24
+ const sHeader = JSON.stringify(header);
25
+ const sPayload = JSON.stringify(payload);
26
+ const secret = "";
27
+ // 同步生成 JWT
28
+ return jws.JWS.sign("HS512", sHeader, sPayload, secret);
29
+ }
14
30
  export default class I18n {
15
31
  constructor(options = {}) {
16
- const { router, appCode, messages, token } = options;
17
- this.token = token || store2.get("token");
32
+ const { router, appCode, messages, token, orgCode } = options;
33
+ this.token = token || generateJWT(orgCode);
18
34
  this.appCode = appCode;
19
35
  this.router = router;
20
36
  this.locale = store2.get("I18N_LANGUAGE") || navigator.language || "zh-CN";
21
37
  this.modalLoad = false;
22
38
  this.name = "";
23
39
  this.messages = messages || {};
40
+ this.translationKeys = [];
41
+
42
+ // 初始化路由守卫
43
+ this.initRouterGuards();
44
+
45
+ // 初始化 i18n 实例
46
+ this.initI18n(options);
47
+
48
+ // 路由首次加载完成后设置语言
49
+ this.router
50
+ .isReady()
51
+ .then(async () => {
52
+ await this.setLanguage(this.locale);
53
+ this.createModal();
54
+ })
55
+ .catch((error) => {
56
+ console.error("路由首次加载出错:", error);
57
+ });
58
+
59
+ keyboardJS.bind("shift > t", (e) => {
60
+ // 打开弹窗
61
+ this.configInstance.openModal({ name: this.name });
62
+ });
63
+
64
+ // 监听浏览器语言变化
65
+ this.setupLanguageChangeListener();
66
+ }
67
+
68
+ // 监听浏览器语言变化
69
+ setupLanguageChangeListener() {
70
+ if ("onlanguagechange" in window) {
71
+ window.addEventListener("languagechange", () => {
72
+ const newBrowserLanguage = navigator.language;
73
+ if (newBrowserLanguage !== this.locale) {
74
+ this.setLanguage(newBrowserLanguage);
75
+ }
76
+ });
77
+ }
78
+ }
79
+
80
+ // 初始化路由守卫
81
+ initRouterGuards() {
82
+ this.router.beforeEach(async (to, from, next) => {
83
+ const { language, token } = to.query;
84
+ this.configInstance && this.configInstance.closeModal();
85
+ this.name = to.name;
86
+ token && this.setToken(token);
87
+ // await this.setLanguage(language || this.locale);
88
+ if (to.matched.length > 0) {
89
+ let component = to.matched[0].components.default;
90
+ if (
91
+ typeof component === "function" &&
92
+ component.toString().includes("import(")
93
+ ) {
94
+ try {
95
+ component = await component();
96
+ component = component.default;
97
+ } catch (error) {
98
+ console.error("加载异步组件时出错:", error);
99
+ next();
100
+ return;
101
+ }
102
+ }
103
+ this.translationKeys = extractTranslationKeys(component);
104
+ }
105
+ next();
106
+ });
107
+ }
108
+
109
+ // 初始化 i18n 实例
110
+ initI18n(options) {
24
111
  this.i18n = createI18n({
25
- locale: "zh-CN",
112
+ locale: this.locale,
26
113
  allowComposition: true,
27
114
  globalInjection: true,
28
115
  legacy: false,
29
- silentTranslationWarn: true, // 屏蔽翻译未找到的警告
30
- silentFallbackWarn: true, // 屏蔽回退语言的警告
31
- missingWarn: false, // 屏蔽缺失翻译的警告
32
- fallbackWarn: false, // 屏蔽回退过程中的警告
116
+ silentTranslationWarn: true,
117
+ silentFallbackWarn: true,
118
+ missingWarn: false,
119
+ fallbackWarn: false,
33
120
  ...options
34
121
  });
35
122
 
36
- // 保存原始的 t 方法
37
123
  const originalT = this.i18n.global.t;
38
- // 自定义 t 方法
39
- this.i18n.global.t = (key, args, comment) => {
40
- const routeName = _.toUpper(router?.currentRoute?.value?.name);
41
- // 使用原始的 t 方法进行翻译
124
+
125
+ globalThis.$t = this.i18n.global.t = (key, args, comment) => {
126
+ const routeName = _.toUpper(this.router?.currentRoute?.value?.name);
42
127
  const originalTranslation = originalT(key, args);
43
- // 如果没有找到翻译值,尝试使用路由名.key
44
- if (originalTranslation === key && routeName) {
128
+ if (
129
+ (originalTranslation === key || originalTranslation === args) &&
130
+ routeName
131
+ ) {
45
132
  const prefixedKey = `${routeName}.${key}`;
46
- // 同样使用原始的 t 方法进行翻译
47
133
  const routeTranslation = originalT(prefixedKey, args);
48
-
49
- if (routeTranslation !== prefixedKey) {
134
+ if (routeTranslation !== prefixedKey && routeTranslation !== args) {
50
135
  return routeTranslation;
51
136
  } else {
52
- if (comment) {
53
- console.log(`找不到"${key}",使用"${comment}"`);
54
- return comment;
137
+ const commonKey = `common.${key}`;
138
+ const commonTranslation = originalT(commonKey, args);
139
+ if (commonTranslation !== commonKey) {
140
+ return commonTranslation;
55
141
  } else {
56
- return key;
142
+ if (comment) {
143
+ console.log(`找不到"${key}",使用"${comment}"`);
144
+ return comment;
145
+ } else {
146
+ return key;
147
+ }
57
148
  }
58
149
  }
59
150
  }
60
151
  return originalTranslation;
61
152
  };
62
153
 
63
- this.i18n.global.changeLocale = (newLocale) => {
64
- this.setLanguage(newLocale || this.locale);
154
+ globalThis.$deepScan = function (val) {
155
+ return val;
65
156
  };
66
- this.createModal();
67
- router.beforeEach(async (to, from, next) => {
68
- const { language, token } = to.query;
69
- this.configInstance && this.configInstance.closeModal();
70
- this.name = to.name;
71
- token && this.setToken(token);
72
- await this.setLanguage(language || this.locale);
73
- next();
74
- });
75
- keyboardJS.bind("shift > t", (e) => {
76
- // 打开弹窗
77
- this.configInstance.openModal({ name: this.name }); // 调用 openModal 方法
78
- });
157
+
158
+ globalThis.$t = (key, val, nameSpace) => {
159
+ const languageStore = store2.namespace(`I18N_${_.toUpper(this.appCode)}`);
160
+ const message = _.get(languageStore.get(this.locale), "translatesDTOs");
161
+ // 获取i18n语言数据
162
+ const value = _.find(message, (val) => {
163
+ return `common.${key}` === val.key;
164
+ });
165
+ return _.get(value, "value", val);
166
+ };
167
+ globalThis.$changeLocale =
168
+ this.changeLocale =
169
+ this.i18n.global.changeLocale =
170
+ async (newLocale) => {
171
+ await this.setLanguage(newLocale || this.locale);
172
+ };
79
173
  }
80
174
  setToken(token) {
81
175
  this.token = token;
82
176
  }
83
- async setLanguage(language = "zh-CN") {
177
+ getLanguage(language) {
178
+ return store2.get("I18N_TEST.zh-TW");
179
+ }
180
+ async getLanguages(isRemote = false) {
181
+ const langs = store2.get("I18N_LANGUAGES");
182
+ if (langs && !isRemote) {
183
+ return langs;
184
+ } else {
185
+ const res = await getLanguages({
186
+ appCode: this.appCode,
187
+ token: this.token
188
+ });
189
+ if (res.data) {
190
+ const languages = _.get(res.data, "retVal");
191
+ store2.set("I18N_LANGUAGES", languages);
192
+ return languages;
193
+ }
194
+ }
195
+ }
196
+ async setLanguage(language) {
84
197
  return new Promise(async (resolve, reject) => {
85
198
  const res = await getTranslate({
86
199
  appCode: this.appCode,
87
- language,
200
+ language: language ? language : this.locale,
88
201
  token: this.token
89
202
  });
90
203
  if (res) {
91
- const messages = convertArrayToObject(res);
92
- console.log("messages", messages, language);
93
- this.i18n.global.setLocaleMessage(
94
- language,
95
- _.assign({}, _.get(this.messages, language), messages)
204
+ const remoteMessages = convertArrayToObject(res);
205
+ const messages = _.assign(
206
+ {},
207
+ _.get(this.messages, language),
208
+ remoteMessages
96
209
  );
210
+ this.i18n.global.setLocaleMessage(language, messages);
97
211
  }
212
+ // if (language !== this.locale) {
98
213
  this.locale = language;
99
214
  this.i18n.global.locale.value = language;
100
215
  store2.set("I18N_LANGUAGE", language);
216
+ // }
101
217
  resolve(true);
102
218
  });
103
219
  }
@@ -108,31 +224,16 @@ export default class I18n {
108
224
  appCode: this.appCode,
109
225
  token: this.token,
110
226
  setLanguage: this.setLanguage.bind(this, this.locale),
111
- name: this.name
227
+ name: this.name,
228
+ translationKeys: this.translationKeys
112
229
  })
113
230
  .use(iview)
114
231
  .mount(document.createElement("div")); // 挂载到一个临时的 div
115
232
  }
116
- Language() {
117
- return {
118
- setup: () => {
119
- return {
120
- token: this.token
121
- };
122
- },
123
- components: {
124
- LanguageTPL
125
- },
126
- template: `<LanguageTPL :token='token'></LanguageTPL>`
127
- };
128
- }
129
233
  install(app, opts = {}) {
130
234
  app.use(iview, {
131
235
  i18n: this.i18n
132
236
  });
133
- app.config.globalProperties.$t = this.i18n.global.t;
134
- app.config.globalProperties.$i18n = this.i18n;
135
- app.component("Language", this.Language());
136
237
  app.use(this.i18n);
137
238
  }
138
239
  }
@@ -33,7 +33,7 @@ axios.interceptors.response.use(
33
33
  } else {
34
34
  res.data.success = true;
35
35
  }
36
- return res.data;
36
+ return res;
37
37
  },
38
38
  (err) => {
39
39
  if (err.response) {
@@ -0,0 +1,53 @@
1
+ export function extractTranslationKeys(component: any): string[] {
2
+ const keys: string[] = [];
3
+ // 优化后的正则表达式,兼容 $t 和 _ctx.$t 的情况
4
+ const regex = /(?:\$t|_ctx\.\$t)\s*\(\s*(['"])([^'"]+?)\1/gs;
5
+
6
+ // 解析组件选项中的模板
7
+ if (component.template) {
8
+ let match;
9
+ while ((match = regex.exec(component.template)) !== null) {
10
+ // 提取第二个捕获组的内容,即引号内的值
11
+ keys.push(match[2]);
12
+ }
13
+ }
14
+
15
+ // 解析组件选项中的渲染函数
16
+ if (component.render) {
17
+ const renderCode = component.render.toString();
18
+ let match;
19
+ while ((match = regex.exec(renderCode)) !== null) {
20
+ keys.push(match[2]);
21
+ }
22
+ }
23
+
24
+ // 解析组件的 setup 函数或 methods 中的 JS 代码
25
+ if (component.setup) {
26
+ const setupCode = component.setup.toString();
27
+ let match;
28
+ while ((match = regex.exec(setupCode)) !== null) {
29
+ keys.push(match[2]);
30
+ }
31
+ }
32
+
33
+ if (component.methods) {
34
+ for (const methodName in component.methods) {
35
+ const methodCode = component.methods[methodName].toString();
36
+ let match;
37
+ while ((match = regex.exec(methodCode)) !== null) {
38
+ keys.push(match[2]);
39
+ }
40
+ }
41
+ }
42
+
43
+ // 递归处理子组件
44
+ if (component.components) {
45
+ for (const childComponentName in component.components) {
46
+ const childComponent = component.components[childComponentName];
47
+ keys.push(...extractTranslationKeys(childComponent));
48
+ }
49
+ }
50
+
51
+ // 合并同名的 key,利用 Set 去重
52
+ return [...new Set(keys)];
53
+ }
@@ -33,8 +33,8 @@ export const fetchTranslate = async ({
33
33
  Authorization: token
34
34
  }
35
35
  });
36
- if (res && res.result == 0) {
37
- resolve(res.retVal);
36
+ if (res.data && res.data.result == 0) {
37
+ resolve(res.data.retVal);
38
38
  } else {
39
39
  resolve(res);
40
40
  }
package/src/main.js CHANGED
@@ -7,12 +7,12 @@ import zh from "view-ui-plus/dist/locale/zh-CN";
7
7
  const i18n = new gc_i18n({
8
8
  appCode: "TEST",
9
9
  router,
10
+ orgCode: "GREENCLOUD",
10
11
  messages: {
11
12
  "zh-CN": { ...zh, test: "合计{0}条" }
12
13
  }
13
14
  });
14
- console.log("i18n", i18n);
15
-
15
+ window.i18n = i18n;
16
16
  const app = createApp(App).use(router).use(i18n);
17
17
 
18
18
  app.mount("#app");
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { useI18n } from "vue-i18n";
3
3
  const { locale, changeLocale } = useI18n();
4
-
4
+ import { ref } from "vue";
5
5
  defineProps({
6
6
  msg: String
7
7
  });
@@ -19,6 +19,7 @@ const langs = [
19
19
  code: "zh-TW"
20
20
  }
21
21
  ];
22
+ const TEST = ref($t("gbfb"));
22
23
  const total = [100];
23
24
  const change = (lang) => {
24
25
  changeLocale(lang);
@@ -40,17 +41,61 @@ const change = (lang) => {
40
41
  </div>
41
42
 
42
43
  <div>
43
- <div>有变量的:</div>
44
- <div>{{ $t("test", total) }}</div>
44
+ <div>有变量的xxx:</div>
45
+ <div>{{ $t("test", total, "找不到test") }}</div>
45
46
  </div>
46
47
  <div>
47
48
  <div>不带前缀:</div>
48
- <div>{{ $t("DAOCHU", "找不到") }}</div>
49
+ <div>{{ $t("xwlywz7", "找不到") }}</div>
49
50
  <!-- <div>{{ $t("中文{0}", [{ comment: "找不到" }]) }}</div> -->
50
51
  </div>
51
52
  <div>
52
- <div>完全找不到的数据:</div>
53
- <div>{{ $t("ggbb", "找不到") }}</div>
53
+ <div>测试:</div>
54
+ <div>{{ $t("ewzh2", "找不到") }}</div>
55
+ </div>
56
+ <div>
57
+ <div>测试:</div>
58
+ <div>{{ $t("4j3dy81", "找不到") }}</div>
59
+ </div>
60
+ <div>
61
+ <div>测试:</div>
62
+ <div>{{ $t("4j3dy82", "找不到") }}</div>
63
+ </div>
64
+ <div>
65
+ <div>测试:</div>
66
+ <div>{{ $t("4j3dy83", "找不到") }}</div>
67
+ </div>
68
+ <div>
69
+ <div>测试:</div>
70
+ <div>{{ $t("4j3dy85", "找不到") }}</div>
71
+ </div>
72
+ <div>
73
+ <div>测试:</div>
74
+ <div>{{ $t("4j3dy84", "找不到") }}</div>
75
+ </div>
76
+ <div>
77
+ <div>测试:</div>
78
+ <div>{{ $t("4j3dy87", "找不到") }}</div>
79
+ </div>
80
+ <div>
81
+ <div>测试:</div>
82
+ <div>{{ $t("4j3dy88", "找不到") }}</div>
83
+ </div>
84
+ <div>
85
+ <div>测试:</div>
86
+ <div>{{ $t("4j3dy86", "找不到") }}</div>
87
+ </div>
88
+ <div>
89
+ <div>测试:</div>
90
+ <div>{{ $t("4j3dy86", "找不到") }}</div>
91
+ </div>
92
+ <div>
93
+ <div>测试:</div>
94
+ <div>{{ $t("4j3dy86", "找不到") }}</div>
95
+ </div>
96
+ <div>
97
+ <div>测试:</div>
98
+ <div>{{ $t("4j3dy86", "找不到") }}</div>
54
99
  </div>
55
100
  <Page
56
101
  :total="40"
package/src/view/Home.vue CHANGED
@@ -2,12 +2,16 @@
2
2
  import { useI18n } from "vue-i18n";
3
3
  const { locale, changeLocal } = useI18n();
4
4
 
5
- const change = (lang) => {
6
- changeLocal(lang);
5
+ const change = async (lang) => {
6
+ const res = await i18n.getLanguages();
7
+ console.log("res", res);
8
+ // locale.value = lang;
7
9
  };
8
10
  </script>
9
11
 
10
12
  <template>
11
13
  {{ locale }}
12
- <Language :more="true"></Language>
14
+ {{ $t("home", "主页", "lang") }}
15
+ <button @click="change('en')">en</button>
16
+ <button @click="change('zh')">zh</button>
13
17
  </template>
package/vite.config.js CHANGED
@@ -19,7 +19,16 @@ export default defineConfig({
19
19
  rollupOptions: {
20
20
  terserOptions: false,
21
21
  // 确保外部化处理那些你不想打包进库的依赖
22
- external: ["vue", "axios", "lodash-es", "view-ui-plus"],
22
+ external: [
23
+ "vue",
24
+ "axios",
25
+ "lodash-es",
26
+ "view-ui-plus",
27
+ "store2",
28
+ "jsrsasign",
29
+ "vue-i18n",
30
+ "keyboardjs"
31
+ ],
23
32
  output: {
24
33
  // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
25
34
  globals: {
@@ -38,9 +47,9 @@ export default defineConfig({
38
47
  },
39
48
  outDir: "lib"
40
49
  // outDir:
41
- // "/Users/huqiliang/Documents/lvyun/dpms-front/node_modules/gc_i18n/lib"
50
+ // "/Users/huqiliang/Documents/github/auto-i18n-translation-plugins/example/vue3/node_modules/gc_i18n/lib"
42
51
  // outDir:
43
- // "/Users/huqiliang/Documents/lvyun/travel-ship-standard-front/node_modules/gc_i18n/lib"
52
+ // "/Users/huqiliang/Documents/lvyun/dpms-front/node_modules/gc_i18n/lib"
44
53
  },
45
54
  plugins: [vue(), vueJsx(), libCss()]
46
55
  });
@@ -1,93 +0,0 @@
1
- <script>
2
- import { getLanguages } from "../libs/service";
3
- import store from "store2";
4
- import _ from "lodash-es";
5
- import { useI18n } from "vue-i18n";
6
-
7
- export default {
8
- setup() {
9
- const { changeLocal, locale } = useI18n();
10
- return {
11
- changeLocal,
12
- locale
13
- };
14
- },
15
- name: "LangChange",
16
- data() {
17
- return {
18
- languages: []
19
- };
20
- },
21
- props: {
22
- token: String,
23
- simple: {
24
- type: Boolean,
25
- default: false
26
- }
27
- },
28
- methods: {
29
- toggle() {
30
- const langToggle = _.find(
31
- this.languages,
32
- (item) => item.code !== this.locale
33
- );
34
-
35
- this.changeLocal(langToggle.code);
36
- }
37
- },
38
- async mounted() {
39
- this.language = store.get("I18N_LANGUAGE") || navigator.language;
40
- const res = await getLanguages(this.token);
41
- if (res && res.result === 0) {
42
- this.languages = res.retVal;
43
- }
44
- }
45
- };
46
- </script>
47
- <template>
48
- <div
49
- class="gc_i18n_language"
50
- v-if="languages && languages.length > 0"
51
- >
52
- <div v-if="languages.length === 1">
53
- <span>{{ languages[0].name }}</span>
54
- </div>
55
- <div
56
- v-else-if="languages.length === 2"
57
- class="two"
58
- >
59
- <span
60
- class="item"
61
- v-for="item in languages"
62
- @click="toggle"
63
- :class="item.code == locale ? 'active' : 'hide'"
64
- >{{ item.name }}</span
65
- >
66
- </div>
67
- <Select
68
- :modelValue="locale"
69
- @on-change="changeLocal"
70
- v-else
71
- >
72
- <Option
73
- :value="item.code"
74
- :label="item.name"
75
- v-for="item in languages"
76
- >{{ item.name }}</Option
77
- >
78
- </Select>
79
- </div>
80
- </template>
81
-
82
- <style lang="less" scoped>
83
- .gc_i18n_language {
84
- .two {
85
- .item {
86
- cursor: pointer;
87
- }
88
- .hide {
89
- display: none;
90
- }
91
- }
92
- }
93
- </style>