vue-i18n-extract-plugin 1.0.62 → 1.0.64

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
@@ -48,7 +48,8 @@ extractI18n(options)
48
48
  ```javascript
49
49
  const defaultOptions = {
50
50
  translateKey: "$t", // 提取的函数的名称
51
- JSXElement: "Trans", // 提取的函数的 JSX 元素名称 默认为 Trans, 如:<Trans id="aaa" defaultMsg="xxx" />
51
+ JSXElement: "Trans", // 提取的函数的 JSX 元素名称 默认为 Trans, 如:<Trans id="aaa" msg="xxx" />
52
+ jsx: false, // 是否启用 JSX 语法转换,开启后JSX里纯文件将转换为 <Trans id="aaa" msg="xxx" />而不是 $t("aaa")
52
53
  rewrite: false, // 是否将提取到的内容转换为id后重写入源文件
53
54
  extractFromText: true, // 是否允许从纯文本节点中提取翻译内容
54
55
  autoImportI18n: true, // 是否自动导入 i18n 模块
@@ -2,8 +2,7 @@ const { declare } = require("@babel/helper-plugin-utils");
2
2
  const { createI18nPlugin } = require("./visitors");
3
3
  const { defaultOptions } = require("./options");
4
4
 
5
- module.exports = declare((api, options) => {
6
- api.assertVersion(7);
5
+ module.exports = declare((_, options) => {
7
6
  options = { ...defaultOptions, ...options };
8
7
 
9
8
  if (!options.enabled) {
@@ -1,4 +1,4 @@
1
- const t = require("@babel/types");
1
+ const { i18nImportAstTransform } = require("./import-i18n-transform");
2
2
  const { defaultOptions } = require("./options");
3
3
 
4
4
  module.exports = function () {
@@ -9,6 +9,8 @@ module.exports = function () {
9
9
  const {
10
10
  translateKey: importName,
11
11
  i18nPkgImportPath: importPath,
12
+ JSXElement,
13
+ jsx,
12
14
  enabled
13
15
  } = {
14
16
  ...defaultOptions,
@@ -17,126 +19,11 @@ module.exports = function () {
17
19
 
18
20
  if (!enabled) return;
19
21
 
20
- const localName = importName;
21
- let hasI18nImport = false;
22
- let lastImportPath = null;
23
- let conflictTDefined = false;
22
+ const ast = path.parentPath?.node || path.node;
23
+ const importNames = jsx ? [importName, JSXElement] : [importName];
24
24
 
25
- path.traverse({
26
- // ImportDeclaration(path) {
27
- // lastImportPath = path;
28
- // if (
29
- // path.node.source.value === importPath &&
30
- // path.node.specifiers.some(
31
- // spec =>
32
- // t.isImportSpecifier(spec) && spec.imported.name === importName
33
- // )
34
- // ) {
35
- // hasI18nImport = true;
36
- // }
37
- // },
38
- ImportDeclaration(path) {
39
- lastImportPath = path;
40
-
41
- const sourcePath = path.node.source.value;
42
-
43
- // 判断是否是其它路径导入了 $t
44
- const importedTElsewhere = path.node.specifiers.some(spec => {
45
- return (
46
- (t.isImportSpecifier(spec) ||
47
- t.isImportDefaultSpecifier(spec)) &&
48
- spec.local.name === importName &&
49
- sourcePath !== importPath
50
- );
51
- });
52
-
53
- if (importedTElsewhere) {
54
- conflictTDefined = true;
55
- path.stop();
56
- return;
57
- }
58
-
59
- // 检查是否已经导入目标路径
60
- if (sourcePath === importPath) {
61
- hasI18nImport = true;
62
-
63
- // 情况1:已有 import { $t } from '@/i18n'
64
- const existingImport = path.node.specifiers.find(
65
- spec =>
66
- t.isImportSpecifier(spec) && spec.imported.name === importName
67
- );
68
-
69
- if (existingImport) return;
70
-
71
- // if (
72
- // existingImport &&
73
- // !t.isImportSpecifier(existingImport, {
74
- // local: { name: localName }
75
- // })
76
- // ) {
77
- // // 将 $t 改为 _$t
78
- // existingImport.local = t.identifier(localName);
79
- // } else
80
- // 情况2:已有 import i18n from '@/i18n'
81
-
82
- // 添加 $t 到已有的 import
83
- path.node.specifiers.push(
84
- t.importSpecifier(
85
- t.identifier(localName),
86
- t.identifier(importName)
87
- )
88
- );
89
- }
90
- },
91
- VariableDeclarator(path) {
92
- if (
93
- t.isIdentifier(path.node.id) &&
94
- path.node.id.name === importName
95
- ) {
96
- conflictTDefined = true;
97
- path.stop();
98
- }
99
- },
100
-
101
- FunctionDeclaration(path) {
102
- if (
103
- t.isIdentifier(path.node.id) &&
104
- path.node.id.name === importName
105
- ) {
106
- conflictTDefined = true;
107
- path.stop();
108
- }
109
- }
110
- });
111
-
112
- // 跳过导入语句的生成
113
- if (conflictTDefined) {
114
- return {
115
- ast,
116
- needTransform: false
117
- };
118
- }
119
-
120
- // 情况4:完全没有导入 @/i18n
121
- if (!hasI18nImport) {
122
- const importNode = t.importDeclaration(
123
- [
124
- // t.importDefaultSpecifier(t.identifier("i18n")),
125
- t.importSpecifier(
126
- t.identifier(localName),
127
- t.identifier(importName)
128
- )
129
- ],
130
- t.stringLiteral(importPath)
131
- );
132
-
133
- if (lastImportPath) {
134
- // 插入到最后一个 import 之后
135
- lastImportPath.insertAfter(importNode);
136
- } else {
137
- // 插入到文件顶部
138
- path.unshiftContainer("body", importNode);
139
- }
25
+ for (const importName of importNames) {
26
+ i18nImportAstTransform(ast, importName, importPath);
140
27
  }
141
28
  }
142
29
  }
package/lib/extract.js CHANGED
@@ -170,7 +170,13 @@ function createDirectiveFromProp(prop, content) {
170
170
  }
171
171
 
172
172
  function addI18nImportIfNeeded(ast, options, generateCode) {
173
- i18nImportAstTransform(ast, options.translateKey, options.i18nPkgImportPath);
173
+ const importNames = options.jsx
174
+ ? [options.translateKey, options.JSXElement]
175
+ : [options.translateKey];
176
+
177
+ for (const importName of importNames) {
178
+ i18nImportAstTransform(ast, importName, options.i18nPkgImportPath);
179
+ }
174
180
 
175
181
  if (generateCode) {
176
182
  return generate(ast);
@@ -7,7 +7,11 @@ function i18nImportAstTransform(ast, importName, importPath) {
7
7
  let hasI18nImport = false;
8
8
  let lastImportNode = null;
9
9
  let needTransform = false;
10
- let conflictTDefined = false;
10
+ let conflictDefined = false;
11
+
12
+ if (importName.includes(".")) {
13
+ importName = importName.split(".")[0];
14
+ }
11
15
 
12
16
  traverse(ast, {
13
17
  ImportDeclaration(path) {
@@ -15,63 +19,50 @@ function i18nImportAstTransform(ast, importName, importPath) {
15
19
 
16
20
  const sourcePath = path.node.source.value;
17
21
 
18
- // 判断是否是其它路径导入了 $t
19
- const importedTElsewhere = path.node.specifiers.some(spec => {
22
+ const importedElsewhere = path.node.specifiers.some(spec => {
20
23
  return (
21
24
  (t.isImportSpecifier(spec) || t.isImportDefaultSpecifier(spec)) &&
22
- spec.local.name === importName &&
23
- sourcePath !== importPath
25
+ spec.local.name === importName
24
26
  );
25
27
  });
26
28
 
27
- if (importedTElsewhere) {
28
- conflictTDefined = true;
29
+ if (importedElsewhere) {
30
+ conflictDefined = true;
29
31
  path.stop();
30
32
  return;
31
33
  }
32
34
 
33
- // 检查是否已经导入目标路径
34
35
  if (sourcePath === importPath) {
35
- hasI18nImport = true;
36
-
37
- // 情况1:已有 import { $t } from '@/i18n'
38
- const existImport = path.node.specifiers.some(
39
- spec => t.isImportSpecifier(spec) && spec.imported.name === importName
40
- );
41
-
42
- if (existImport) return;
43
-
44
- // 添加 $t 到已有的 import
45
36
  path.node.specifiers.push(
46
37
  t.importSpecifier(t.identifier(importName), t.identifier(importName))
47
38
  );
39
+
40
+ hasI18nImport = true;
48
41
  needTransform = true;
49
42
  }
50
43
  },
51
44
  VariableDeclarator(path) {
52
45
  if (t.isIdentifier(path.node.id) && path.node.id.name === importName) {
53
- conflictTDefined = true;
46
+ conflictDefined = true;
54
47
  path.stop();
55
48
  }
56
49
  },
57
50
 
58
51
  FunctionDeclaration(path) {
59
52
  if (t.isIdentifier(path.node.id) && path.node.id.name === importName) {
60
- conflictTDefined = true;
53
+ conflictDefined = true;
61
54
  path.stop();
62
55
  }
63
56
  }
64
57
  });
65
58
 
66
- // 跳过导入语句的生成
67
- if (conflictTDefined) {
59
+ if (conflictDefined) {
68
60
  return {
69
61
  ast,
70
62
  needTransform: false
71
63
  };
72
64
  }
73
65
 
74
- // 情况4:完全没有导入 @/i18n
75
66
  if (!hasI18nImport) {
76
67
  const importNode = t.importDeclaration(
77
68
  [
@@ -90,7 +81,6 @@ function i18nImportAstTransform(ast, importName, importPath) {
90
81
  } else {
91
82
  ast.program.body.unshift(importNode);
92
83
  }
93
- needTransform = true;
94
84
  }
95
85
 
96
86
  return {
@@ -99,7 +89,7 @@ function i18nImportAstTransform(ast, importName, importPath) {
99
89
  };
100
90
  }
101
91
 
102
- async function i18nImportTransform(code, path, importName, importPath) {
92
+ async function i18nImportTransform(code, path, importNames, importPath) {
103
93
  const scriptContent = extractScriptContent(code, path);
104
94
  if (!scriptContent) return code;
105
95
 
@@ -113,14 +103,19 @@ async function i18nImportTransform(code, path, importName, importPath) {
113
103
  ].filter(Boolean)
114
104
  });
115
105
 
116
- const { needTransform } = i18nImportAstTransform(
117
- ast,
118
- importName,
119
- importPath
120
- );
106
+ let transformNeeded = false;
107
+
108
+ for (const importName of importNames) {
109
+ const { needTransform } = i18nImportAstTransform(
110
+ ast,
111
+ importName,
112
+ importPath
113
+ );
114
+ transformNeeded = transformNeeded || needTransform;
115
+ }
121
116
 
122
117
  // 只有当需要修改时才重新生成代码
123
- if (needTransform) {
118
+ if (transformNeeded) {
124
119
  const { code: newScript } = generate(ast);
125
120
  return path.endsWith(".vue")
126
121
  ? code.replace(scriptContent, newScript)
package/lib/options.js CHANGED
@@ -2,7 +2,8 @@ const { GoogleTranslator } = require("./translators");
2
2
 
3
3
  const defaultOptions = {
4
4
  translateKey: "$t", // 提取的函数的名称
5
- JSXElement: "Trans", // 提取的函数的 JSX 元素名称 默认为 Trans, 如:<Trans id="aaa" defaultMsg="xxx" />
5
+ JSXElement: "Trans", // 提取的函数的 JSX 元素名称 默认为 Trans, 如:<Trans id="aaa" msg="xxx" />
6
+ jsx: false, // 是否启用 JSX 语法转换,开启后JSX里纯文件将转换为 <Trans id="aaa" msg="xxx" />而不是 $t("aaa")
6
7
  rewrite: false, // 是否将提取到的内容转换为id后重写入源文件
7
8
  extractFromText: true, // 是否允许从纯文本节点中提取翻译内容
8
9
  autoImportI18n: true, // 是否自动导入 i18n 模块
package/lib/visitors.js CHANGED
@@ -11,7 +11,9 @@ function isTFunction(node, option) {
11
11
  t.isCallExpression(node) &&
12
12
  (t.isIdentifier(node.callee, { name: option.translateKey }) || // $t(...)
13
13
  (t.isMemberExpression(node.callee) &&
14
- t.isIdentifier(node.callee.property, { name: option.translateKey }))) // xxx.$t(...)
14
+ t.isIdentifier(node.callee.property, {
15
+ name: option.translateKey.split(".").pop()
16
+ }))) // xxx.$t(...)
15
17
  );
16
18
  }
17
19
 
@@ -136,6 +138,20 @@ function generateId(rawText, options) {
136
138
  return _generateId(rawText);
137
139
  }
138
140
 
141
+ function generateJSXElement(name, id, msg) {
142
+ // 生成 <Trans id="hashed" msg="xxxx" />
143
+ const openingElement = t.jsxOpeningElement(
144
+ t.jsxIdentifier(name),
145
+ [
146
+ t.jsxAttribute(t.jsxIdentifier("id"), t.stringLiteral(id)),
147
+ msg ? t.jsxAttribute(t.jsxIdentifier("msg"), t.stringLiteral(msg)) : null
148
+ ].filter(Boolean),
149
+ true // self-closing
150
+ );
151
+
152
+ return t.jsxElement(openingElement, null, [], true);
153
+ }
154
+
139
155
  function createI18nVisitor(option, i18nMap) {
140
156
  const excludedCall = [...option.excludedCall, ...EXCLUDED_CALL];
141
157
 
@@ -336,6 +352,16 @@ function createI18nVisitor(option, i18nMap) {
336
352
  i18nMap[hashed] = text;
337
353
  }
338
354
 
355
+ if (option.jsx) {
356
+ const jsxElement = generateJSXElement(
357
+ option.JSXElement,
358
+ hashed,
359
+ option.keepDefaultMsg ? text : null
360
+ );
361
+ path.replaceWith(jsxElement);
362
+ return;
363
+ }
364
+
339
365
  // 替换为表达式 {$t("hashed")}
340
366
  path.replaceWith(
341
367
  t.jsxExpressionContainer(
@@ -368,6 +394,19 @@ function createI18nVisitor(option, i18nMap) {
368
394
  i18nMap[hashed] = value;
369
395
  }
370
396
 
397
+ if (
398
+ option.jsx &&
399
+ (path.parentPath.isJSXElement() || path.parentPath.isJSXFragment())
400
+ ) {
401
+ const jsxElement = generateJSXElement(
402
+ option.JSXElement,
403
+ hashed,
404
+ option.keepDefaultMsg ? value : null
405
+ );
406
+ path.replaceWith(jsxElement);
407
+ return;
408
+ }
409
+
371
410
  path.replaceWith(
372
411
  t.jsxExpressionContainer(
373
412
  t.callExpression(
@@ -16,7 +16,9 @@ function vitePluginImportI18n(option) {
16
16
  return i18nImportTransform(
17
17
  code,
18
18
  path,
19
- option.translateKey,
19
+ option.jsx
20
+ ? [option.translateKey, option.JSXElement]
21
+ : [option.translateKey],
20
22
  option.i18nPkgImportPath
21
23
  );
22
24
  }
@@ -9,6 +9,8 @@ module.exports = function (source) {
9
9
  includePath,
10
10
  excludedPath,
11
11
  translateKey: importName,
12
+ JSXElement,
13
+ jsx,
12
14
  i18nPkgImportPath: importPath
13
15
  } = { ...defaultOptions, ...global.getOptions() };
14
16
 
@@ -42,7 +44,7 @@ module.exports = function (source) {
42
44
  return i18nImportTransform(
43
45
  source,
44
46
  global.resourcePath,
45
- importName,
47
+ jsx ? [importName, JSXElement] : [importName],
46
48
  importPath
47
49
  );
48
50
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-i18n-extract-plugin",
3
- "version": "1.0.62",
3
+ "version": "1.0.64",
4
4
  "main": "lib/index.js",
5
5
  "types": "types/index.d.ts",
6
6
  "bin": {