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 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
- if (REGEX_MAP[langKey]) {
161
- return REGEX_MAP[langKey].test(str);
160
+ const regex = REGEX_MAP[langKey] || REGEX_MAP[translateLangKeyEnum.ZH];
161
+ if (regex instanceof RegExp) {
162
+ return regex.test(str);
162
163
  }
163
- return REGEX_MAP[translateLangKeyEnum.ZH].test(str);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-i18n-extract-plugin",
3
- "version": "1.0.54",
3
+ "version": "1.0.56",
4
4
  "main": "lib/index.js",
5
5
  "types": "types/index.d.ts",
6
6
  "bin": {
package/types/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export {
16
16
  relativeCWDPath,
17
17
  getLangJsonPath,
18
18
  shouldExtract,
19
+ registerLangMatch,
19
20
  trimEmptyLine,
20
21
  padEmptyLine,
21
22
  excludeDirectives,
@@ -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[];