@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 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) {
@@ -10,8 +10,21 @@ const {
10
10
  translateAndUpdateFiles,
11
11
  singleTranslateAndUpdateFiles,
12
12
  } = require('./file-processor');
13
- const { extractI18nKey } = require('../utils/ast-utils');
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
- const languageFiles = {};
325
- if (dashScope && dashScope.valueTranslateAppId) {
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
- for (const language of Object.keys(dashScope.valueTranslateAppId)) {
336
- try {
337
- languageFiles[language] = fs.readJsonSync(`${localesFilePath}/${language}.json`);
338
- } catch (error) {
339
- console.log(` 未找到${language}.json文件`);
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 = (outputLines, missingKeys, unusedKeys, autoDeleteUnused) => {
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
- const outputLines = generateCheckOutput(missingKeys, unusedKeys);
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
- // 处理输出和JSON文件生成
732
- handleOutputAndJsonGeneration(outputLines, missingKeys, unusedKeys, autoDeleteUnused);
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, // 添加使用的key数据
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
- const chnRegExp = new RegExp(/[^\x00-\xff]/);
10
-
11
- // 检查是否只包含中文标点符号
12
- const onlyContainsChinesePunctuation = (text) => {
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 = []) => {
@@ -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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentscope-ai/i18n",
3
- "version": "0.1.9-rc.0",
3
+ "version": "0.1.9-rc.2",
4
4
  "description": "A tool for translating Chinese content in frontend code repositories",
5
5
  "keywords": [
6
6
  "i18n",