vue-i18n-extract-plugin 1.0.55 → 1.0.57

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/README.md CHANGED
@@ -55,6 +55,7 @@ const defaultOptions = {
55
55
  autoTranslate: true, // 提取完成后是否自动翻译
56
56
  cleanTranslate: true, // 是否清理无用的翻译内容
57
57
  keepRaw: false, // 开启后只做转换不生成hash值,即:"测试" -> $t("测试"), 开启rewrite时生效
58
+ keepDefaultMsg: false, // 保留默认消息,即:"测试" -> $t("hashedKey", "测试")
58
59
  enabled: true, // 是否启用插件
59
60
  outputJsonFileInPlugin: true, // 是否在插件中输出 JSON 文件
60
61
  outputJsonFileDebounceTimeInPlugin: 2000, // 输出 JSON 文件的防抖时间
@@ -63,6 +64,7 @@ const defaultOptions = {
63
64
  includePath: ['src/'], // 包含路径的数组
64
65
  excludedPath: [], // 排除路径的数组 refer to https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#how-to-exclude-directory-from-reading
65
66
  allowedExtensions: [".vue", ".tsx", ".ts", ".jsx", ".js"], // 允许提取的文件扩展名
67
+ generateId: null, // 自定义生成 key 的函数
66
68
  fromLang: 'zh-cn', // 源语言, 目前支持提取的语言有:zh-cn(zh-tw), en, ja, ko, ru
67
69
  translateLangKeys: ["zh-tw", "en"], // 需要翻译为的语言键
68
70
  i18nPkgImportPath: "@/i18n", // i18n语言包导入路径
package/lib/options.js CHANGED
@@ -9,6 +9,7 @@ const defaultOptions = {
9
9
  autoTranslate: true, // 提取完成后是否自动翻译
10
10
  cleanTranslate: true, // 是否清理无用的翻译内容
11
11
  keepRaw: false, // 开启后只做转换不生成hash值,即:"测试" -> $t("测试"), 开启rewrite时生效
12
+ keepDefaultMsg: false, // 保留默认消息,即:"测试" -> $t("hashedKey", "测试")
12
13
  enabled: true, // 是否启用插件
13
14
  outputJsonFileInPlugin: true, // 是否在插件中输出 JSON 文件
14
15
  outputJsonFileDebounceTimeInPlugin: 2000, // 输出 JSON 文件的防抖时间
@@ -17,6 +18,7 @@ const defaultOptions = {
17
18
  includePath: ["src/"], // 包含路径的数组
18
19
  excludedPath: [], // 排除路径的数组
19
20
  allowedExtensions: [".vue", ".tsx", ".ts", ".jsx", ".js"], // 允许提取的文件扩展名
21
+ generateId: null, // 自定义生成 key 的函数
20
22
  fromLang: "zh-cn", // 源语言, 目前支持提取的语言有:zh-cn(zh-tw), en, ja, ko, ru
21
23
  translateLangKeys: ["zh-tw", "en"], // 需要翻译为的语言键
22
24
  i18nPkgImportPath: "@/i18n", // i18n语言包导入路径
package/lib/visitors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const t = require("@babel/types");
2
2
  const {
3
- generateId,
3
+ generateId: _generateId,
4
4
  extractFunctionName,
5
5
  EXCLUDED_CALL,
6
6
  shouldExtract
@@ -129,6 +129,13 @@ function generateText(rawText, hashedText, options) {
129
129
  return hashedText;
130
130
  }
131
131
 
132
+ function generateId(rawText, options) {
133
+ if (typeof options.generateId === "function") {
134
+ return options.generateId(rawText);
135
+ }
136
+ return _generateId(rawText);
137
+ }
138
+
132
139
  function createI18nVisitor(option, i18nMap) {
133
140
  const excludedCall = [...option.excludedCall, ...EXCLUDED_CALL];
134
141
 
@@ -153,7 +160,7 @@ function createI18nVisitor(option, i18nMap) {
153
160
  return;
154
161
  }
155
162
 
156
- const hashed = generateId(keyText);
163
+ const hashed = generateId(keyText, option);
157
164
 
158
165
  if (i18nMap) {
159
166
  i18nMap[hashed] = keyText;
@@ -161,6 +168,10 @@ function createI18nVisitor(option, i18nMap) {
161
168
 
162
169
  const newArg = t.stringLiteral(generateText(keyText, hashed, option));
163
170
  path.node.arguments[0] = newArg;
171
+
172
+ if (option.keepDefaultMsg) {
173
+ path.node.arguments.push(t.stringLiteral(keyText));
174
+ }
164
175
  },
165
176
 
166
177
  StringLiteral(path) {
@@ -208,7 +219,7 @@ function createI18nVisitor(option, i18nMap) {
208
219
  return;
209
220
  }
210
221
 
211
- const hashed = generateId(value);
222
+ const hashed = generateId(value, option);
212
223
 
213
224
  if (i18nMap) {
214
225
  i18nMap[hashed] = value;
@@ -220,7 +231,10 @@ function createI18nVisitor(option, i18nMap) {
220
231
  t.identifier("_ctx"),
221
232
  t.identifier(option.translateKey)
222
233
  ),
223
- [t.stringLiteral(generateText(value, hashed, option))]
234
+ [
235
+ t.stringLiteral(generateText(value, hashed, option)),
236
+ option.keepDefaultMsg ? t.stringLiteral(value) : null
237
+ ].filter(Boolean)
224
238
  );
225
239
 
226
240
  // 判断是否createTextVNode或MemberExpression(如 Vue.createTextVNode)
@@ -246,9 +260,13 @@ function createI18nVisitor(option, i18nMap) {
246
260
  const hasCreateVNode = transformDirectiveIfNeeded(path, parentPath);
247
261
  if (!hasCreateVNode) {
248
262
  // 生成 $t("hashed")
249
- callExpression = t.callExpression(t.identifier(option.translateKey), [
250
- t.stringLiteral(generateText(value, hashed, option))
251
- ]);
263
+ callExpression = t.callExpression(
264
+ t.identifier(option.translateKey),
265
+ [
266
+ t.stringLiteral(generateText(value, hashed, option)),
267
+ option.keepDefaultMsg ? t.stringLiteral(value) : null
268
+ ].filter(Boolean)
269
+ );
252
270
  }
253
271
  }
254
272
 
@@ -288,14 +306,17 @@ function createI18nVisitor(option, i18nMap) {
288
306
 
289
307
  if (option.extractFromText === false) return;
290
308
 
291
- const hashed = generateId(value);
309
+ const hashed = generateId(value, option);
292
310
 
293
311
  if (i18nMap) {
294
312
  i18nMap[hashed] = value;
295
313
  }
296
314
 
297
315
  // 替换为字符类型翻译节点
298
- const tCallExpression = `${option.translateKey}('${generateText(value, hashed, option)}')`;
316
+ const tCallExpression = option.keepDefaultMsg
317
+ ? `${option.translateKey}('${generateText(value, hashed, option)}', ${JSON.stringify(value)})`
318
+ : `${option.translateKey}('${generateText(value, hashed, option)}')`;
319
+
299
320
  node.value.raw = node.value.cooked = `\${${tCallExpression}}`;
300
321
  },
301
322
  JSXText(path) {
@@ -309,7 +330,7 @@ function createI18nVisitor(option, i18nMap) {
309
330
  return;
310
331
  }
311
332
 
312
- const hashed = generateId(text);
333
+ const hashed = generateId(text, option);
313
334
 
314
335
  if (i18nMap) {
315
336
  i18nMap[hashed] = text;
@@ -318,9 +339,13 @@ function createI18nVisitor(option, i18nMap) {
318
339
  // 替换为表达式 {$t("hashed")}
319
340
  path.replaceWith(
320
341
  t.jsxExpressionContainer(
321
- t.callExpression(t.identifier(option.translateKey), [
322
- t.stringLiteral(generateText(text, hashed, option))
323
- ])
342
+ t.callExpression(
343
+ t.identifier(option.translateKey),
344
+ [
345
+ t.stringLiteral(generateText(text, hashed, option)),
346
+ option.keepDefaultMsg ? t.stringLiteral(text) : null
347
+ ].filter(Boolean)
348
+ )
324
349
  )
325
350
  );
326
351
  },
@@ -337,7 +362,7 @@ function createI18nVisitor(option, i18nMap) {
337
362
  return;
338
363
  }
339
364
 
340
- const hashed = generateId(value);
365
+ const hashed = generateId(value, option);
341
366
 
342
367
  if (i18nMap) {
343
368
  i18nMap[hashed] = value;
@@ -345,9 +370,13 @@ function createI18nVisitor(option, i18nMap) {
345
370
 
346
371
  path.replaceWith(
347
372
  t.jsxExpressionContainer(
348
- t.callExpression(t.identifier(option.translateKey), [
349
- t.stringLiteral(generateText(value, hashed, option))
350
- ])
373
+ t.callExpression(
374
+ t.identifier(option.translateKey),
375
+ [
376
+ t.stringLiteral(generateText(value, hashed, option)),
377
+ option.keepDefaultMsg ? t.stringLiteral(value) : null
378
+ ].filter(Boolean)
379
+ )
351
380
  )
352
381
  );
353
382
  }
@@ -386,7 +415,7 @@ function createI18nVisitor(option, i18nMap) {
386
415
 
387
416
  // 计算 id,如果未提供,则使用 defaultMsg 的哈希值
388
417
  if (!idValue && defaultMsgValue) {
389
- idValue = generateId(defaultMsgValue);
418
+ idValue = generateId(defaultMsgValue, option);
390
419
 
391
420
  if (i18nMap) {
392
421
  i18nMap[idValue] = defaultMsgValue;
@@ -411,7 +440,7 @@ function createI18nVisitor(option, i18nMap) {
411
440
  }
412
441
  }
413
442
 
414
- if (!option.keepRaw) {
443
+ if (!option.keepDefaultMsg && !option.keepRaw) {
415
444
  // 移除 defaultMsg
416
445
  openingElement.attributes = openingElement.attributes.filter(
417
446
  attr =>
@@ -1,15 +1,52 @@
1
+ const { createFilter } = require("@rollup/pluginutils");
1
2
  const { transformAsync } = require("@babel/core");
2
3
  const { createI18nPlugin } = require("./visitors");
3
- const { relativeCWDPath, checkAgainstRegexArray } = require("./utils");
4
+ const {
5
+ relativeCWDPath,
6
+ resolveFilterPath,
7
+ fixFolderPath
8
+ } = require("./utils");
4
9
  const { defaultOptions } = require("./options");
5
10
  const { globalI18nMap, handleFinalI18nMap } = require("./extract");
6
11
 
12
+ function createFilterFn(option) {
13
+ const {
14
+ i18nPkgImportPath: importPath,
15
+ allowedExtensions: extensions,
16
+ includePath,
17
+ excludedPath
18
+ } = option;
19
+
20
+ return createFilter(
21
+ extensions
22
+ .map(ext => includePath.map(p => `${fixFolderPath(p)}**/*${ext}`))
23
+ .flat(),
24
+ [
25
+ "node_modules/**",
26
+ importPath.endsWith("/")
27
+ ? [
28
+ resolveFilterPath(importPath + "index.ts"),
29
+ resolveFilterPath(importPath + "index.js")
30
+ ]
31
+ : [
32
+ resolveFilterPath(importPath + "/index.ts"),
33
+ resolveFilterPath(importPath + "/index.js"),
34
+ resolveFilterPath(importPath + ".ts"),
35
+ resolveFilterPath(importPath + ".js")
36
+ ],
37
+ excludedPath.map(resolveFilterPath)
38
+ ].flat()
39
+ );
40
+ }
41
+
7
42
  function vitePluginI18n(option) {
8
43
  option = { ...defaultOptions, ...option };
9
44
 
10
45
  let config;
11
46
  let timer;
12
47
 
48
+ const filter = createFilterFn(option);
49
+
13
50
  return {
14
51
  name: "vite-plugin-i18n-hash",
15
52
  enforce: "post",
@@ -41,39 +78,24 @@ function vitePluginI18n(option) {
41
78
  config = resolvedConfig;
42
79
  },
43
80
  async transform(code, path) {
44
- if (!option.enabled) return;
81
+ if (!option.enabled || !filter(path)) return;
45
82
 
46
- if (option.allowedExtensions.some(ext => path.endsWith(ext))) {
47
- if (
48
- option.includePath?.length &&
49
- !checkAgainstRegexArray(path, option.includePath)
50
- ) {
51
- return code;
52
- }
53
- if (
54
- option.excludedPath?.length &&
55
- checkAgainstRegexArray(path, option.excludedPath)
56
- ) {
57
- return code;
58
- }
59
-
60
- return transformAsync(code, {
61
- configFile: false,
62
- plugins: [createI18nPlugin(option, globalI18nMap)]
83
+ return transformAsync(code, {
84
+ configFile: false,
85
+ plugins: [createI18nPlugin(option, globalI18nMap)]
86
+ })
87
+ .then(result => {
88
+ if (option.outputJsonFileInPlugin && config?.command === "serve") {
89
+ clearTimeout(timer);
90
+ timer = setTimeout(() => {
91
+ handleFinalI18nMap(globalI18nMap, option, true);
92
+ }, option.outputJsonFileDebounceTimeInPlugin);
93
+ }
94
+ return result?.code;
63
95
  })
64
- .then(result => {
65
- if (option.outputJsonFileInPlugin && config?.command === "serve") {
66
- clearTimeout(timer);
67
- timer = setTimeout(() => {
68
- handleFinalI18nMap(globalI18nMap, option, true);
69
- }, option.outputJsonFileDebounceTimeInPlugin);
70
- }
71
- return result?.code;
72
- })
73
- .catch(e => {
74
- console.error(e);
75
- });
76
- }
96
+ .catch(e => {
97
+ console.error(e);
98
+ });
77
99
  },
78
100
  buildEnd() {},
79
101
  closeBundle() {
@@ -83,6 +105,8 @@ function vitePluginI18n(option) {
83
105
  };
84
106
  }
85
107
 
108
+ exports.createFilterFn = createFilterFn;
109
+
86
110
  module.exports = vitePluginI18n;
87
111
 
88
112
  /* import { defineConfig } from 'vite'
@@ -1,45 +1,24 @@
1
- const { createFilter } = require("@rollup/pluginutils");
2
1
  const { i18nImportTransform } = require("./import-i18n-transform");
2
+ const { createFilterFn } = require("./vite-plugin-i18n");
3
3
  const { defaultOptions } = require("./options");
4
- const { resolveFilterPath, fixFolderPath } = require("./utils");
5
4
 
6
- function vitePluginImportI18n(options) {
7
- const {
8
- translateKey: importName,
9
- i18nPkgImportPath: importPath,
10
- allowedExtensions: extensions,
11
- includePath,
12
- excludedPath,
13
- enabled
14
- } = { ...defaultOptions, ...options };
5
+ function vitePluginImportI18n(option) {
6
+ option = { ...defaultOptions, ...option };
15
7
 
16
- const filter = createFilter(
17
- extensions
18
- .map(ext => includePath.map(p => `${fixFolderPath(p)}**/*${ext}`))
19
- .flat(),
20
- [
21
- "node_modules/**",
22
- importPath.endsWith("/")
23
- ? [
24
- resolveFilterPath(importPath + "index.ts"),
25
- resolveFilterPath(importPath + "index.js")
26
- ]
27
- : [
28
- resolveFilterPath(importPath + "/index.ts"),
29
- resolveFilterPath(importPath + "/index.js"),
30
- resolveFilterPath(importPath + ".ts"),
31
- resolveFilterPath(importPath + ".js")
32
- ],
33
- excludedPath.map(resolveFilterPath)
34
- ].flat()
35
- );
8
+ const filter = createFilterFn(option);
36
9
 
37
10
  return {
38
11
  name: "vite-plugin-import-i18n",
39
12
  enforce: "pre",
40
13
  async transform(code, path) {
41
- if (!enabled || !filter(path)) return;
42
- return i18nImportTransform(code, path, importName, importPath);
14
+ if (!option.enabled || !filter(path)) return;
15
+
16
+ return i18nImportTransform(
17
+ code,
18
+ path,
19
+ option.translateKey,
20
+ option.i18nPkgImportPath
21
+ );
43
22
  }
44
23
  };
45
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-i18n-extract-plugin",
3
- "version": "1.0.55",
3
+ "version": "1.0.57",
4
4
  "main": "lib/index.js",
5
5
  "types": "types/index.d.ts",
6
6
  "bin": {
@@ -19,6 +19,7 @@ export interface I18nOptions {
19
19
  translateLangKeys: LangKey[]
20
20
  i18nPkgImportPath: string
21
21
  outputPath: string
22
+ generateId: ((text: string) => string) | null | undefined
22
23
  customGenLangFileName: (langKey: LangKey) => LangKey
23
24
  customTranslatedText: (text: string, toLang: LangKey) => string,
24
25
  // translator: new GoogleTranslator({
@@ -1,4 +1,6 @@
1
1
  import { Plugin } from 'vite';
2
2
  import { I18nOptions } from './options';
3
3
 
4
+ export function createFilterFn(option: Partial<I18nOptions>): (id: string) => boolean;
5
+
4
6
  export default function vitePluginI18n(option?: Partial<I18nOptions>): Plugin