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 +2 -0
- package/lib/options.js +2 -0
- package/lib/visitors.js +48 -19
- package/lib/vite-plugin-i18n.js +56 -32
- package/lib/vite-plugin-import-i18n.js +12 -33
- package/package.json +1 -1
- package/types/options.d.ts +1 -0
- package/types/vite-plugin-i18n.d.ts +2 -0
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
|
-
[
|
|
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(
|
|
250
|
-
t.
|
|
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 =
|
|
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(
|
|
322
|
-
t.
|
|
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(
|
|
349
|
-
t.
|
|
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 =>
|
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
package/types/options.d.ts
CHANGED
|
@@ -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({
|