vue-i18n-extract-plugin 1.0.54 → 1.0.56
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/index.js +2 -0
- package/lib/options.js +2 -0
- package/lib/utils.js +16 -4
- package/lib/visitors.js +105 -6
- package/package.json +1 -1
- package/types/index.d.ts +1 -0
- package/types/options.d.ts +1 -0
- package/types/utils.d.ts +1 -0
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ extractI18n(options)
|
|
|
48
48
|
```javascript
|
|
49
49
|
const defaultOptions = {
|
|
50
50
|
translateKey: "$t", // 提取的函数的名称
|
|
51
|
+
JSXElement: "Trans", // 提取的函数的 JSX 元素名称 默认为 Trans, 如:<Trans id="aaa" defaultMsg="xxx" />
|
|
51
52
|
rewrite: false, // 是否将提取到的内容转换为id后重写入源文件
|
|
52
53
|
extractFromText: true, // 是否允许从纯文本节点中提取翻译内容
|
|
53
54
|
autoImportI18n: true, // 是否自动导入 i18n 模块
|
|
@@ -62,6 +63,7 @@ const defaultOptions = {
|
|
|
62
63
|
includePath: ['src/'], // 包含路径的数组
|
|
63
64
|
excludedPath: [], // 排除路径的数组 refer to https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#how-to-exclude-directory-from-reading
|
|
64
65
|
allowedExtensions: [".vue", ".tsx", ".ts", ".jsx", ".js"], // 允许提取的文件扩展名
|
|
66
|
+
generateId: null, // 自定义生成 key 的函数
|
|
65
67
|
fromLang: 'zh-cn', // 源语言, 目前支持提取的语言有:zh-cn(zh-tw), en, ja, ko, ru
|
|
66
68
|
translateLangKeys: ["zh-tw", "en"], // 需要翻译为的语言键
|
|
67
69
|
i18nPkgImportPath: "@/i18n", // i18n语言包导入路径
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
relativeCWDPath,
|
|
17
17
|
getLangJsonPath,
|
|
18
18
|
shouldExtract,
|
|
19
|
+
registerLangMatch,
|
|
19
20
|
trimEmptyLine,
|
|
20
21
|
padEmptyLine,
|
|
21
22
|
excludeDirectives,
|
|
@@ -58,6 +59,7 @@ module.exports = {
|
|
|
58
59
|
extractFunctionName,
|
|
59
60
|
relativeCWDPath,
|
|
60
61
|
shouldExtract,
|
|
62
|
+
registerLangMatch,
|
|
61
63
|
trimEmptyLine,
|
|
62
64
|
padEmptyLine,
|
|
63
65
|
excludeDirectives,
|
package/lib/options.js
CHANGED
|
@@ -2,6 +2,7 @@ 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
6
|
rewrite: false, // 是否将提取到的内容转换为id后重写入源文件
|
|
6
7
|
extractFromText: true, // 是否允许从纯文本节点中提取翻译内容
|
|
7
8
|
autoImportI18n: true, // 是否自动导入 i18n 模块
|
|
@@ -16,6 +17,7 @@ const defaultOptions = {
|
|
|
16
17
|
includePath: ["src/"], // 包含路径的数组
|
|
17
18
|
excludedPath: [], // 排除路径的数组
|
|
18
19
|
allowedExtensions: [".vue", ".tsx", ".ts", ".jsx", ".js"], // 允许提取的文件扩展名
|
|
20
|
+
generateId: null, // 自定义生成 key 的函数
|
|
19
21
|
fromLang: "zh-cn", // 源语言, 目前支持提取的语言有:zh-cn(zh-tw), en, ja, ko, ru
|
|
20
22
|
translateLangKeys: ["zh-tw", "en"], // 需要翻译为的语言键
|
|
21
23
|
i18nPkgImportPath: "@/i18n", // i18n语言包导入路径
|
package/lib/utils.js
CHANGED
|
@@ -157,10 +157,18 @@ function sleep(ms) {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
function shouldExtract(str, langKey) {
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
const regex = REGEX_MAP[langKey] || REGEX_MAP[translateLangKeyEnum.ZH];
|
|
161
|
+
if (regex instanceof RegExp) {
|
|
162
|
+
return regex.test(str);
|
|
162
163
|
}
|
|
163
|
-
|
|
164
|
+
if (typeof regex === "function") {
|
|
165
|
+
return regex(str);
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function registerLangMatch(langKey, regex) {
|
|
171
|
+
REGEX_MAP[langKey] = regex;
|
|
164
172
|
}
|
|
165
173
|
|
|
166
174
|
function trimEmptyLine(str) {
|
|
@@ -178,13 +186,15 @@ const translateLangKeyEnum = {
|
|
|
178
186
|
KO: "ko",
|
|
179
187
|
RU: "ru"
|
|
180
188
|
};
|
|
189
|
+
|
|
181
190
|
const REGEX_MAP = {
|
|
182
|
-
[translateLangKeyEnum.ZH]: /[\u4e00-\u9fff]/,
|
|
191
|
+
[translateLangKeyEnum.ZH]: /[\u4e00-\u9fff]/, // 简中/繁中
|
|
183
192
|
[translateLangKeyEnum.EN]: /[a-zA-Z]/,
|
|
184
193
|
[translateLangKeyEnum.JA]: /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]/, // 日语假名和汉字
|
|
185
194
|
[translateLangKeyEnum.KO]: /[\uAC00-\uD7A3]/, // 韩语字母
|
|
186
195
|
[translateLangKeyEnum.RU]: /[йцукенгшщзхъфывапролджэячсмитьбюё .-]{1,}/ // 俄语字母
|
|
187
196
|
};
|
|
197
|
+
|
|
188
198
|
const excludeDirectives = [
|
|
189
199
|
"model",
|
|
190
200
|
"slot",
|
|
@@ -195,6 +205,7 @@ const excludeDirectives = [
|
|
|
195
205
|
"once",
|
|
196
206
|
"memo"
|
|
197
207
|
];
|
|
208
|
+
|
|
198
209
|
const EXCLUDED_CALL = [
|
|
199
210
|
"$deepScan",
|
|
200
211
|
"console.log",
|
|
@@ -227,6 +238,7 @@ module.exports = {
|
|
|
227
238
|
fixFolderPath,
|
|
228
239
|
sleep,
|
|
229
240
|
shouldExtract,
|
|
241
|
+
registerLangMatch,
|
|
230
242
|
trimEmptyLine,
|
|
231
243
|
padEmptyLine,
|
|
232
244
|
excludeDirectives,
|
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
|
|
@@ -28,6 +28,22 @@ function isVNodeCall(path, nodeName) {
|
|
|
28
28
|
);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function isJSXElement(path, nodeName) {
|
|
32
|
+
const jsxElement = path.findParent(p => p.isJSXOpeningElement());
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
jsxElement &&
|
|
36
|
+
t.isJSXIdentifier(jsxElement.node.name, { name: nodeName })
|
|
37
|
+
) {
|
|
38
|
+
const jsxAttr = path.findParent(p => p.isJSXAttribute());
|
|
39
|
+
const attrName = jsxAttr.node.name.name;
|
|
40
|
+
if (attrName === "defaultMsg" || attrName === "values") {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
function getPropKey(propNode) {
|
|
32
48
|
if (t.isIdentifier(propNode.key)) {
|
|
33
49
|
return propNode.key.name;
|
|
@@ -113,6 +129,13 @@ function generateText(rawText, hashedText, options) {
|
|
|
113
129
|
return hashedText;
|
|
114
130
|
}
|
|
115
131
|
|
|
132
|
+
function generateId(rawText, options) {
|
|
133
|
+
if (typeof options.generateId === "function") {
|
|
134
|
+
return options.generateId(rawText);
|
|
135
|
+
}
|
|
136
|
+
return _generateId(rawText);
|
|
137
|
+
}
|
|
138
|
+
|
|
116
139
|
function createI18nVisitor(option, i18nMap) {
|
|
117
140
|
const excludedCall = [...option.excludedCall, ...EXCLUDED_CALL];
|
|
118
141
|
|
|
@@ -137,7 +160,7 @@ function createI18nVisitor(option, i18nMap) {
|
|
|
137
160
|
return;
|
|
138
161
|
}
|
|
139
162
|
|
|
140
|
-
const hashed = generateId(keyText);
|
|
163
|
+
const hashed = generateId(keyText, option);
|
|
141
164
|
|
|
142
165
|
if (i18nMap) {
|
|
143
166
|
i18nMap[hashed] = keyText;
|
|
@@ -158,6 +181,11 @@ function createI18nVisitor(option, i18nMap) {
|
|
|
158
181
|
return;
|
|
159
182
|
}
|
|
160
183
|
|
|
184
|
+
// 跳过<Trans defaultMsg="你好,{name}" values={{name: '世界'} /> defaultMsg和values属性的转换
|
|
185
|
+
if (isJSXElement(path, option.JSXElement)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
161
189
|
// 获取真实调用函数
|
|
162
190
|
const extractFnName = extractFunctionName(path);
|
|
163
191
|
|
|
@@ -187,7 +215,7 @@ function createI18nVisitor(option, i18nMap) {
|
|
|
187
215
|
return;
|
|
188
216
|
}
|
|
189
217
|
|
|
190
|
-
const hashed = generateId(value);
|
|
218
|
+
const hashed = generateId(value, option);
|
|
191
219
|
|
|
192
220
|
if (i18nMap) {
|
|
193
221
|
i18nMap[hashed] = value;
|
|
@@ -267,7 +295,7 @@ function createI18nVisitor(option, i18nMap) {
|
|
|
267
295
|
|
|
268
296
|
if (option.extractFromText === false) return;
|
|
269
297
|
|
|
270
|
-
const hashed = generateId(value);
|
|
298
|
+
const hashed = generateId(value, option);
|
|
271
299
|
|
|
272
300
|
if (i18nMap) {
|
|
273
301
|
i18nMap[hashed] = value;
|
|
@@ -288,7 +316,7 @@ function createI18nVisitor(option, i18nMap) {
|
|
|
288
316
|
return;
|
|
289
317
|
}
|
|
290
318
|
|
|
291
|
-
const hashed = generateId(text);
|
|
319
|
+
const hashed = generateId(text, option);
|
|
292
320
|
|
|
293
321
|
if (i18nMap) {
|
|
294
322
|
i18nMap[hashed] = text;
|
|
@@ -316,7 +344,7 @@ function createI18nVisitor(option, i18nMap) {
|
|
|
316
344
|
return;
|
|
317
345
|
}
|
|
318
346
|
|
|
319
|
-
const hashed = generateId(value);
|
|
347
|
+
const hashed = generateId(value, option);
|
|
320
348
|
|
|
321
349
|
if (i18nMap) {
|
|
322
350
|
i18nMap[hashed] = value;
|
|
@@ -330,6 +358,77 @@ function createI18nVisitor(option, i18nMap) {
|
|
|
330
358
|
)
|
|
331
359
|
);
|
|
332
360
|
}
|
|
361
|
+
},
|
|
362
|
+
JSXElement(path) {
|
|
363
|
+
// <Trans id="aaa" defaultMsg="xxx" />
|
|
364
|
+
const openingElement = path.node.openingElement;
|
|
365
|
+
if (
|
|
366
|
+
!t.isJSXIdentifier(openingElement.name) ||
|
|
367
|
+
openingElement.name.name !== option.JSXElement
|
|
368
|
+
) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let idAttr = null;
|
|
373
|
+
let idValue = null;
|
|
374
|
+
let defaultMsgValue = null;
|
|
375
|
+
|
|
376
|
+
// 遍历属性,查找 id 和 defaultMsg
|
|
377
|
+
openingElement.attributes.forEach(attr => {
|
|
378
|
+
if (!t.isJSXAttribute(attr) || !t.isJSXIdentifier(attr.name)) return;
|
|
379
|
+
|
|
380
|
+
if (attr.name.name === "id") {
|
|
381
|
+
idAttr = attr;
|
|
382
|
+
if (attr.value && t.isStringLiteral(attr.value)) {
|
|
383
|
+
idValue = attr.value.value;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (attr.name.name === "defaultMsg") {
|
|
388
|
+
if (attr.value && t.isStringLiteral(attr.value)) {
|
|
389
|
+
defaultMsgValue = attr.value.value;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// 计算 id,如果未提供,则使用 defaultMsg 的哈希值
|
|
395
|
+
if (!idValue && defaultMsgValue) {
|
|
396
|
+
idValue = generateId(defaultMsgValue, option);
|
|
397
|
+
|
|
398
|
+
if (i18nMap) {
|
|
399
|
+
i18nMap[idValue] = defaultMsgValue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 有id并且有defaultMsg的情况
|
|
404
|
+
if (idValue && defaultMsgValue) {
|
|
405
|
+
if (i18nMap) {
|
|
406
|
+
i18nMap[idValue] = defaultMsgValue;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (idValue) {
|
|
411
|
+
// 添加或更新 id 属性
|
|
412
|
+
if (idAttr) {
|
|
413
|
+
idAttr.value = t.stringLiteral(idValue);
|
|
414
|
+
} else {
|
|
415
|
+
openingElement.attributes.push(
|
|
416
|
+
t.jsxAttribute(t.jsxIdentifier("id"), t.stringLiteral(idValue))
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (!option.keepRaw) {
|
|
422
|
+
// 移除 defaultMsg
|
|
423
|
+
openingElement.attributes = openingElement.attributes.filter(
|
|
424
|
+
attr =>
|
|
425
|
+
!(
|
|
426
|
+
t.isJSXAttribute(attr) &&
|
|
427
|
+
t.isJSXIdentifier(attr.name) &&
|
|
428
|
+
attr.name.name === "defaultMsg"
|
|
429
|
+
)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
333
432
|
}
|
|
334
433
|
};
|
|
335
434
|
}
|
package/package.json
CHANGED
package/types/index.d.ts
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({
|
package/types/utils.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export function resolveFilterPath(pathStr: string): string;
|
|
|
15
15
|
export function fixFolderPath(pathStr: string | RegExp): string;
|
|
16
16
|
export function sleep(ms: number): Promise<void>;
|
|
17
17
|
export function shouldExtract(str: string, langKey: LangKey): boolean;
|
|
18
|
+
export function registerLangMatch(langKey: LangKey, regex: RegExp | ((str: string) => boolean)): void;
|
|
18
19
|
export function trimEmptyLine(str: string): string;
|
|
19
20
|
export function padEmptyLine(str: string): string;
|
|
20
21
|
export const excludeDirectives: string[];
|