vue-i18n-extract-plugin 1.0.56 → 1.0.58

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
@@ -24,10 +24,10 @@ yarn global add vue-i18n-extract-plugin
24
24
  ## CLI
25
25
 
26
26
  ```bash
27
- extract-i18n --includePath=demo --rewrite
27
+ extract-i18n --includePath=demo/src --rewrite
28
28
  ```
29
29
 
30
- 这会提取demo目录下的所有`allowedExtensions`文件的`fromLang`,并生成一个对应的JSON文件,如果开启了自动翻译,则会自动翻译并生成对应的翻译JSON文件.
30
+ 这会提取demo/src目录下的所有`allowedExtensions`文件的`fromLang`,并生成一个对应的JSON文件,如果开启了自动翻译,则会自动翻译并生成对应的翻译JSON文件.
31
31
 
32
32
  ## Programming API
33
33
 
@@ -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 文件的防抖时间
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 文件的防抖时间
package/lib/visitors.js CHANGED
@@ -37,7 +37,7 @@ function isJSXElement(path, nodeName) {
37
37
  ) {
38
38
  const jsxAttr = path.findParent(p => p.isJSXAttribute());
39
39
  const attrName = jsxAttr.node.name.name;
40
- if (attrName === "defaultMsg" || attrName === "values") {
40
+ if (attrName === "msg" || attrName === "values") {
41
41
  return true;
42
42
  }
43
43
  }
@@ -168,6 +168,10 @@ function createI18nVisitor(option, i18nMap) {
168
168
 
169
169
  const newArg = t.stringLiteral(generateText(keyText, hashed, option));
170
170
  path.node.arguments[0] = newArg;
171
+
172
+ if (option.keepDefaultMsg) {
173
+ path.node.arguments.push(t.stringLiteral(keyText));
174
+ }
171
175
  },
172
176
 
173
177
  StringLiteral(path) {
@@ -181,7 +185,7 @@ function createI18nVisitor(option, i18nMap) {
181
185
  return;
182
186
  }
183
187
 
184
- // 跳过<Trans defaultMsg="你好,{name}" values={{name: '世界'} /> defaultMsg和values属性的转换
188
+ // 跳过<Trans msg="你好,{name}" values={{name: '世界'} /> msg和values属性的转换
185
189
  if (isJSXElement(path, option.JSXElement)) {
186
190
  return;
187
191
  }
@@ -227,7 +231,10 @@ function createI18nVisitor(option, i18nMap) {
227
231
  t.identifier("_ctx"),
228
232
  t.identifier(option.translateKey)
229
233
  ),
230
- [t.stringLiteral(generateText(value, hashed, option))]
234
+ [
235
+ t.stringLiteral(generateText(value, hashed, option)),
236
+ option.keepDefaultMsg ? t.stringLiteral(value) : null
237
+ ].filter(Boolean)
231
238
  );
232
239
 
233
240
  // 判断是否createTextVNode或MemberExpression(如 Vue.createTextVNode)
@@ -253,9 +260,13 @@ function createI18nVisitor(option, i18nMap) {
253
260
  const hasCreateVNode = transformDirectiveIfNeeded(path, parentPath);
254
261
  if (!hasCreateVNode) {
255
262
  // 生成 $t("hashed")
256
- callExpression = t.callExpression(t.identifier(option.translateKey), [
257
- t.stringLiteral(generateText(value, hashed, option))
258
- ]);
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
+ );
259
270
  }
260
271
  }
261
272
 
@@ -302,7 +313,10 @@ function createI18nVisitor(option, i18nMap) {
302
313
  }
303
314
 
304
315
  // 替换为字符类型翻译节点
305
- 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
+
306
320
  node.value.raw = node.value.cooked = `\${${tCallExpression}}`;
307
321
  },
308
322
  JSXText(path) {
@@ -325,9 +339,13 @@ function createI18nVisitor(option, i18nMap) {
325
339
  // 替换为表达式 {$t("hashed")}
326
340
  path.replaceWith(
327
341
  t.jsxExpressionContainer(
328
- t.callExpression(t.identifier(option.translateKey), [
329
- t.stringLiteral(generateText(text, hashed, option))
330
- ])
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
+ )
331
349
  )
332
350
  );
333
351
  },
@@ -352,15 +370,19 @@ function createI18nVisitor(option, i18nMap) {
352
370
 
353
371
  path.replaceWith(
354
372
  t.jsxExpressionContainer(
355
- t.callExpression(t.identifier(option.translateKey), [
356
- t.stringLiteral(generateText(value, hashed, option))
357
- ])
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
+ )
358
380
  )
359
381
  );
360
382
  }
361
383
  },
362
384
  JSXElement(path) {
363
- // <Trans id="aaa" defaultMsg="xxx" />
385
+ // <Trans id="aaa" msg="xxx" />
364
386
  const openingElement = path.node.openingElement;
365
387
  if (
366
388
  !t.isJSXIdentifier(openingElement.name) ||
@@ -371,9 +393,9 @@ function createI18nVisitor(option, i18nMap) {
371
393
 
372
394
  let idAttr = null;
373
395
  let idValue = null;
374
- let defaultMsgValue = null;
396
+ let msgValue = null;
375
397
 
376
- // 遍历属性,查找 id 和 defaultMsg
398
+ // 遍历属性,查找 id 和 msg
377
399
  openingElement.attributes.forEach(attr => {
378
400
  if (!t.isJSXAttribute(attr) || !t.isJSXIdentifier(attr.name)) return;
379
401
 
@@ -384,26 +406,26 @@ function createI18nVisitor(option, i18nMap) {
384
406
  }
385
407
  }
386
408
 
387
- if (attr.name.name === "defaultMsg") {
409
+ if (attr.name.name === "msg") {
388
410
  if (attr.value && t.isStringLiteral(attr.value)) {
389
- defaultMsgValue = attr.value.value;
411
+ msgValue = attr.value.value;
390
412
  }
391
413
  }
392
414
  });
393
415
 
394
- // 计算 id,如果未提供,则使用 defaultMsg 的哈希值
395
- if (!idValue && defaultMsgValue) {
396
- idValue = generateId(defaultMsgValue, option);
416
+ // 计算 id,如果未提供,则使用 msg 的哈希值
417
+ if (!idValue && msgValue) {
418
+ idValue = generateId(msgValue, option);
397
419
 
398
420
  if (i18nMap) {
399
- i18nMap[idValue] = defaultMsgValue;
421
+ i18nMap[idValue] = msgValue;
400
422
  }
401
423
  }
402
424
 
403
- // 有id并且有defaultMsg的情况
404
- if (idValue && defaultMsgValue) {
425
+ // 有id并且有msg的情况
426
+ if (idValue && msgValue) {
405
427
  if (i18nMap) {
406
- i18nMap[idValue] = defaultMsgValue;
428
+ i18nMap[idValue] = msgValue;
407
429
  }
408
430
  }
409
431
 
@@ -418,14 +440,14 @@ function createI18nVisitor(option, i18nMap) {
418
440
  }
419
441
  }
420
442
 
421
- if (!option.keepRaw) {
422
- // 移除 defaultMsg
443
+ if (!option.keepDefaultMsg && !option.keepRaw) {
444
+ // 移除 msg
423
445
  openingElement.attributes = openingElement.attributes.filter(
424
446
  attr =>
425
447
  !(
426
448
  t.isJSXAttribute(attr) &&
427
449
  t.isJSXIdentifier(attr.name) &&
428
- attr.name.name === "defaultMsg"
450
+ attr.name.name === "msg"
429
451
  )
430
452
  );
431
453
  }
@@ -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.56",
3
+ "version": "1.0.58",
4
4
  "main": "lib/index.js",
5
5
  "types": "types/index.d.ts",
6
6
  "bin": {
@@ -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