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 +3 -2
- package/lib/options.js +1 -0
- package/lib/visitors.js +50 -28
- package/lib/vite-plugin-i18n.js +56 -32
- package/lib/vite-plugin-import-i18n.js +12 -33
- package/package.json +1 -1
- package/types/vite-plugin-i18n.d.ts +2 -0
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 === "
|
|
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
|
|
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
|
-
[
|
|
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(
|
|
257
|
-
t.
|
|
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 =
|
|
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(
|
|
329
|
-
t.
|
|
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(
|
|
356
|
-
t.
|
|
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"
|
|
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
|
|
396
|
+
let msgValue = null;
|
|
375
397
|
|
|
376
|
-
// 遍历属性,查找 id 和
|
|
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 === "
|
|
409
|
+
if (attr.name.name === "msg") {
|
|
388
410
|
if (attr.value && t.isStringLiteral(attr.value)) {
|
|
389
|
-
|
|
411
|
+
msgValue = attr.value.value;
|
|
390
412
|
}
|
|
391
413
|
}
|
|
392
414
|
});
|
|
393
415
|
|
|
394
|
-
// 计算 id,如果未提供,则使用
|
|
395
|
-
if (!idValue &&
|
|
396
|
-
idValue = generateId(
|
|
416
|
+
// 计算 id,如果未提供,则使用 msg 的哈希值
|
|
417
|
+
if (!idValue && msgValue) {
|
|
418
|
+
idValue = generateId(msgValue, option);
|
|
397
419
|
|
|
398
420
|
if (i18nMap) {
|
|
399
|
-
i18nMap[idValue] =
|
|
421
|
+
i18nMap[idValue] = msgValue;
|
|
400
422
|
}
|
|
401
423
|
}
|
|
402
424
|
|
|
403
|
-
// 有id并且有
|
|
404
|
-
if (idValue &&
|
|
425
|
+
// 有id并且有msg的情况
|
|
426
|
+
if (idValue && msgValue) {
|
|
405
427
|
if (i18nMap) {
|
|
406
|
-
i18nMap[idValue] =
|
|
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
|
-
// 移除
|
|
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 === "
|
|
450
|
+
attr.name.name === "msg"
|
|
429
451
|
)
|
|
430
452
|
);
|
|
431
453
|
}
|
package/lib/vite-plugin-i18n.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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(
|
|
7
|
-
|
|
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 =
|
|
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
|
-
|
|
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