vue-i18n-extract-plugin 1.0.63 → 1.0.65

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 模块
@@ -64,11 +65,11 @@ const defaultOptions = {
64
65
  includePath: ['src/'], // 包含路径的数组
65
66
  excludedPath: [], // 排除路径的数组 refer to https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#how-to-exclude-directory-from-reading
66
67
  allowedExtensions: [".vue", ".tsx", ".ts", ".jsx", ".js"], // 允许提取的文件扩展名
67
- generateId: null, // 自定义生成 key 的函数
68
68
  fromLang: 'zh-cn', // 源语言, 目前支持提取的语言有:zh-cn(zh-tw), en, ja, ko, ru
69
69
  translateLangKeys: ["zh-tw", "en"], // 需要翻译为的语言键
70
70
  i18nPkgImportPath: "@/i18n", // i18n语言包导入路径
71
71
  outputPath: "src/i18n", // 提取的语言包输出文件路径
72
+ generateId: null, // 自定义生成 key 的函数
72
73
  customGenLangFileName: langKey => langKey, // 自定义生成语言文件名
73
74
  // 翻译后的文本处理函数,方便对翻译后的文本进行二次加工,如每个单词首字母大写, params: text: 翻译后的文本, toLang: 翻译后的目标语言,translateLangKeys的枚举成员
74
75
  customTranslatedText: (text, toLang) => text,
@@ -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 () {
@@ -7,8 +7,10 @@ module.exports = function () {
7
7
  visitor: {
8
8
  Program(path, state) {
9
9
  const {
10
- translateKey: _importName,
10
+ translateKey: importName,
11
11
  i18nPkgImportPath: importPath,
12
+ JSXElement,
13
+ jsx,
12
14
  enabled
13
15
  } = {
14
16
  ...defaultOptions,
@@ -17,132 +19,11 @@ module.exports = function () {
17
19
 
18
20
  if (!enabled) return;
19
21
 
20
- let importName = _importName;
22
+ const ast = path.parentPath?.node || path.node;
23
+ const importNames = jsx ? [importName, JSXElement] : [importName];
21
24
 
22
- if (importName.includes(".")) {
23
- importName = importName.split(".")[0];
24
- }
25
-
26
- const localName = importName;
27
- let hasI18nImport = false;
28
- let lastImportPath = null;
29
- let conflictTDefined = false;
30
-
31
- path.traverse({
32
- // ImportDeclaration(path) {
33
- // lastImportPath = path;
34
- // if (
35
- // path.node.source.value === importPath &&
36
- // path.node.specifiers.some(
37
- // spec =>
38
- // t.isImportSpecifier(spec) && spec.imported.name === importName
39
- // )
40
- // ) {
41
- // hasI18nImport = true;
42
- // }
43
- // },
44
- ImportDeclaration(path) {
45
- lastImportPath = path;
46
-
47
- const sourcePath = path.node.source.value;
48
-
49
- // 判断是否是其它路径导入了 $t
50
- const importedTElsewhere = path.node.specifiers.some(spec => {
51
- return (
52
- (t.isImportSpecifier(spec) ||
53
- t.isImportDefaultSpecifier(spec)) &&
54
- spec.local.name === importName &&
55
- sourcePath !== importPath
56
- );
57
- });
58
-
59
- if (importedTElsewhere) {
60
- conflictTDefined = true;
61
- path.stop();
62
- return;
63
- }
64
-
65
- // 检查是否已经导入目标路径
66
- if (sourcePath === importPath) {
67
- hasI18nImport = true;
68
-
69
- // 情况1:已有 import { $t } from '@/i18n'
70
- const existingImport = path.node.specifiers.find(
71
- spec =>
72
- t.isImportSpecifier(spec) && spec.imported.name === importName
73
- );
74
-
75
- if (existingImport) return;
76
-
77
- // if (
78
- // existingImport &&
79
- // !t.isImportSpecifier(existingImport, {
80
- // local: { name: localName }
81
- // })
82
- // ) {
83
- // // 将 $t 改为 _$t
84
- // existingImport.local = t.identifier(localName);
85
- // } else
86
- // 情况2:已有 import i18n from '@/i18n'
87
-
88
- // 添加 $t 到已有的 import
89
- path.node.specifiers.push(
90
- t.importSpecifier(
91
- t.identifier(localName),
92
- t.identifier(importName)
93
- )
94
- );
95
- }
96
- },
97
- VariableDeclarator(path) {
98
- if (
99
- t.isIdentifier(path.node.id) &&
100
- path.node.id.name === importName
101
- ) {
102
- conflictTDefined = true;
103
- path.stop();
104
- }
105
- },
106
-
107
- FunctionDeclaration(path) {
108
- if (
109
- t.isIdentifier(path.node.id) &&
110
- path.node.id.name === importName
111
- ) {
112
- conflictTDefined = true;
113
- path.stop();
114
- }
115
- }
116
- });
117
-
118
- // 跳过导入语句的生成
119
- if (conflictTDefined) {
120
- return {
121
- ast,
122
- needTransform: false
123
- };
124
- }
125
-
126
- // 情况4:完全没有导入 @/i18n
127
- if (!hasI18nImport) {
128
- const importNode = t.importDeclaration(
129
- [
130
- // t.importDefaultSpecifier(t.identifier("i18n")),
131
- t.importSpecifier(
132
- t.identifier(localName),
133
- t.identifier(importName)
134
- )
135
- ],
136
- t.stringLiteral(importPath)
137
- );
138
-
139
- if (lastImportPath) {
140
- // 插入到最后一个 import 之后
141
- lastImportPath.insertAfter(importNode);
142
- } else {
143
- // 插入到文件顶部
144
- path.unshiftContainer("body", importNode);
145
- }
25
+ for (const importName of importNames) {
26
+ i18nImportAstTransform(ast, importName, importPath);
146
27
  }
147
28
  }
148
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,7 @@ 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
11
 
12
12
  if (importName.includes(".")) {
13
13
  importName = importName.split(".")[0];
@@ -19,63 +19,50 @@ function i18nImportAstTransform(ast, importName, importPath) {
19
19
 
20
20
  const sourcePath = path.node.source.value;
21
21
 
22
- // 判断是否是其它路径导入了 $t
23
- const importedTElsewhere = path.node.specifiers.some(spec => {
22
+ const importedElsewhere = path.node.specifiers.some(spec => {
24
23
  return (
25
24
  (t.isImportSpecifier(spec) || t.isImportDefaultSpecifier(spec)) &&
26
- spec.local.name === importName &&
27
- sourcePath !== importPath
25
+ spec.local.name === importName
28
26
  );
29
27
  });
30
28
 
31
- if (importedTElsewhere) {
32
- conflictTDefined = true;
29
+ if (importedElsewhere) {
30
+ conflictDefined = true;
33
31
  path.stop();
34
32
  return;
35
33
  }
36
34
 
37
- // 检查是否已经导入目标路径
38
35
  if (sourcePath === importPath) {
39
- hasI18nImport = true;
40
-
41
- // 情况1:已有 import { $t } from '@/i18n'
42
- const existImport = path.node.specifiers.some(
43
- spec => t.isImportSpecifier(spec) && spec.imported.name === importName
44
- );
45
-
46
- if (existImport) return;
47
-
48
- // 添加 $t 到已有的 import
49
36
  path.node.specifiers.push(
50
37
  t.importSpecifier(t.identifier(importName), t.identifier(importName))
51
38
  );
39
+
40
+ hasI18nImport = true;
52
41
  needTransform = true;
53
42
  }
54
43
  },
55
44
  VariableDeclarator(path) {
56
45
  if (t.isIdentifier(path.node.id) && path.node.id.name === importName) {
57
- conflictTDefined = true;
46
+ conflictDefined = true;
58
47
  path.stop();
59
48
  }
60
49
  },
61
50
 
62
51
  FunctionDeclaration(path) {
63
52
  if (t.isIdentifier(path.node.id) && path.node.id.name === importName) {
64
- conflictTDefined = true;
53
+ conflictDefined = true;
65
54
  path.stop();
66
55
  }
67
56
  }
68
57
  });
69
58
 
70
- // 跳过导入语句的生成
71
- if (conflictTDefined) {
59
+ if (conflictDefined) {
72
60
  return {
73
61
  ast,
74
62
  needTransform: false
75
63
  };
76
64
  }
77
65
 
78
- // 情况4:完全没有导入 @/i18n
79
66
  if (!hasI18nImport) {
80
67
  const importNode = t.importDeclaration(
81
68
  [
@@ -94,7 +81,6 @@ function i18nImportAstTransform(ast, importName, importPath) {
94
81
  } else {
95
82
  ast.program.body.unshift(importNode);
96
83
  }
97
- needTransform = true;
98
84
  }
99
85
 
100
86
  return {
@@ -103,7 +89,7 @@ function i18nImportAstTransform(ast, importName, importPath) {
103
89
  };
104
90
  }
105
91
 
106
- async function i18nImportTransform(code, path, importName, importPath) {
92
+ async function i18nImportTransform(code, path, importNames, importPath) {
107
93
  const scriptContent = extractScriptContent(code, path);
108
94
  if (!scriptContent) return code;
109
95
 
@@ -117,14 +103,19 @@ async function i18nImportTransform(code, path, importName, importPath) {
117
103
  ].filter(Boolean)
118
104
  });
119
105
 
120
- const { needTransform } = i18nImportAstTransform(
121
- ast,
122
- importName,
123
- importPath
124
- );
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
+ }
125
116
 
126
117
  // 只有当需要修改时才重新生成代码
127
- if (needTransform) {
118
+ if (transformNeeded) {
128
119
  const { code: newScript } = generate(ast);
129
120
  return path.endsWith(".vue")
130
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 模块
@@ -18,11 +19,11 @@ const defaultOptions = {
18
19
  includePath: ["src/"], // 包含路径的数组
19
20
  excludedPath: [], // 排除路径的数组
20
21
  allowedExtensions: [".vue", ".tsx", ".ts", ".jsx", ".js"], // 允许提取的文件扩展名
21
- generateId: null, // 自定义生成 key 的函数
22
22
  fromLang: "zh-cn", // 源语言, 目前支持提取的语言有:zh-cn(zh-tw), en, ja, ko, ru
23
23
  translateLangKeys: ["zh-tw", "en"], // 需要翻译为的语言键
24
24
  i18nPkgImportPath: "@/i18n", // i18n语言包导入路径
25
25
  outputPath: "src/i18n", // 提取的语言包输出文件路径
26
+ generateId: null, // 自定义生成 key 的函数
26
27
  customGenLangFileName: langKey => langKey, // 自定义生成语言文件名
27
28
  // 翻译后的文本处理函数,方便对翻译后的文本进行二次加工,如每个单词首字母大写, params: text: 翻译后的文本, toLang: 翻译后的目标语言,translateLangKeys的枚举成员
28
29
  customTranslatedText: (text, toLang) => text,
package/lib/visitors.js CHANGED
@@ -138,6 +138,20 @@ function generateId(rawText, options) {
138
138
  return _generateId(rawText);
139
139
  }
140
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
+
141
155
  function createI18nVisitor(option, i18nMap) {
142
156
  const excludedCall = [...option.excludedCall, ...EXCLUDED_CALL];
143
157
 
@@ -338,6 +352,16 @@ function createI18nVisitor(option, i18nMap) {
338
352
  i18nMap[hashed] = text;
339
353
  }
340
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
+
341
365
  // 替换为表达式 {$t("hashed")}
342
366
  path.replaceWith(
343
367
  t.jsxExpressionContainer(
@@ -370,6 +394,19 @@ function createI18nVisitor(option, i18nMap) {
370
394
  i18nMap[hashed] = value;
371
395
  }
372
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
+
373
410
  path.replaceWith(
374
411
  t.jsxExpressionContainer(
375
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.63",
3
+ "version": "1.0.65",
4
4
  "main": "lib/index.js",
5
5
  "types": "types/index.d.ts",
6
6
  "bin": {
@@ -14,7 +14,7 @@ export function i18nImportAstTransform(
14
14
  export function i18nImportTransform(
15
15
  code: string,
16
16
  path: string,
17
- importName: string,
17
+ importNames: string[],
18
18
  importPath: string
19
19
  ): Promise<string>;
20
20
 
@@ -1,12 +1,18 @@
1
+ import { Translator } from './translators';
2
+
1
3
  export type LangKey = "zh-cn" | 'zh-tw' | 'en' | 'ja' | 'ko' | 'ru' | string
2
4
 
3
5
  export interface I18nOptions {
4
6
  translateKey: string
7
+ JSXElement: string
8
+ jsx: boolean
5
9
  rewrite: boolean
6
10
  extractFromText: boolean
7
11
  autoImportI18n: boolean
8
12
  autoTranslate: boolean
9
13
  cleanTranslate: boolean
14
+ keepRaw: boolean
15
+ keepDefaultMsg: boolean
10
16
  enabled: boolean
11
17
  outputJsonFileInPlugin: boolean
12
18
  outputJsonFileDebounceTimeInPlugin: number
@@ -22,13 +28,5 @@ export interface I18nOptions {
22
28
  generateId: ((text: string) => string) | null | undefined
23
29
  customGenLangFileName: (langKey: LangKey) => LangKey
24
30
  customTranslatedText: (text: string, toLang: LangKey) => string,
25
- // translator: new GoogleTranslator({
26
- // // proxyOption: {
27
- // // port: 7890,
28
- // // host: '127.0.0.1',
29
- // // headers: {
30
- // // 'User-Agent': 'Node'
31
- // // }
32
- // // }
33
- // })
31
+ translator: Translator
34
32
  };
@@ -26,6 +26,7 @@ interface TranslatorOption {
26
26
  */
27
27
  onError?: (err: unknown, defaultErrorHandler: (error: unknown) => void) => void;
28
28
  }
29
+
29
30
  declare class Translator {
30
31
  option: Required<TranslatorOption>;
31
32
  constructor(option: TranslatorOption);