@agentscope-ai/i18n 0.1.9-rc.0 → 0.1.9-rc.2
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/lib/cli.js +5 -0
- package/lib/core/translator.js +240 -33
- package/lib/parse-jsx.js +7 -81
- package/lib/utils/ast-utils.js +66 -0
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -161,10 +161,15 @@ withCommonOptions(
|
|
|
161
161
|
|
|
162
162
|
withCommonOptions(program.command('check').description('Check translation status'))
|
|
163
163
|
.option('--auto-delete-unused', 'Automatically delete unused translation keys')
|
|
164
|
+
.option(
|
|
165
|
+
'--summary-only',
|
|
166
|
+
'Only output missing key counts per language, skip deletion and file generation',
|
|
167
|
+
)
|
|
164
168
|
.action(async (options) => {
|
|
165
169
|
try {
|
|
166
170
|
const config = getConfig(options);
|
|
167
171
|
config.autoDeleteUnused = options.autoDeleteUnused;
|
|
172
|
+
config.summaryOnly = options.summaryOnly;
|
|
168
173
|
const targetPath = resolveCommonOptions(options, config);
|
|
169
174
|
await check(targetPath, config);
|
|
170
175
|
} catch (error) {
|
package/lib/core/translator.js
CHANGED
|
@@ -10,8 +10,21 @@ const {
|
|
|
10
10
|
translateAndUpdateFiles,
|
|
11
11
|
singleTranslateAndUpdateFiles,
|
|
12
12
|
} = require('./file-processor');
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
extractI18nKey,
|
|
15
|
+
chnRegExp,
|
|
16
|
+
onlyContainsChinesePunctuation,
|
|
17
|
+
onlyContainsEmoji,
|
|
18
|
+
isI18nCall,
|
|
19
|
+
isInConsoleCall,
|
|
20
|
+
} = require('../utils/ast-utils');
|
|
14
21
|
const { askQuestion } = require('../utils/cli-utils');
|
|
22
|
+
const {
|
|
23
|
+
shouldIgnoreLineByComment,
|
|
24
|
+
shouldIgnoreBlockByComment,
|
|
25
|
+
shouldIgnoreByComment,
|
|
26
|
+
shouldIgnoreFile,
|
|
27
|
+
} = require('../utils/file-utils');
|
|
15
28
|
const { generateMedusaExcel } = require('../utils/excel-utils');
|
|
16
29
|
// inquirer需要动态导入,因为它是ES模块
|
|
17
30
|
const t = require('@babel/types');
|
|
@@ -310,8 +323,115 @@ const scanFilesAndCollectKeys = (files) => {
|
|
|
310
323
|
return { usedKeys, usedKeysData };
|
|
311
324
|
};
|
|
312
325
|
|
|
326
|
+
// 扫描未翻译的中文文案(未被 $i18n.get 包裹的中文字符串)
|
|
327
|
+
const scanUntranslatedTexts = (files, doNotTranslateFiles) => {
|
|
328
|
+
const untranslatedTexts = [];
|
|
329
|
+
|
|
330
|
+
const filteredFiles = files.filter((file) => {
|
|
331
|
+
if (shouldIgnoreFile(file, doNotTranslateFiles || [])) return false;
|
|
332
|
+
if (shouldIgnoreByComment(file)) return false;
|
|
333
|
+
return true;
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
filteredFiles.forEach((file) => {
|
|
337
|
+
try {
|
|
338
|
+
const code = fs.readFileSync(file, 'utf8');
|
|
339
|
+
const ast = parseAST(code);
|
|
340
|
+
|
|
341
|
+
const shouldIgnoreNode = (nodePath) => {
|
|
342
|
+
if (!nodePath.node.loc) return false;
|
|
343
|
+
const lineNumber = nodePath.node.loc.start.line;
|
|
344
|
+
return (
|
|
345
|
+
shouldIgnoreLineByComment(file, lineNumber) ||
|
|
346
|
+
shouldIgnoreBlockByComment(file, lineNumber)
|
|
347
|
+
);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
traverse(ast, {
|
|
351
|
+
StringLiteral(astPath) {
|
|
352
|
+
const { value } = astPath.node;
|
|
353
|
+
if (
|
|
354
|
+
chnRegExp.test(value) &&
|
|
355
|
+
!isI18nCall(astPath.parent) &&
|
|
356
|
+
!astPath.findParent((p) => isI18nCall(p.node)) &&
|
|
357
|
+
!isInConsoleCall(astPath) &&
|
|
358
|
+
!shouldIgnoreNode(astPath) &&
|
|
359
|
+
!onlyContainsChinesePunctuation(value) &&
|
|
360
|
+
!onlyContainsEmoji(value)
|
|
361
|
+
) {
|
|
362
|
+
untranslatedTexts.push({
|
|
363
|
+
file,
|
|
364
|
+
line: astPath.node.loc?.start.line,
|
|
365
|
+
column: astPath.node.loc?.start.column,
|
|
366
|
+
type: 'StringLiteral',
|
|
367
|
+
text: value,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
TemplateLiteral(astPath) {
|
|
372
|
+
if (
|
|
373
|
+
!isI18nCall(astPath.parent) &&
|
|
374
|
+
!astPath.findParent((p) => isI18nCall(p.node)) &&
|
|
375
|
+
!isInConsoleCall(astPath) &&
|
|
376
|
+
!shouldIgnoreNode(astPath)
|
|
377
|
+
) {
|
|
378
|
+
const staticParts = astPath.node.quasis.map((q) => q.value.raw).join('');
|
|
379
|
+
if (
|
|
380
|
+
chnRegExp.test(staticParts) &&
|
|
381
|
+
!onlyContainsChinesePunctuation(staticParts) &&
|
|
382
|
+
!onlyContainsEmoji(staticParts)
|
|
383
|
+
) {
|
|
384
|
+
const fullText = astPath.node.quasis
|
|
385
|
+
.map((q, i) => {
|
|
386
|
+
const expr = astPath.node.expressions[i];
|
|
387
|
+
const exprStr = expr ? `\${${generate(expr, { compact: true }).code}}` : '';
|
|
388
|
+
return q.value.raw + exprStr;
|
|
389
|
+
})
|
|
390
|
+
.join('');
|
|
391
|
+
untranslatedTexts.push({
|
|
392
|
+
file,
|
|
393
|
+
line: astPath.node.loc?.start.line,
|
|
394
|
+
column: astPath.node.loc?.start.column,
|
|
395
|
+
type: 'TemplateLiteral',
|
|
396
|
+
text: fullText,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
JSXText(astPath) {
|
|
402
|
+
const { value } = astPath.node;
|
|
403
|
+
if (
|
|
404
|
+
chnRegExp.test(value) &&
|
|
405
|
+
!isI18nCall(astPath.parent) &&
|
|
406
|
+
!astPath.findParent((p) => isI18nCall(p.node)) &&
|
|
407
|
+
!isInConsoleCall(astPath) &&
|
|
408
|
+
!shouldIgnoreNode(astPath) &&
|
|
409
|
+
!onlyContainsChinesePunctuation(value) &&
|
|
410
|
+
!onlyContainsEmoji(value)
|
|
411
|
+
) {
|
|
412
|
+
const trimmedValue = value.replace(/^\s+|\s+$/g, '');
|
|
413
|
+
if (trimmedValue) {
|
|
414
|
+
untranslatedTexts.push({
|
|
415
|
+
file,
|
|
416
|
+
line: astPath.node.loc?.start.line,
|
|
417
|
+
column: astPath.node.loc?.start.column,
|
|
418
|
+
type: 'JSXText',
|
|
419
|
+
text: trimmedValue,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
} catch (error) {
|
|
426
|
+
console.error(` # 扫描未翻译文案失败: ${file}`, error.message);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
return untranslatedTexts;
|
|
431
|
+
};
|
|
432
|
+
|
|
313
433
|
// 读取翻译文件
|
|
314
|
-
const readTranslationFiles = (localesFilePath, dashScope) => {
|
|
434
|
+
const readTranslationFiles = (localesFilePath, dashScope, checkLanguages) => {
|
|
315
435
|
// 读取中文翻译文件
|
|
316
436
|
let zhCN = {};
|
|
317
437
|
try {
|
|
@@ -320,25 +440,26 @@ const readTranslationFiles = (localesFilePath, dashScope) => {
|
|
|
320
440
|
console.log(' 未找到zh-cn.json文件');
|
|
321
441
|
}
|
|
322
442
|
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
|
|
443
|
+
// 确定需要检查的语言列表,优先级:check.languages > dashScope.valueTranslateAppId
|
|
444
|
+
let languages = [];
|
|
445
|
+
|
|
446
|
+
if (Array.isArray(checkLanguages) && checkLanguages.length > 0) {
|
|
447
|
+
// 从 check.languages 配置中获取,排除 zh-cn(已单独处理)
|
|
448
|
+
languages = checkLanguages.filter((lang) => lang !== 'zh-cn');
|
|
449
|
+
} else if (dashScope && dashScope.valueTranslateAppId) {
|
|
326
450
|
if (typeof dashScope.valueTranslateAppId === 'string') {
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
languageFiles['en-us'] = fs.readJsonSync(`${localesFilePath}/en-us.json`);
|
|
330
|
-
} catch (error) {
|
|
331
|
-
console.log(' 未找到en-us.json文件');
|
|
332
|
-
}
|
|
451
|
+
languages = ['en-us'];
|
|
333
452
|
} else if (typeof dashScope.valueTranslateAppId === 'object') {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
453
|
+
languages = Object.keys(dashScope.valueTranslateAppId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const languageFiles = {};
|
|
458
|
+
for (const language of languages) {
|
|
459
|
+
try {
|
|
460
|
+
languageFiles[language] = fs.readJsonSync(`${localesFilePath}/${language}.json`);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.log(` 未找到${language}.json文件`);
|
|
342
463
|
}
|
|
343
464
|
}
|
|
344
465
|
|
|
@@ -375,7 +496,7 @@ const analyzeMissingAndUnusedKeys = (usedKeys, zhCN, languageFiles) => {
|
|
|
375
496
|
};
|
|
376
497
|
|
|
377
498
|
// 生成检查结果输出
|
|
378
|
-
const generateCheckOutput = (missingKeys, unusedKeys) => {
|
|
499
|
+
const generateCheckOutput = (missingKeys, unusedKeys, untranslatedTexts) => {
|
|
379
500
|
const outputLines = [];
|
|
380
501
|
outputLines.push('=== 检查结果 ===');
|
|
381
502
|
outputLines.push('');
|
|
@@ -391,7 +512,7 @@ const generateCheckOutput = (missingKeys, unusedKeys) => {
|
|
|
391
512
|
}
|
|
392
513
|
|
|
393
514
|
for (const [language, missing] of Object.entries(missingKeys)) {
|
|
394
|
-
if (language === 'zhCN') continue;
|
|
515
|
+
if (language === 'zhCN') continue;
|
|
395
516
|
|
|
396
517
|
if (missing.length > 0) {
|
|
397
518
|
outputLines.push('');
|
|
@@ -416,7 +537,7 @@ const generateCheckOutput = (missingKeys, unusedKeys) => {
|
|
|
416
537
|
}
|
|
417
538
|
|
|
418
539
|
for (const [language, unused] of Object.entries(unusedKeys)) {
|
|
419
|
-
if (language === 'zhCN') continue;
|
|
540
|
+
if (language === 'zhCN') continue;
|
|
420
541
|
|
|
421
542
|
if (unused.length > 0) {
|
|
422
543
|
outputLines.push('');
|
|
@@ -428,6 +549,20 @@ const generateCheckOutput = (missingKeys, unusedKeys) => {
|
|
|
428
549
|
}
|
|
429
550
|
}
|
|
430
551
|
|
|
552
|
+
outputLines.push('');
|
|
553
|
+
outputLines.push('3. 未翻译的中文文案:');
|
|
554
|
+
|
|
555
|
+
if (untranslatedTexts.length > 0) {
|
|
556
|
+
outputLines.push('');
|
|
557
|
+
outputLines.push(`共发现 ${untranslatedTexts.length} 条未翻译的中文文案:`);
|
|
558
|
+
untranslatedTexts.forEach((item) => {
|
|
559
|
+
outputLines.push(` ${item.file}:${item.line}:${item.column} "${item.text}"`);
|
|
560
|
+
});
|
|
561
|
+
} else {
|
|
562
|
+
outputLines.push('');
|
|
563
|
+
outputLines.push('没有发现未翻译的中文文案');
|
|
564
|
+
}
|
|
565
|
+
|
|
431
566
|
outputLines.push('');
|
|
432
567
|
outputLines.push('=== 检查完成 ===');
|
|
433
568
|
|
|
@@ -435,7 +570,13 @@ const generateCheckOutput = (missingKeys, unusedKeys) => {
|
|
|
435
570
|
};
|
|
436
571
|
|
|
437
572
|
// 处理输出和JSON文件生成
|
|
438
|
-
const handleOutputAndJsonGeneration = (
|
|
573
|
+
const handleOutputAndJsonGeneration = (
|
|
574
|
+
outputLines,
|
|
575
|
+
missingKeys,
|
|
576
|
+
unusedKeys,
|
|
577
|
+
untranslatedTexts,
|
|
578
|
+
autoDeleteUnused,
|
|
579
|
+
) => {
|
|
439
580
|
// 判断是否需要生成JSON文件(自动删除模式下不生成日志文件)
|
|
440
581
|
if (outputLines.length > 100 && !autoDeleteUnused) {
|
|
441
582
|
// 生成JSON文件
|
|
@@ -447,10 +588,12 @@ const handleOutputAndJsonGeneration = (outputLines, missingKeys, unusedKeys, aut
|
|
|
447
588
|
summary: {
|
|
448
589
|
totalMissingKeys: Object.values(missingKeys).reduce((sum, keys) => sum + keys.length, 0),
|
|
449
590
|
totalUnusedKeys: Object.values(unusedKeys).reduce((sum, keys) => sum + keys.length, 0),
|
|
591
|
+
totalUntranslatedTexts: untranslatedTexts.length,
|
|
450
592
|
totalOutputLines: outputLines.length,
|
|
451
593
|
},
|
|
452
594
|
missingKeys,
|
|
453
595
|
unusedKeys,
|
|
596
|
+
untranslatedTexts,
|
|
454
597
|
};
|
|
455
598
|
|
|
456
599
|
try {
|
|
@@ -462,6 +605,7 @@ const handleOutputAndJsonGeneration = (outputLines, missingKeys, unusedKeys, aut
|
|
|
462
605
|
console.log('\n📊 概要信息:');
|
|
463
606
|
console.log(` • 缺失的翻译key总数: ${jsonResult.summary.totalMissingKeys}`);
|
|
464
607
|
console.log(` • 未使用的翻译key总数: ${jsonResult.summary.totalUnusedKeys}`);
|
|
608
|
+
console.log(` • 未翻译的中文文案总数: ${jsonResult.summary.totalUntranslatedTexts}`);
|
|
465
609
|
|
|
466
610
|
// 显示各语言的统计
|
|
467
611
|
console.log('\n📋 各语言统计:');
|
|
@@ -477,10 +621,13 @@ const handleOutputAndJsonGeneration = (outputLines, missingKeys, unusedKeys, aut
|
|
|
477
621
|
console.log(` ${displayLang}.json: ${unused.length}个`);
|
|
478
622
|
}
|
|
479
623
|
|
|
624
|
+
if (untranslatedTexts.length > 0) {
|
|
625
|
+
console.log(` 未翻译的中文文案: ${untranslatedTexts.length}条`);
|
|
626
|
+
}
|
|
627
|
+
|
|
480
628
|
console.log('\n=== 检查完成 ===\n');
|
|
481
629
|
} catch (error) {
|
|
482
630
|
console.error(`\n❌ 生成JSON文件失败: ${error.message}`);
|
|
483
|
-
// 如果JSON文件生成失败,仍然输出完整结果(自动删除模式下不输出)
|
|
484
631
|
if (!autoDeleteUnused) {
|
|
485
632
|
outputLines.forEach((line) => console.log(line));
|
|
486
633
|
}
|
|
@@ -708,8 +855,13 @@ const check = async function (sourcePath, params) {
|
|
|
708
855
|
dashScope,
|
|
709
856
|
medusa,
|
|
710
857
|
autoDeleteUnused,
|
|
858
|
+
summaryOnly,
|
|
859
|
+
doNotTranslateFiles,
|
|
860
|
+
check: checkConfig,
|
|
711
861
|
} = params;
|
|
712
862
|
|
|
863
|
+
const checkLanguages = checkConfig?.languages;
|
|
864
|
+
|
|
713
865
|
// 扫描文件
|
|
714
866
|
const fileTypes = fileType.split(',').map((s) => s.trim());
|
|
715
867
|
const re = new RegExp(`(${fileTypes.map((ft) => ft.replace('.', '\\.')).join('|')})$`);
|
|
@@ -720,18 +872,74 @@ const check = async function (sourcePath, params) {
|
|
|
720
872
|
const { usedKeys, usedKeysData } = scanFilesAndCollectKeys(files);
|
|
721
873
|
|
|
722
874
|
// 读取翻译文件
|
|
723
|
-
const { zhCN, languageFiles } = readTranslationFiles(localesFilePath, dashScope);
|
|
875
|
+
const { zhCN, languageFiles } = readTranslationFiles(localesFilePath, dashScope, checkLanguages);
|
|
724
876
|
|
|
725
877
|
// 分析缺失和未使用的key
|
|
726
878
|
const { missingKeys, unusedKeys } = analyzeMissingAndUnusedKeys(usedKeys, zhCN, languageFiles);
|
|
727
879
|
|
|
728
|
-
//
|
|
729
|
-
|
|
880
|
+
// 扫描未翻译的中文文案
|
|
881
|
+
console.log(` # 扫描未翻译的中文文案...`);
|
|
882
|
+
const untranslatedTexts = scanUntranslatedTexts(files, doNotTranslateFiles);
|
|
883
|
+
if (untranslatedTexts.length > 0) {
|
|
884
|
+
console.log(` # 发现 ${untranslatedTexts.length} 条未翻译的中文文案`);
|
|
885
|
+
} else {
|
|
886
|
+
console.log(` # 未发现未翻译的中文文案`);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// summaryOnly 模式:只输出各语言缺失的key数量,不做删除/生成等操作
|
|
890
|
+
if (summaryOnly) {
|
|
891
|
+
console.log('');
|
|
892
|
+
console.log('=== 翻译缺失统计 ===');
|
|
893
|
+
console.log(` 代码中共使用 ${usedKeys.size} 个 i18n key`);
|
|
894
|
+
console.log('');
|
|
895
|
+
|
|
896
|
+
const allGood = Object.values(missingKeys).every((keys) => keys.length === 0);
|
|
897
|
+
|
|
898
|
+
if (missingKeys.zhCN.length > 0) {
|
|
899
|
+
console.log(` zh-cn.json 缺失: ${missingKeys.zhCN.length}`);
|
|
900
|
+
} else {
|
|
901
|
+
console.log(` zh-cn.json ✓ 完整`);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
for (const [language, missing] of Object.entries(missingKeys)) {
|
|
905
|
+
if (language === 'zhCN') continue;
|
|
906
|
+
if (missing.length > 0) {
|
|
907
|
+
console.log(` ${language}.json 缺失: ${missing.length}`);
|
|
908
|
+
} else {
|
|
909
|
+
console.log(` ${language}.json ✓ 完整`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
730
912
|
|
|
731
|
-
|
|
732
|
-
|
|
913
|
+
console.log('');
|
|
914
|
+
if (untranslatedTexts.length > 0) {
|
|
915
|
+
console.log(` 未翻译的中文文案: ${untranslatedTexts.length} 条`);
|
|
916
|
+
} else {
|
|
917
|
+
console.log(` 未翻译的中文文案: ✓ 无`);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
console.log('');
|
|
921
|
+
if (allGood && untranslatedTexts.length === 0) {
|
|
922
|
+
console.log('✅ 所有语言翻译完整,无未翻译文案');
|
|
923
|
+
} else if (allGood) {
|
|
924
|
+
console.log('❌ 存在未翻译的中文文案,请运行 npx i18n check 查看详情');
|
|
925
|
+
} else {
|
|
926
|
+
console.log('❌ 存在翻译缺失,请运行 npx i18n check 查看详情');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
return { missingKeys, unusedKeys, usedKeysData, untranslatedTexts };
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// 完整模式:输出详情、删除未使用key、生成Excel等
|
|
933
|
+
const outputLines = generateCheckOutput(missingKeys, unusedKeys, untranslatedTexts);
|
|
934
|
+
|
|
935
|
+
handleOutputAndJsonGeneration(
|
|
936
|
+
outputLines,
|
|
937
|
+
missingKeys,
|
|
938
|
+
unusedKeys,
|
|
939
|
+
untranslatedTexts,
|
|
940
|
+
autoDeleteUnused,
|
|
941
|
+
);
|
|
733
942
|
|
|
734
|
-
// 处理未使用key的删除
|
|
735
943
|
await handleUnusedKeysDeletion(
|
|
736
944
|
unusedKeys,
|
|
737
945
|
zhCN,
|
|
@@ -740,14 +948,13 @@ const check = async function (sourcePath, params) {
|
|
|
740
948
|
autoDeleteUnused,
|
|
741
949
|
);
|
|
742
950
|
|
|
743
|
-
// 处理Excel生成
|
|
744
951
|
await handleExcelGeneration(missingKeys, usedKeysData, zhCN, params, autoDeleteUnused);
|
|
745
952
|
|
|
746
|
-
// 返回结果,以便其他程序可能需要使用
|
|
747
953
|
const result = {
|
|
748
954
|
missingKeys,
|
|
749
955
|
unusedKeys,
|
|
750
|
-
usedKeysData,
|
|
956
|
+
usedKeysData,
|
|
957
|
+
untranslatedTexts,
|
|
751
958
|
};
|
|
752
959
|
|
|
753
960
|
return result;
|
package/lib/parse-jsx.js
CHANGED
|
@@ -4,87 +4,13 @@ const generate = require('@babel/generator').default;
|
|
|
4
4
|
const t = require('@babel/types');
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
6
|
const { shouldIgnoreLineByComment, shouldIgnoreBlockByComment } = require('./utils/file-utils');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const chinesePunctuationRegex =
|
|
15
|
-
/[,。!?;:""''()【】《》|、…—·~~@#$%^&*()_+\-={}\[\]|\\:;"'<>,.?\/]/g;
|
|
16
|
-
// 移除所有中文标点符号
|
|
17
|
-
const textWithoutPunctuation = text.replace(chinesePunctuationRegex, '');
|
|
18
|
-
// 如果移除标点后为空,说明只包含标点符号
|
|
19
|
-
return textWithoutPunctuation.trim() === '';
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const onlyContainsEmoji = (text) => {
|
|
23
|
-
// Emoji 正则表达式,包含常见的emoji Unicode范围
|
|
24
|
-
const emojiRegex =
|
|
25
|
-
/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F900}-\u{1F9FF}]|[\u{1F018}-\u{1F270}]|[\u{238C}-\u{2454}]|[\u{20D0}-\u{20FF}]|[\u{FE00}-\u{FE0F}]|[\u{1F200}-\u{1F2FF}]|[\u{1F700}-\u{1F77F}]|[\u{1F780}-\u{1F7FF}]|[\u{1F800}-\u{1F8FF}]|[\u{2300}-\u{23FF}]|[\u{2B00}-\u{2BFF}]|[\u{3030}]|[\u{303D}]|[\u{3297}]|[\u{3299}]/gu;
|
|
26
|
-
|
|
27
|
-
// 移除所有空白字符
|
|
28
|
-
const trimmedText = text.trim();
|
|
29
|
-
|
|
30
|
-
// 如果文本为空,返回false
|
|
31
|
-
if (!trimmedText) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 移除所有emoji字符
|
|
36
|
-
const textWithoutEmoji = trimmedText.replace(emojiRegex, '');
|
|
37
|
-
|
|
38
|
-
// 如果移除emoji后为空,说明只包含emoji
|
|
39
|
-
return textWithoutEmoji === '';
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// 检查节点是否是i18n调用
|
|
43
|
-
function isI18nCall(node) {
|
|
44
|
-
return (
|
|
45
|
-
node &&
|
|
46
|
-
node.type === 'CallExpression' &&
|
|
47
|
-
node.callee &&
|
|
48
|
-
node.callee.type === 'MemberExpression' &&
|
|
49
|
-
node.callee.object &&
|
|
50
|
-
node.callee.object.name === '$i18n' &&
|
|
51
|
-
node.callee.property &&
|
|
52
|
-
node.callee.property.name === 'get'
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 检测是否在 console 方法中
|
|
57
|
-
function isInConsoleCall(path) {
|
|
58
|
-
const consoleMethods = [
|
|
59
|
-
'log',
|
|
60
|
-
'warn',
|
|
61
|
-
'error',
|
|
62
|
-
'info',
|
|
63
|
-
'debug',
|
|
64
|
-
'trace',
|
|
65
|
-
'group',
|
|
66
|
-
'groupEnd',
|
|
67
|
-
'groupCollapsed',
|
|
68
|
-
'assert',
|
|
69
|
-
'table',
|
|
70
|
-
'time',
|
|
71
|
-
'timeEnd',
|
|
72
|
-
'count',
|
|
73
|
-
'countReset',
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
return path.findParent((p) => {
|
|
77
|
-
return (
|
|
78
|
-
p.isCallExpression() &&
|
|
79
|
-
p.node.callee &&
|
|
80
|
-
p.node.callee.type === 'MemberExpression' &&
|
|
81
|
-
p.node.callee.object &&
|
|
82
|
-
p.node.callee.object.name === 'console' &&
|
|
83
|
-
p.node.callee.property &&
|
|
84
|
-
consoleMethods.includes(p.node.callee.property.name)
|
|
85
|
-
);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
7
|
+
const {
|
|
8
|
+
chnRegExp,
|
|
9
|
+
onlyContainsChinesePunctuation,
|
|
10
|
+
onlyContainsEmoji,
|
|
11
|
+
isI18nCall,
|
|
12
|
+
isInConsoleCall,
|
|
13
|
+
} = require('./utils/ast-utils');
|
|
88
14
|
|
|
89
15
|
// 生成i18n调用节点
|
|
90
16
|
const createI18nCallNode = (i18nKey, defaultMessage, variables = []) => {
|
package/lib/utils/ast-utils.js
CHANGED
|
@@ -93,8 +93,74 @@ function checkI18nCall(node) {
|
|
|
93
93
|
return extractI18nKey(node);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
const chnRegExp = /[^\x00-\xff]/;
|
|
97
|
+
|
|
98
|
+
const onlyContainsChinesePunctuation = (text) => {
|
|
99
|
+
const chinesePunctuationRegex =
|
|
100
|
+
/[,。!?;:""''()【】《》|、…—·~~@#$%^&*()_+\-={}\[\]|\\:;"'<>,.?\/]/g;
|
|
101
|
+
const textWithoutPunctuation = text.replace(chinesePunctuationRegex, '');
|
|
102
|
+
return textWithoutPunctuation.trim() === '';
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const onlyContainsEmoji = (text) => {
|
|
106
|
+
const emojiRegex =
|
|
107
|
+
/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F900}-\u{1F9FF}]|[\u{1F018}-\u{1F270}]|[\u{238C}-\u{2454}]|[\u{20D0}-\u{20FF}]|[\u{FE00}-\u{FE0F}]|[\u{1F200}-\u{1F2FF}]|[\u{1F700}-\u{1F77F}]|[\u{1F780}-\u{1F7FF}]|[\u{1F800}-\u{1F8FF}]|[\u{2300}-\u{23FF}]|[\u{2B00}-\u{2BFF}]|[\u{3030}]|[\u{303D}]|[\u{3297}]|[\u{3299}]/gu;
|
|
108
|
+
const trimmedText = text.trim();
|
|
109
|
+
if (!trimmedText) return false;
|
|
110
|
+
return trimmedText.replace(emojiRegex, '') === '';
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function isI18nCall(node) {
|
|
114
|
+
return (
|
|
115
|
+
node &&
|
|
116
|
+
node.type === 'CallExpression' &&
|
|
117
|
+
node.callee &&
|
|
118
|
+
node.callee.type === 'MemberExpression' &&
|
|
119
|
+
node.callee.object &&
|
|
120
|
+
node.callee.object.name === '$i18n' &&
|
|
121
|
+
node.callee.property &&
|
|
122
|
+
node.callee.property.name === 'get'
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isInConsoleCall(path) {
|
|
127
|
+
const consoleMethods = [
|
|
128
|
+
'log',
|
|
129
|
+
'warn',
|
|
130
|
+
'error',
|
|
131
|
+
'info',
|
|
132
|
+
'debug',
|
|
133
|
+
'trace',
|
|
134
|
+
'group',
|
|
135
|
+
'groupEnd',
|
|
136
|
+
'groupCollapsed',
|
|
137
|
+
'assert',
|
|
138
|
+
'table',
|
|
139
|
+
'time',
|
|
140
|
+
'timeEnd',
|
|
141
|
+
'count',
|
|
142
|
+
'countReset',
|
|
143
|
+
];
|
|
144
|
+
return path.findParent((p) => {
|
|
145
|
+
return (
|
|
146
|
+
p.isCallExpression() &&
|
|
147
|
+
p.node.callee &&
|
|
148
|
+
p.node.callee.type === 'MemberExpression' &&
|
|
149
|
+
p.node.callee.object &&
|
|
150
|
+
p.node.callee.object.name === 'console' &&
|
|
151
|
+
p.node.callee.property &&
|
|
152
|
+
consoleMethods.includes(p.node.callee.property.name)
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
96
157
|
module.exports = {
|
|
97
158
|
extractStringValue,
|
|
98
159
|
extractI18nKey,
|
|
99
160
|
checkI18nCall,
|
|
161
|
+
chnRegExp,
|
|
162
|
+
onlyContainsChinesePunctuation,
|
|
163
|
+
onlyContainsEmoji,
|
|
164
|
+
isI18nCall,
|
|
165
|
+
isInConsoleCall,
|
|
100
166
|
};
|