vite-gc-i18n-plugin 1.1.3 → 1.1.5

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/dist/index.cjs CHANGED
@@ -597,794 +597,502 @@ const REGEX_MAP = {
597
597
  [OriginLangKeyEnum.RU]: /[йцукенгшщзхъфывапролджэячсмитьбюё .-]{1,}/ // 俄语字母
598
598
  };
599
599
 
600
- /*
601
- * @Author: xiaoshanwen
602
- * @Date: 2023-10-11 10:01:43
603
- * @LastEditTime: 2025-03-28 19:07:17
604
- * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/base.ts
605
- */
606
- function getOriginRegex() {
607
- const originLang = FunctionFactoryOption.originLang;
608
- return REGEX_MAP[originLang];
609
- }
600
+ var allLanguage = [{
601
+ code: 'zh-CN',
602
+ name: '简体中文'
603
+ }, {
604
+ code: 'zh-TW',
605
+ name: '繁体中文'
606
+ }, {
607
+ code: 'zh-UG',
608
+ name: '维吾尔语'
609
+ }, {
610
+ code: 'en-US',
611
+ name: '英文'
612
+ }, {
613
+ code: 'tr-TR',
614
+ name: '土耳其语'
615
+ }, {
616
+ code: 'es-ES',
617
+ name: '西班牙语'
618
+ }, {
619
+ code: 'ja-JP',
620
+ name: '日语'
621
+ }, {
622
+ code: 'ru-RU',
623
+ name: '俄语'
624
+ }, {
625
+ code: 'fr-FR',
626
+ name: '法语'
627
+ }, {
628
+ code: 'de-DE',
629
+ name: '德语'
630
+ }, {
631
+ code: 'pt-BR',
632
+ name: '巴西葡萄牙语'
633
+ }, {
634
+ code: 'pt-PT',
635
+ name: '葡萄牙语'
636
+ }, {
637
+ code: 'ko-KR',
638
+ name: '韩语'
639
+ }, {
640
+ code: 'vi-VN',
641
+ name: '越南语'
642
+ }, {
643
+ code: 'sv-SE',
644
+ name: '瑞典语'
645
+ }, {
646
+ code: 'id-ID',
647
+ name: '印尼语'
648
+ }, {
649
+ code: 'uk-UA',
650
+ name: '乌克兰语'
651
+ }, {
652
+ code: 'it-IT',
653
+ name: '意大利语'
654
+ }, {
655
+ code: 'th-TH',
656
+ name: '泰语'
657
+ }, {
658
+ code: 'hi-IN',
659
+ name: '印地语'
660
+ }, {
661
+ code: 'fa-IR',
662
+ name: '波斯语'
663
+ }, {
664
+ code: 'ro-RO',
665
+ name: '罗马尼亚语'
666
+ }, {
667
+ code: 'el-GR',
668
+ name: '希腊语'
669
+ }, {
670
+ code: 'nl-NL',
671
+ name: '荷兰语'
672
+ }, {
673
+ code: 'cs-CZ',
674
+ name: '捷克语'
675
+ }, {
676
+ code: 'mn-MN',
677
+ name: '蒙古语'
678
+ }, {
679
+ code: 'mn-TR',
680
+ name: '传统蒙古语'
681
+ }, {
682
+ code: 'fi-FI',
683
+ name: '芬兰语'
684
+ }, {
685
+ code: 'ar-SA',
686
+ name: '阿拉伯语'
687
+ }, {
688
+ code: 'ar-EG',
689
+ name: '阿拉伯语-埃及'
690
+ }, {
691
+ code: 'da-DK',
692
+ name: '丹麦语'
693
+ }, {
694
+ code: 'pl-PL',
695
+ name: '波兰语'
696
+ }, {
697
+ code: 'nb-NO',
698
+ name: '挪威语'
699
+ }, {
700
+ code: 'si-LK',
701
+ name: '僧伽罗语'
702
+ }];
610
703
 
611
704
  /**
612
- * @description: 是否包含来源语言字符
613
- * @param {string} code
614
- * @return {*}
705
+ * JSON 格式化配置类型
615
706
  */
616
- function hasOriginSymbols(code) {
617
- return getOriginRegex().test(code);
618
- }
619
707
 
620
- /**
621
- * @description: 过滤注释
622
- * @param {string} code
623
- * @return {*}
624
- */
625
- const removeComments = function (code) {
626
- // 使用正则表达式匹配并删除单行注释
627
- code = code.replace(/\/\/.*?\n/g, '');
628
- // 使用正则表达式匹配并删除多行注释
629
- code = code.replace(/\/\*[\s\S]*?\*\//g, '');
630
- // 使用正则表达式匹配并删除HTML注释
631
- code = code.replace(/<!--[\s\S]*?-->/g, '');
632
- return code;
708
+ // 默认的缩进配置
709
+ const indentConfig = {
710
+ tab: {
711
+ char: '\t',
712
+ size: 1
713
+ },
714
+ space: {
715
+ char: ' ',
716
+ size: 4
717
+ }
718
+ };
719
+
720
+ // 默认格式化配置
721
+ const configDefault = {
722
+ type: 'tab'
723
+ };
724
+
725
+ // 临时存储替换字符串的数组
726
+ let placeholderStorage = [];
727
+
728
+ // 临时存储处理函数
729
+ const pushPlaceholder = match => `\\${placeholderStorage.push(match)}\\`;
730
+ const popPlaceholder = (_match, index) => placeholderStorage[+index - 1];
731
+
732
+ // 生成缩进字符
733
+ const generateIndentation = (count, indentType) => {
734
+ return new Array(count + 1).join(indentType);
633
735
  };
634
736
 
635
737
  /**
636
- * @description: 用于判断提供的值是否符合正则表达式数组中的任一规则,符合则跳过
637
- * @param {*} value
638
- * @param {*} regexArray
639
- * @return {*}
738
+ * 格式化 JSON 字符串
739
+ * @param json JSON 字符串
740
+ * @param indentType 缩进类型
741
+ * @returns 格式化的 JSON 字符串
640
742
  */
641
- function checkAgainstRegexArray(value, regexArray) {
642
- for (let i = 0; i < regexArray.length; i++) {
643
- const regex = typeof regexArray[i] === 'string' ? new RegExp(regexArray[i]) : regexArray[i];
644
- if (regex.test(value)) {
645
- return true; // 如果符合任何一个规则,返回 true
743
+ function formatJSON(json, indentType) {
744
+ placeholderStorage = [];
745
+ let output = '';
746
+ let indentLevel = 0;
747
+
748
+ // 提取反斜杠和字符串
749
+ json = json.replace(/\\./g, pushPlaceholder) // 处理反斜杠
750
+ .replace(/(".*?"|'.*?')/g, pushPlaceholder) // 处理字符串
751
+ .replace(/\s+/g, ''); // 去除多余空格
752
+
753
+ // 根据 JSON 内容添加换行和缩进
754
+ for (let i = 0; i < json.length; i++) {
755
+ const char = json.charAt(i);
756
+ switch (char) {
757
+ case '{':
758
+ case '[':
759
+ output += char + '\n' + generateIndentation(++indentLevel, indentType);
760
+ break;
761
+ case '}':
762
+ case ']':
763
+ output += '\n' + generateIndentation(--indentLevel, indentType) + char;
764
+ break;
765
+ case ',':
766
+ output += ',\n' + generateIndentation(indentLevel, indentType);
767
+ break;
768
+ case ':':
769
+ output += ': ';
770
+ break;
771
+ default:
772
+ output += char;
773
+ break;
646
774
  }
647
775
  }
648
- return false; // 如果所有规则都不符合,返回 false
776
+
777
+ // 去除数字数组的空格,并还原反斜杠和字符串
778
+ output = output.replace(/\[[\d,\s]+?\]/g, match => match.replace(/\s/g, '')).replace(/\\(\d+)\\/g, popPlaceholder) // 还原填充数据
779
+ .replace(/\\(\d+)\\/g, popPlaceholder); // 再次还原填充数据
780
+
781
+ return output;
649
782
  }
650
783
 
651
784
  /**
652
- * @description: 用于解析抽象语法树中的调用表达式,并提取出调用的名称,如a.b.c() 取 c。
653
- * @param {any} node
654
- * @return {*}
785
+ * 格式化 JSON 的主函数
786
+ * @param json 需要格式化的 JSON 对象或字符串
787
+ * @param config 配置选项
788
+ * @returns 格式化后的 JSON 字符串
655
789
  */
656
- function extractFunctionName(node) {
657
- let callName = '';
658
- function callObjName(callObj, name) {
659
- name += '.' + callObj.property.name;
660
- if (types.isMemberExpression(callObj.object)) {
661
- // isMemberExpression: 是否是成员表达式
662
- return callObjName(callObj.object, name);
663
- }
664
- name = callObj.object.name + name;
665
- return name;
666
- }
667
- if (types.isCallExpression(node)) {
668
- // isCallExpression: 是否是调用表达式
669
- if (types.isMemberExpression(node.callee)) {
670
- callName = callObjName(node.callee, '');
671
- } else {
672
- callName = node.callee.name || '';
673
- }
790
+ function jsonFormatter(json) {
791
+ let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : configDefault;
792
+ // 确保输入是 JSON 字符串
793
+ const jsonString = typeof json === 'string' ? json : JSON.stringify(json);
794
+
795
+ // 获取缩进配置
796
+ const indent = indentConfig[config.type];
797
+ if (!indent) {
798
+ throw new Error(`Unrecognized indent type: "${config.type}"`);
674
799
  }
675
- return callName;
800
+
801
+ // 生成缩进字符
802
+ const indentType = generateIndentation(config.size || indent.size, indent.char);
803
+
804
+ // 格式化 JSON 字符串
805
+ return formatJSON(jsonString, indentType);
676
806
  }
677
807
 
678
- /**
679
- * @description: 提取文件的中文部分
680
- * @param {string} fileContent
681
- * @return {*}
808
+ /*
809
+ * @Date: 2025-02-17 17:11:26
810
+ * @LastEditors: xiaoshan
811
+ * @LastEditTime: 2025-02-17 18:00:37
812
+ * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/chunk.ts
682
813
  */
683
- const extractCnStrings = fileContent => {
684
- const regex = /[^\x00-\xff]+/g;
685
- return extractStrings(fileContent, regex);
686
- };
687
814
 
688
815
  /**
689
- * @description: 提取文件指定部分内容
690
- * @param {string} fileContent
691
- * @param {any} regex
692
- * @return {*}
816
+ * 智能文本分块处理器
817
+ * @param values 待分块的原始文本数组
818
+ * @param maxChunkSize 最大分块长度
819
+ * @returns 包含分块文本和重组方法的对象
820
+ *
821
+ * 功能特性:
822
+ * 1. 自动合并小文本为最大可能块
823
+ * 2. 处理超长文本并给出警告
824
+ * 3. 保证块长度不超过限制
825
+ * 4. 保留原始顺序和分隔符语义
693
826
  */
694
- function extractStrings(fileContent, regex) {
695
- const matches = fileContent.match(regex);
696
- return matches ? matches.filter((item, index) => matches.indexOf(item) === index) : [];
697
- }
827
+ function createTextSplitter(values, maxChunkSize) {
828
+ // 分隔符定义(用于合并/拆分时保持语义)
829
+ const SEP_LENGTH = SEPARATOR.length;
698
830
 
699
- /**
700
- * @description: 生成i8n翻译函数
701
- * @param {string} value
702
- * @param {boolean} isExpression
703
- * @param {string} key
704
- * @return {*}
705
- */
706
- function createI18nTranslator(createOption) {
707
- const {
708
- value,
709
- isExpression = false,
710
- key,
711
- insertOption
712
- } = createOption;
831
+ // 结果存储和缓冲区
832
+ const result = []; // 最终分块结果
833
+ let buffer = []; // 当前累积块缓冲区
834
+ let currentSize = 0; // 当前缓冲区字符数(含分隔符)
713
835
 
714
- // 从全局配置对象 option 中获取命名空间
715
- const nameSpace = exports.option.namespace;
716
- const {
717
- trimmedValue,
718
- valStr
719
- } = normalizeTranslateValue(value);
720
- // key 存在则使用 key,否则调用 generateId 函数根据 valStr 生成唯一的键
721
- const generatedKey = key || generateId(valStr);
722
- // 提取公共配置对象,避免重复代码
723
- const config = {
724
- option: exports.option,
725
- hash: generatedKey,
726
- value: trimmedValue,
727
- uncodeValue: valStr,
728
- namespace: nameSpace
836
+ /**
837
+ * 提交缓冲区内容到结果集
838
+ * - 将缓冲区内容用分隔符连接
839
+ * - 重置缓冲区和计数器
840
+ */
841
+ const commitBuffer = () => {
842
+ if (buffer.length > 0) {
843
+ // 计算实际连接长度用于验证
844
+ const actualLength = buffer.join(SEPARATOR).length;
845
+ if (actualLength > maxChunkSize) {
846
+ console.warn(`缓冲区提交异常:生成块长度 ${actualLength} 超过限制`);
847
+ }
848
+ result.push(buffer.join(SEPARATOR));
849
+ buffer = [];
850
+ currentSize = 0;
851
+ }
729
852
  };
730
- if (exports.option.translateExtends) {
731
- const {
732
- handleCodeCall,
733
- handleCodeString
734
- } = exports.option.translateExtends;
735
- return isExpression ? handleCodeCall(config, insertOption) : handleCodeString(config, insertOption);
736
- }
737
- if (isExpression) {
738
- const valueExp = types.stringLiteral(trimmedValue);
739
- valueExp.extra = {
740
- raw: `'${valStr}'`,
741
- // 防止转码为unicode
742
- rawValue: trimmedValue
743
- };
744
- return exports.option.useValueAsKey ? types.callExpression(types.identifier(exports.option.translateKey), [valueExp]) : types.callExpression(types.identifier(exports.option.translateKey), [types.stringLiteral(generatedKey), valueExp]);
745
- } else {
746
- return `${exports.option.translateKey}('${generatedKey}','${valStr}')`;
853
+
854
+ // 主处理循环:遍历所有原始文本项
855
+ for (const value of values) {
856
+ // 计算需要新增的空间:文本长度 + 分隔符(非首项)
857
+ const neededSpace = value.length + (buffer.length > 0 ? SEP_LENGTH : 0);
858
+
859
+ // ─── 超长文本处理策略 ───
860
+ if (value.length > maxChunkSize) {
861
+ // 优先提交现有缓冲区内容
862
+ if (buffer.length > 0) commitBuffer();
863
+
864
+ /**
865
+ * 超长文本处理逻辑:
866
+ * - 长度超过1.5倍限制时发出强警告
867
+ * - 强制单独成块(即使超过限制)
868
+ * - 后续需要特殊处理这些异常块
869
+ */
870
+ if (value.length > maxChunkSize * 1.5) {
871
+ console.warn(`超长文本告警:检测到长度 ${value.length} 字符的文本项,可能影响翻译质量`);
872
+ }
873
+ // 结果直接新增一个超长文本
874
+ result.push(value);
875
+ continue;
876
+ }
877
+
878
+ // ─── 正常分块逻辑 ───
879
+ // 空间不足时提交当前缓冲区
880
+ if (currentSize + neededSpace > maxChunkSize) {
881
+ commitBuffer();
882
+ }
883
+
884
+ // 更新缓冲区状态(累加长度需包含分隔符)
885
+ currentSize += neededSpace;
886
+ buffer.push(value);
747
887
  }
748
- }
749
- function normalizeTranslateValue(value) {
750
- const trimmedValue = exports.option.isClearSpace ? value : value.trim();
751
- const valStr = trimmedValue.replace(/'/g, '"').replace(/(\n)/g, '\\n');
752
- return {
753
- trimmedValue,
754
- valStr
755
- };
888
+
889
+ // 提交最终未完成的缓冲区内容
890
+ commitBuffer();
891
+
892
+ // 返回分块结果
893
+ return result;
756
894
  }
757
895
 
758
- /**
759
- * @description: 生成唯一id
760
- * @param {string} key
761
- * @return {*}
896
+ /*
897
+ * @Date: 2025-03-26 20:28:21
898
+ * @LastEditors: xiaoshan
899
+ * @LastEditTime: 2025-03-31 10:29:49
900
+ * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/split.ts
762
901
  */
763
- function generateId(key) {
764
- let hash = 0;
765
- for (let i = 0; i < key.length; i++) {
766
- const charCode = key.charCodeAt(i);
767
- hash = (hash << 5) - hash + charCode;
768
- hash = hash & hash;
769
- }
770
- const id = Math.abs(hash).toString(36) + key.length.toString(36);
771
- return id;
772
- }
902
+ // 插件核心文件
903
+ // 字符串切割与转换函数
904
+ // import generate from '@babel/generator'
773
905
 
906
+ // todo 这个切割函数可以优化,性能可能很差
774
907
  /**
775
- * @description: unicode转普通字符串
776
- * @param {string} str
777
- * @return {*}
908
+ * 根据正则表达式分割字符串,并将符合正则的连续字符拼接起来。
909
+ * @param str - 要分割的字符串。
910
+ * @param separatorRegex - 用于分割字符串的正则表达式。
911
+ * @returns 分割并拼接后的字符串数组。
778
912
  */
779
- const unicodeToString = str => {
780
- return str.replace(/\\u[\dA-Fa-f]{4}/g, match => {
781
- return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16));
782
- });
783
- };
784
-
785
913
  /**
786
- * @description: 有道翻译 标识截取
787
- * @param {string} q
788
- * @return {*}
914
+ * 这个函数的主要功能是根据给定的正则表达式分割字符串,并对分割结果进行特殊处理。
915
+ * 处理过程分为三个主要步骤:
916
+ *
917
+ * 1. 首先根据分隔符正则和标点符号正则进行初步分割
918
+ * 2. 然后将连续的标点符号和符合分隔符正则的部分重新连接
919
+ * 3. 最后将不符合分隔符正则的相邻部分合并
920
+ *
921
+ * @param str - 需要分割的源字符串
922
+ * @param separatorRegex - 用于分割的正则表达式
923
+ * @returns 处理后的字符串数组
789
924
  */
790
- function truncate(q) {
791
- // 检查输入字符串的长度
792
- if (q.length <= 20) {
793
- // 如果长度小于等于20,直接返回原字符串
794
- return q;
795
- } else {
796
- // 如果长度大于20,截取前10个字符和后10个字符,并在中间插入长度信息
797
- const len = q.length;
798
- return q.substring(0, 10) + len + q.substring(len - 10);
799
- }
800
- }
925
+ function splitByRegex(str, separatorRegex) {
926
+ // 定义标点符号的正则表达式
927
+ const punctuationRegex = /[,。?!《》,..:!?""'';'"、0-9\n\r\t\v\f]/;
928
+ // 创建一个新的正则表达式,用于分割字符串
929
+ const splitRegex = new RegExp(`(${separatorRegex.source}|${punctuationRegex.source})`, separatorRegex.flags);
801
930
 
802
- // 导出一个深拷贝函数,用于克隆对象
803
- function cloneDeep(value) {
804
- let cache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new WeakMap();
805
- // 处理基本类型和 null
806
- if (typeof value !== 'object' || value === null) {
807
- return value;
808
- }
931
+ // 使用正则表达式分割字符串,并过滤掉空字符串
932
+ const splitArr = str.split(splitRegex).filter(Boolean);
933
+ const result = [];
934
+ let currentMatch = '';
809
935
 
810
- // 处理循环引用
811
- if (cache.has(value)) {
812
- return cache.get(value);
813
- }
936
+ // 定义连接标点符号的正则表达式
937
+ const connectPunctuationRegex = /[,。?!《》,..:!?;'"、0-9]/;
938
+ // 创建一个新的正则表达式,用于检测是否需要连接
939
+ const connectRegex = new RegExp(`(${separatorRegex.source}|${connectPunctuationRegex.source})`, separatorRegex.flags);
814
940
 
815
- // 处理特殊对象类型
816
- if (value instanceof Date) {
817
- return new Date(value);
818
- }
819
- if (value instanceof RegExp) {
820
- return new RegExp(value.source, value.flags);
941
+ // 遍历分割后的数组
942
+ for (const item of splitArr) {
943
+ if (connectRegex.test(item)) {
944
+ // 如果当前项符合连接条件,则将其添加到当前匹配字符串中
945
+ currentMatch += item;
946
+ } else {
947
+ // 如果当前匹配字符串不为空,则将其添加到结果数组中
948
+ if (currentMatch) {
949
+ result.push(currentMatch);
950
+ currentMatch = '';
951
+ }
952
+ // 将当前项添加到结果数组中
953
+ result.push(item);
954
+ }
821
955
  }
822
956
 
823
- // 初始化克隆容器
824
- const clone = Array.isArray(value) ? [] : {};
825
-
826
- // 缓存对象防止循环引用
827
- cache.set(value, clone);
957
+ // 如果最后一个匹配字符串不为空,则将其添加到结果数组中
958
+ if (currentMatch) {
959
+ result.push(currentMatch);
960
+ }
828
961
 
829
- // 处理 Symbol 和普通键的枚举
830
- const keys = [...Object.keys(value), ...Object.getOwnPropertySymbols(value).filter(sym => value.propertyIsEnumerable(sym))];
831
-
832
- // 递归克隆属性
833
- for (const key of keys) {
834
- clone[key] = cloneDeep(value[key], cache);
835
- }
836
- return clone;
837
- }
838
-
839
- var base = /*#__PURE__*/Object.freeze({
840
- __proto__: null,
841
- checkAgainstRegexArray: checkAgainstRegexArray,
842
- cloneDeep: cloneDeep,
843
- createI18nTranslator: createI18nTranslator,
844
- extractCnStrings: extractCnStrings,
845
- extractFunctionName: extractFunctionName,
846
- extractStrings: extractStrings,
847
- generateId: generateId,
848
- getOriginRegex: getOriginRegex,
849
- hasOriginSymbols: hasOriginSymbols,
850
- normalizeTranslateValue: normalizeTranslateValue,
851
- removeComments: removeComments,
852
- truncate: truncate,
853
- unicodeToString: unicodeToString
854
- });
855
-
856
- // 代码灵感来自https://github.com/dadidi9900/auto-plugins-json-translate/blob/main/src/services/translationService.ts
857
- /**
858
- * 火山引擎翻译器,内置豆包、deepseek等模型
859
- *
860
- * 火山引擎大模型介绍:https://www.volcengine.com/docs/82379/1099455
861
- *
862
- * api文档:https://www.volcengine.com/docs/82379/1298454
863
- *
864
- * 使用方式:
865
- * ```ts
866
- * vitePluginsAutoI18n({
867
- ...
868
- translator: new VolcengineTranslator({
869
- apiKey: '你申请的apiKey',
870
- model: '你要调用的模型,如:`doubao-1-5-pro-32k-250115`,请确保使用前已在控制台开通了对应模型'
871
- })
872
- })
873
- * ```
874
- */
875
- class VolcengineTranslator extends Translator {
876
- constructor(option) {
877
- super({
878
- name: '火山引擎ai翻译',
879
- fetchMethod: async (text, fromKey, toKey, separator) => {
880
- let salt = new Date().getTime();
881
- const textArr = text.split(separator);
882
- const sourceMap = Object.fromEntries(textArr.map(text => [generateId(text), text]));
883
- const data = {
884
- model: option.model,
885
- messages: [{
886
- role: 'system',
887
- content: `
888
- ###
889
- 假如你是一个无情的翻译接口,你将根据一个文本组成的JSON对象,来解决将数组每个成员从源语言A翻译成目标语言B并返回翻译后的JSON对象的任务。需要注意的是,待翻译的文本均来自一个${option.desc ? option.desc + '的' : ''}web平台,遇到歧义时需要做好处理。根据以下规则一步步执行:
890
- 1. 明确源语言A和目标语言B。
891
- 2. 对JSON对象中数组的每个成员进行从源语言A到目标语言B的翻译。
892
- 3. 将翻译后的内容以JSON对象格式返回,确保返回的内容可以被JSON.parse解析。
893
-
894
- 参考例子:
895
- 示例1:
896
- 输入:zh-cn -> en { "awfgx": "你好", "qwfga": "世界" }
897
- 输出:{ "awfgx": "Hello", "qwfga": "World" }
898
-
899
- 示例2:
900
- 输入:de -> fr { "gweaq": "Hallo", "wtrts": "Welt" }
901
- 输出:{ "gweaq": "Bonjour", "wtrts": "Monde" }
902
-
903
- 请回答问题:
904
- 输入:源语言A -> 目标语言B { "wghhj": "XXX" }
905
- 输出:
906
-
907
- 要求:
908
- 1 以JSON对象格式输出
909
- 2 JSON对象中每个成员为翻译后的内容
910
- ###
911
- `
912
- }, {
913
- role: 'user',
914
- content: `${fromKey} -> ${toKey} ${JSON.stringify(sourceMap)}`
915
- }],
916
- ...(option.insertOption || {})
917
- };
918
- const response = await axios.post(`https://ark.cn-beijing.volces.com/api/v3/chat/completions?t=${salt}`, data, {
919
- headers: {
920
- 'Content-Type': 'application/json',
921
- Authorization: `Bearer ${option.apiKey}`
922
- },
923
- proxy: option.proxy
924
- });
925
- let resultTextArr = Array.from(textArr).fill('');
926
- const content = response.data.choices[0].message.content;
927
- try {
928
- let resultMap;
929
- try {
930
- resultMap = JSON.parse(content);
931
- } catch (error) {
932
- throw new Error('大模型返回文本解析失败');
933
- }
934
- if (typeof resultMap !== 'object' || !resultMap) {
935
- throw new Error('大模型返回文本解析后类型不正确');
936
- }
937
- const isMiss = Object.keys(resultMap).some(key => !(key in sourceMap));
938
- if (isMiss) {
939
- throw new Error('大模型返回文本内容不完整');
940
- }
941
- resultTextArr = textArr.map(text => resultMap[generateId(text)]); // 用textArr遍历,保证顺序
942
- } catch (error) {
943
- const message = error instanceof Error ? error.message : '未知错误';
944
- console.warn('⚠', message);
945
- console.warn('⚠ 返回的文本内容:', content);
946
- console.warn('⚠ 原文本内容:', JSON.stringify(sourceMap));
947
- }
948
- return resultTextArr.join(separator);
949
- },
950
- onError: (error, cb) => {
951
- cb(error);
952
- console.error('请确保在火山引擎控制台开通了对应模型,且有足够的token余额。控制台地址:https://console.volcengine.com/ark/');
953
- },
954
- maxChunkSize: 1000,
955
- // 太长可能会导致返回文本不完整
956
- interval: option.interval ?? 1000
957
- });
958
- }
959
- }
960
-
961
- /*
962
- * @Author: xiaoshanwen
963
- * @Date: 2024-04-03 18:12:45
964
- * @LastEditTime: 2024-04-03 18:33:53
965
- * @FilePath: /i18n_translation_vite/autoI18nPluginCore/src/enums/translate.ts
966
- */
967
- let TranslateApiEnum = /*#__PURE__*/function (TranslateApiEnum) {
968
- TranslateApiEnum["google"] = "Google";
969
- TranslateApiEnum["youdao"] = "Youdao";
970
- return TranslateApiEnum;
971
- }({});
972
-
973
- /*
974
- * @Date: 2025-03-16 14:12:30
975
- * @LastEditors: xiaoshan
976
- * @LastEditTime: 2025-03-16 14:13:42
977
- * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/enums/option.ts
978
- */
979
- /**
980
- * 翻译类型枚举
981
- */
982
- let TranslateTypeEnum = /*#__PURE__*/function (TranslateTypeEnum) {
983
- TranslateTypeEnum["FULL_AUTO"] = "full-auto";
984
- TranslateTypeEnum["SEMI_AUTO"] = "semi-auto";
985
- return TranslateTypeEnum;
986
- }({});
987
-
988
- /*
989
- * @Author: xiaoshanwen
990
- * @Date: 2023-10-26 17:34:47
991
- * @LastEditTime: 2025-03-31 19:58:37
992
- * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/option.ts
993
- */
994
-
995
- const EXCLUDED_CALL = ['$deepScan', 'console.info', 'console.warn', 'console.error', '$i8n', 'console.log', '$t', 'require', '$$i8n', '$$t', '_createCommentVNode'];
996
- /**
997
- * 默认插件配置选项
998
- */
999
- const DEFAULT_OPTION = {
1000
- appCode: '',
1001
- /** 是否启用插件,默认启用 */
1002
- enabled: true,
1003
- /** 翻译调用函数,默认为 $t */
1004
- translateKey: '$t',
1005
- /** 标记不翻译调用函数列表,避免某些调用被错误翻译 */
1006
- excludedCall: [],
1007
- /** 标记不用翻译的字符串模式数组,默认是匹配文件扩展名 */
1008
- excludedPattern: [/\.\w+$/],
1009
- /** 排查不需要翻译的目录下的文件路径(黑名单), 默认不处理node_modules */
1010
- excludedPath: ['node_modules'],
1011
- /** 指定需要翻译文件的目录路径正则(白名单) */
1012
- includePath: [/src\//, /src\\/],
1013
- /** 配置文件生成位置,默认为 './lang' */
1014
- globalPath: './lang',
1015
- /** 打包后生成文件的位置,例如 './dist/assets' */
1016
- distPath: '',
1017
- /** 打包后生成文件的主文件名称,默认是 'index' */
1018
- distKey: 'index',
1019
- /** 来源语言,默认是中文 */
1020
- originLang: OriginLangKeyEnum.ZH,
1021
- /** 翻译目标语言列表,默认包含英文 */
1022
- targetLangList: ['en'],
1023
- /** 语言key,用于请求谷歌api和生成配置文件下对应语言的内容文件 */
1024
- langKey: [],
1025
- /** 命名空间,防止全局命名冲突 */
1026
- namespace: 'lang',
1027
- /** 单位代码 */
1028
- orgCode: 'GREENCLOUD',
1029
- /** 是否启用源码的值作为key */
1030
- useValueAsKey: false,
1031
- /** 是否在构建结束之后将最新的翻译重新打包到主包中,默认不打包 */
1032
- buildToDist: false,
1033
- /** 是否启用上传翻译文件到服务器,默认开启 */
1034
- uploadEnabled: true,
1035
- /** 翻译器,决定自动翻译使用的api与调用方式,默认使用 Google 翻译器并使用7890(clash)端口代理 */
1036
- translator: new GoogleTranslator({
1037
- proxyOption: {
1038
- port: 7890,
1039
- host: '127.0.0.1',
1040
- headers: {
1041
- 'User-Agent': 'Node'
1042
- }
1043
- }
1044
- }),
1045
- /** 翻译器配置选项,优先级低于translator */
1046
- translatorOption: undefined,
1047
- /**
1048
- * 翻译类型,支持全自动和半自动两种模式
1049
- * 全自动:所有翻译任务自动完成
1050
- * 半自动:需要人工标识,类似于 $t('key') 的方式
1051
- * 默认值为全自动
1052
- */
1053
- translateType: TranslateTypeEnum.FULL_AUTO,
1054
- /**
1055
- * 是否重写配置文件,默认为true
1056
- */
1057
- rewriteConfig: true,
1058
- /**
1059
- * 通用翻译key,默认使用namespace,如果commonTranslateKey不为空,则使用commonTranslateKey
1060
- */
1061
- commonTranslateKey: '',
1062
- /**
1063
- * 实验性属性,表示是否进行深层扫描字符串,默认为 false
1064
- * 当设置为 true 时,会对代码中的字符串进行更深入的扫描
1065
- */
1066
- deepScan: false,
1067
- /**
1068
- * 自定义文件拓展名数组
1069
- */
1070
- insertFileExtensions: [],
1071
- /**
1072
- * 自定义拓展类,插件默认翻译函数挂载在window上,如果希望自定义翻译函数挂载在其他对象上,可以使用该属性
1073
- * 注意:该属性需要继承BaseExtends类,并且需要实现handleInitFile和handleCodeCall和handleCodeString方法
1074
- */
1075
- translateExtends: null,
1076
- isClear: false,
1077
- // 是否清除已经不在上下文中的内容(清除项目中不再使用到的源语言键值对)
1078
-
1079
- /**
1080
- * 是否保留空格
1081
- */
1082
- isClearSpace: true
1083
- };
1084
-
1085
- /**
1086
- * 类型定义:插件配置选项类型
1087
- */
1088
-
1089
- /**
1090
- * 全局插件配置实例,复制自默认配置
1091
- */
1092
- exports.option = {
1093
- ...DEFAULT_OPTION
1094
- };
1095
-
1096
- /**
1097
- * 类型定义:用户传入的配置选项
1098
- */
1099
-
1100
- /**
1101
- * 通过深度克隆提供的选项信息生成一个用户选项对象,
1102
- * 确保原始配置不被修改。它还根据用户的配置初始化翻译器。
1103
- * @param optionInfo - 包含用户选项和翻译器细节的选项信息。
1104
- * @returns 一个新的、可能已初始化翻译器的用户选项对象。
1105
- */
1106
- function generateUserOption(optionInfo) {
1107
- // 深拷贝用户传入的配置,防止修改原配置对象
1108
- const userOption = cloneDeep(optionInfo);
1109
- userOption.translator = optionInfo?.translator;
1110
-
1111
- // 如果用户配置了translatorOption则初始化translator,如果都没有则不设置translator
1112
- userOption.translator ||= userOption.translatorOption ? new Translator(userOption.translatorOption) : undefined;
1113
- if (!userOption.translator) delete userOption.translator;
1114
- return userOption;
1115
- }
1116
-
1117
- /**
1118
- * 初始化插件配置选项
1119
- * @param optionInfo 用户提供的配置选项
1120
- */
1121
- function initOption(optionInfo) {
1122
- // 合并默认配置和用户配置
1123
- exports.option = {
1124
- ...DEFAULT_OPTION,
1125
- ...generateUserOption(optionInfo)
1126
- };
1127
-
1128
- // 初始化语言key数组,包含来源语言和目标语言
1129
- exports.option.langKey = [exports.option.originLang, ...exports.option.targetLangList];
1130
- // 初始化排除调用函数列表,包含默认排除和调用函数主动排除
1131
- exports.option.excludedCall = [...exports.option.excludedCall, ...EXCLUDED_CALL, ...[exports.option.translateKey, '$' + exports.option.translateKey]];
1132
- return exports.option;
1133
- }
1134
-
1135
- /**
1136
- * 校验插件配置选项是否完整有效
1137
- * @returns {boolean} 校验结果,完整返回 true,否则返回 false
1138
- */
1139
- function checkOption() {
1140
- // 校验翻译调用函数是否配置
1141
- if (!exports.option.translateKey) {
1142
- console.error('❌请配置翻译调用函数');
1143
- return false;
1144
- }
1145
-
1146
- // 校验命名空间是否配置
1147
- if (!exports.option.namespace) {
1148
- console.error('❌请配置命名空间');
1149
- return false;
1150
- }
1151
-
1152
- // 校验是否配置了打包后生成文件的主文件名称(如果需要打包到主包中)
1153
- if (exports.option.buildToDist && !exports.option.distKey) {
1154
- console.log('❌请配置打包后生成文件的主文件名称');
1155
- return false;
1156
- }
1157
-
1158
- // 校验是否配置了打包后生成文件的位置(如果需要打包到主包中)
1159
- if (exports.option.buildToDist && !exports.option.distPath) {
1160
- console.log('❌请配置打包后生成文件的位置');
1161
- return false;
1162
- }
1163
-
1164
- // 校验来源语言是否配置
1165
- if (!exports.option.originLang) {
1166
- console.error('❌请配置来源语言');
1167
- return false;
1168
- }
1169
-
1170
- // 校验目标翻译语言数组是否配置
1171
- if (!exports.option.targetLangList || !exports.option.targetLangList.length) {
1172
- console.error('❌请配置目标翻译语言数组');
1173
- return false;
1174
- }
1175
-
1176
- // 如果所有校验通过,返回 true
1177
- return true;
1178
- }
1179
-
1180
- var allLanguage = [{
1181
- code: 'zh-CN',
1182
- name: '简体中文'
1183
- }, {
1184
- code: 'zh-TW',
1185
- name: '繁体中文'
1186
- }, {
1187
- code: 'zh-UG',
1188
- name: '维吾尔语'
1189
- }, {
1190
- code: 'en-US',
1191
- name: '英文'
1192
- }, {
1193
- code: 'tr-TR',
1194
- name: '土耳其语'
1195
- }, {
1196
- code: 'es-ES',
1197
- name: '西班牙语'
1198
- }, {
1199
- code: 'ja-JP',
1200
- name: '日语'
1201
- }, {
1202
- code: 'ru-RU',
1203
- name: '俄语'
1204
- }, {
1205
- code: 'fr-FR',
1206
- name: '法语'
1207
- }, {
1208
- code: 'de-DE',
1209
- name: '德语'
1210
- }, {
1211
- code: 'pt-BR',
1212
- name: '巴西葡萄牙语'
1213
- }, {
1214
- code: 'pt-PT',
1215
- name: '葡萄牙语'
1216
- }, {
1217
- code: 'ko-KR',
1218
- name: '韩语'
1219
- }, {
1220
- code: 'vi-VN',
1221
- name: '越南语'
1222
- }, {
1223
- code: 'sv-SE',
1224
- name: '瑞典语'
1225
- }, {
1226
- code: 'id-ID',
1227
- name: '印尼语'
1228
- }, {
1229
- code: 'uk-UA',
1230
- name: '乌克兰语'
1231
- }, {
1232
- code: 'it-IT',
1233
- name: '意大利语'
1234
- }, {
1235
- code: 'th-TH',
1236
- name: '泰语'
1237
- }, {
1238
- code: 'hi-IN',
1239
- name: '印地语'
1240
- }, {
1241
- code: 'fa-IR',
1242
- name: '波斯语'
1243
- }, {
1244
- code: 'ro-RO',
1245
- name: '罗马尼亚语'
1246
- }, {
1247
- code: 'el-GR',
1248
- name: '希腊语'
1249
- }, {
1250
- code: 'nl-NL',
1251
- name: '荷兰语'
1252
- }, {
1253
- code: 'cs-CZ',
1254
- name: '捷克语'
1255
- }, {
1256
- code: 'mn-MN',
1257
- name: '蒙古语'
1258
- }, {
1259
- code: 'mn-TR',
1260
- name: '传统蒙古语'
1261
- }, {
1262
- code: 'fi-FI',
1263
- name: '芬兰语'
1264
- }, {
1265
- code: 'ar-SA',
1266
- name: '阿拉伯语'
1267
- }, {
1268
- code: 'ar-EG',
1269
- name: '阿拉伯语-埃及'
1270
- }, {
1271
- code: 'da-DK',
1272
- name: '丹麦语'
1273
- }, {
1274
- code: 'pl-PL',
1275
- name: '波兰语'
1276
- }, {
1277
- code: 'nb-NO',
1278
- name: '挪威语'
1279
- }, {
1280
- code: 'si-LK',
1281
- name: '僧伽罗语'
1282
- }];
1283
-
1284
- /**
1285
- * JSON 格式化配置类型
1286
- */
1287
-
1288
- // 默认的缩进配置
1289
- const indentConfig = {
1290
- tab: {
1291
- char: '\t',
1292
- size: 1
1293
- },
1294
- space: {
1295
- char: ' ',
1296
- size: 4
962
+ // 再遍历一次,把不符合separatorRegex 这个正则的拼起来
963
+ const finalResult = [];
964
+ let tempStr = '';
965
+ for (let i = 0; i < result.length; i++) {
966
+ const item = result[i];
967
+ if (separatorRegex.test(item)) {
968
+ if (tempStr) {
969
+ finalResult.push(tempStr);
970
+ tempStr = '';
971
+ }
972
+ finalResult.push(item);
973
+ } else {
974
+ tempStr += item;
975
+ if (i === result.length - 1 || separatorRegex.test(result[i + 1])) {
976
+ finalResult.push(tempStr);
977
+ tempStr = '';
978
+ }
979
+ }
1297
980
  }
1298
- };
1299
-
1300
- // 默认格式化配置
1301
- const configDefault = {
1302
- type: 'tab'
1303
- };
1304
-
1305
- // 临时存储替换字符串的数组
1306
- let placeholderStorage = [];
1307
-
1308
- // 临时存储处理函数
1309
- const pushPlaceholder = match => `\\${placeholderStorage.push(match)}\\`;
1310
- const popPlaceholder = (_match, index) => placeholderStorage[+index - 1];
1311
-
1312
- // 生成缩进字符
1313
- const generateIndentation = (count, indentType) => {
1314
- return new Array(count + 1).join(indentType);
1315
- };
981
+ if (tempStr) {
982
+ finalResult.push(tempStr);
983
+ }
984
+ return finalResult;
985
+ }
1316
986
 
1317
987
  /**
1318
- * 格式化 JSON 字符串
1319
- * @param json JSON 字符串
1320
- * @param indentType 缩进类型
1321
- * @returns 格式化的 JSON 字符串
988
+ * 检查字符串是否需要切割。
989
+ * @param str - 要检查的字符串。
990
+ * @returns 如果字符串需要切割,则返回 true,否则返回 false。
1322
991
  */
1323
- function formatJSON(json, indentType) {
1324
- placeholderStorage = [];
1325
- let output = '';
1326
- let indentLevel = 0;
1327
-
1328
- // 提取反斜杠和字符串
1329
- json = json.replace(/\\./g, pushPlaceholder) // 处理反斜杠
1330
- .replace(/(".*?"|'.*?')/g, pushPlaceholder) // 处理字符串
1331
- .replace(/\s+/g, ''); // 去除多余空格
1332
-
1333
- // 根据 JSON 内容添加换行和缩进
1334
- for (let i = 0; i < json.length; i++) {
1335
- const char = json.charAt(i);
1336
- switch (char) {
1337
- case '{':
1338
- case '[':
1339
- output += char + '\n' + generateIndentation(++indentLevel, indentType);
1340
- break;
1341
- case '}':
1342
- case ']':
1343
- output += '\n' + generateIndentation(--indentLevel, indentType) + char;
1344
- break;
1345
- case ',':
1346
- output += ',\n' + generateIndentation(indentLevel, indentType);
1347
- break;
1348
- case ':':
1349
- output += ': ';
1350
- break;
1351
- default:
1352
- output += char;
1353
- break;
1354
- }
1355
- }
1356
-
1357
- // 去除数字数组的空格,并还原反斜杠和字符串
1358
- output = output.replace(/\[[\d,\s]+?\]/g, match => match.replace(/\s/g, '')).replace(/\\(\d+)\\/g, popPlaceholder) // 还原填充数据
1359
- .replace(/\\(\d+)\\/g, popPlaceholder); // 再次还原填充数据
1360
-
1361
- return output;
992
+ function checkNeedSplit(str) {
993
+ // 检查字符串中是否包含需要切割的特殊字符
994
+ return str.includes('\n') || str.includes('\\') || str.includes('\r') || str.includes('\t') || str.includes('\v') || str.includes('\f') || str.includes('>') || str.includes('<');
1362
995
  }
1363
996
 
1364
997
  /**
1365
- * 格式化 JSON 的主函数
1366
- * @param json 需要格式化的 JSON 对象或字符串
1367
- * @param config 配置选项
1368
- * @returns 格式化后的 JSON 字符串
998
+ * @description: 将字符串数组转换为babel的模板字符串节点
999
+ * @param {string[]} strArray - 字符串数组
1000
+ * @return {types.CallExpression} - babel的深度扫描的表达式
1369
1001
  */
1370
- function jsonFormatter(json) {
1371
- let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : configDefault;
1372
- // 确保输入是 JSON 字符串
1373
- const jsonString = typeof json === 'string' ? json : JSON.stringify(json);
1374
-
1375
- // 获取缩进配置
1376
- const indent = indentConfig[config.type];
1377
- if (!indent) {
1378
- throw new Error(`Unrecognized indent type: "${config.type}"`);
1002
+ function convertToTemplateLiteral(strArray, option) {
1003
+ const quasis = [];
1004
+ const expressions = [];
1005
+ strArray.forEach((str, index) => {
1006
+ if (index === 0) {
1007
+ if (getOriginRegex().test(str)) {
1008
+ quasis.push(types__namespace.templateElement({
1009
+ raw: '',
1010
+ cooked: ''
1011
+ }, false));
1012
+ expressions.push(createI18nTranslator({
1013
+ value: str,
1014
+ isExpression: true,
1015
+ insertOption: option
1016
+ }));
1017
+ } else {
1018
+ quasis.push(types__namespace.templateElement({
1019
+ raw: str,
1020
+ cooked: str
1021
+ }, false));
1022
+ }
1023
+ } else {
1024
+ if (getOriginRegex().test(str)) {
1025
+ expressions.push(createI18nTranslator({
1026
+ value: str,
1027
+ isExpression: true,
1028
+ insertOption: option
1029
+ }));
1030
+ } else {
1031
+ quasis.push(types__namespace.templateElement({
1032
+ raw: str,
1033
+ cooked: str
1034
+ }, false));
1035
+ }
1036
+ }
1037
+ });
1038
+ if (quasis.length === expressions.length) {
1039
+ quasis.push(types__namespace.templateElement({
1040
+ raw: '',
1041
+ cooked: ''
1042
+ }, true));
1043
+ } else if (quasis.length > expressions.length) {
1044
+ quasis[quasis.length - 1].tail = true;
1379
1045
  }
1046
+ const templateLiteral = types__namespace.templateLiteral(quasis, expressions);
1047
+ const deepScanCall = types__namespace.callExpression(types__namespace.identifier('$deepScan'), [templateLiteral]);
1048
+ // 打印转换结果
1049
+ // console.log('deepScanCall', (generate as any).default(deepScanCall).code)
1050
+ return deepScanCall;
1051
+ }
1380
1052
 
1381
- // 生成缩进字符
1382
- const indentType = generateIndentation(config.size || indent.size, indent.char);
1383
-
1384
- // 格式化 JSON 字符串
1385
- return formatJSON(jsonString, indentType);
1053
+ // 上传到远程服务器
1054
+ // import { getAppCode, saveConfig, convertToUploadJson } from './config.js'
1055
+ async function upload(json) {
1056
+ const {
1057
+ appCode,
1058
+ targetLangList
1059
+ } = exports.option;
1060
+ let data = [];
1061
+ _.map(json, (value, key) => {
1062
+ _.map(targetLangList, item => {
1063
+ const parts = item.split('-');
1064
+ const lang = parts[0] + '-' + parts[1].toUpperCase();
1065
+ data.push({
1066
+ page: 'common',
1067
+ key,
1068
+ lang,
1069
+ value: _.get(value, item, key)
1070
+ });
1071
+ });
1072
+ });
1073
+ const res = await axios.post('http://192.168.0.104:8108/i18n-web/kv_translate/batch', {
1074
+ appCode,
1075
+ data
1076
+ }, {
1077
+ headers: {
1078
+ 'Content-Type': 'application/json',
1079
+ Authorization: '728635D658AE11F18F33000C29A621CA'
1080
+ }
1081
+ });
1082
+ if (res.status === 200 && res.data.result === 0) {
1083
+ console.info('上传翻译服务成功✔');
1084
+ } else {
1085
+ console.error('上传翻译服务失败❌');
1086
+ console.log(`请求数据为:`, JSON.stringify(data));
1087
+ console.log(`返回报错信息为:`, res.data);
1088
+ }
1386
1089
  }
1387
1090
 
1091
+ var upload$1 = /*#__PURE__*/Object.freeze({
1092
+ __proto__: null,
1093
+ upload: upload
1094
+ });
1095
+
1388
1096
  /**
1389
1097
  * @description: 新建国际化配置文件夹
1390
1098
  * @return {*}
@@ -1605,6 +1313,32 @@ var file = /*#__PURE__*/Object.freeze({
1605
1313
  const SEPARATOR = '\n┇┇┇\n';
1606
1314
  const SPLIT_SEPARATOR_REGEX = /\n┇ *┇ *┇\n/;
1607
1315
  let langObj = {};
1316
+ function getLangKeyByValue(value) {
1317
+ return Object.entries(langObj).find(_ref => {
1318
+ let [, oldValue] = _ref;
1319
+ return oldValue === value;
1320
+ })?.[0];
1321
+ }
1322
+ function resolveLangKey(key, value) {
1323
+ return getLangKeyByValue(value) || key;
1324
+ }
1325
+ function isTemplateValue(value) {
1326
+ return /\$\{[^}]+}/.test(value);
1327
+ }
1328
+ function getTemplateStaticSegments(value) {
1329
+ return value.split(/\$\{[^}]+}/g).filter(item => item);
1330
+ }
1331
+ function isTemplateStaticSegment(value) {
1332
+ return Object.values(langObj).some(oldValue => isTemplateValue(oldValue) && getTemplateStaticSegments(oldValue).includes(value));
1333
+ }
1334
+ function removeTemplateStaticSegments(value) {
1335
+ const segments = new Set(getTemplateStaticSegments(value));
1336
+ Object.keys(langObj).forEach(key => {
1337
+ if (segments.has(langObj[key])) {
1338
+ delete langObj[key];
1339
+ }
1340
+ });
1341
+ }
1608
1342
 
1609
1343
  /**
1610
1344
  * @description: 设置翻译对象属性
@@ -1613,6 +1347,15 @@ let langObj = {};
1613
1347
  * @return {*}
1614
1348
  */
1615
1349
  function setLangObj(key, value) {
1350
+ if (isTemplateValue(value)) {
1351
+ removeTemplateStaticSegments(value);
1352
+ } else if (isTemplateStaticSegment(value)) {
1353
+ return;
1354
+ }
1355
+ const existingKey = getLangKeyByValue(value);
1356
+ if (existingKey && existingKey !== key) {
1357
+ return;
1358
+ }
1616
1359
  if (!langObj[key]) {
1617
1360
  langObj[key] = value;
1618
1361
  }
@@ -1632,7 +1375,7 @@ function getLangObj() {
1632
1375
  * @return {*}
1633
1376
  */
1634
1377
  function initLangObj(obj) {
1635
- if (!Object.keys(langObj)) {
1378
+ if (!Object.keys(langObj).length) {
1636
1379
  langObj = obj;
1637
1380
  }
1638
1381
  }
@@ -1676,7 +1419,6 @@ async function autoTranslate() {
1676
1419
 
1677
1420
  // 无新内容提前退出
1678
1421
  if (Object.keys(transLangObj).length === 0) {
1679
- console.info('✅ 当前没有需要翻译的新内容');
1680
1422
  return;
1681
1423
  }
1682
1424
 
@@ -1814,378 +1556,684 @@ async function completionTranslateAndWriteConfigFile(langObj, curLangObj, transl
1814
1556
  } catch (error) {
1815
1557
  console.error('❌JSON配置文件写入失败' + error);
1816
1558
  }
1817
- console.info('新增语言翻译补全成功⭐️⭐️⭐️');
1559
+ console.info('新增语言翻译补全成功⭐️⭐️⭐️');
1560
+ }
1561
+
1562
+ // 分块翻译流程函数
1563
+ async function translateChunks(transLangObj, translateKey) {
1564
+ const {
1565
+ translator
1566
+ } = exports.option;
1567
+ const placeholders = [];
1568
+ const protectedValues = Object.values(transLangObj).map(value => protectInterpolationPlaceholders(value, placeholders));
1569
+ // 获取分块后的文本列表
1570
+ const translationChunks = createTextSplitter(protectedValues, translator.option.maxChunkSize);
1571
+ // 并行执行分块翻译
1572
+ const translatePromises = [];
1573
+ for (let i = 0; i < translationChunks.length; i++) {
1574
+ translatePromises.push(translator.translate(translationChunks[i], exports.option.originLang, translateKey, SEPARATOR));
1575
+ }
1576
+
1577
+ // 等待所有分块完成并合并结果
1578
+ const chunkResults = await Promise.all(translatePromises);
1579
+ return chunkResults.map(item => {
1580
+ item = restoreInterpolationPlaceholders(item, placeholders);
1581
+ // 提取分割逻辑到单独的函数中,提高代码复用性
1582
+ const splitTranslation = (text, separatorRegex) => {
1583
+ return text.split(separatorRegex).map(v => v.trim());
1584
+ };
1585
+
1586
+ // 分割符可能会被翻译,所以这里做了兼容处理
1587
+ if (SPLIT_SEPARATOR_REGEX.test(item)) {
1588
+ return splitTranslation(item, SPLIT_SEPARATOR_REGEX);
1589
+ } else {
1590
+ const lines = item.split('\n');
1591
+ const separator = lines.find(line => line.length === 3);
1592
+ let value = [];
1593
+ if (separator) {
1594
+ value = splitTranslation(item, new RegExp(`\\n${separator}\\n`));
1595
+ }
1596
+ const realList = value.filter(Boolean);
1597
+ if (realList.length > 1) {
1598
+ return realList;
1599
+ }
1600
+ return splitTranslation(item, SPLIT_SEPARATOR_REGEX);
1601
+ }
1602
+ }).flat();
1603
+ }
1604
+ function protectInterpolationPlaceholders(value, placeholders) {
1605
+ return value.replace(/\$\{[^}]*\}/g, match => {
1606
+ const index = placeholders.push(match) - 1;
1607
+ return `__GC_I18N_PH_${index}__`;
1608
+ });
1609
+ }
1610
+ function restoreInterpolationPlaceholders(value, placeholders) {
1611
+ return value.replace(/__GC_I18N_PH_(\d+)__/g, (match, index) => {
1612
+ return placeholders[Number(index)] || match;
1613
+ });
1614
+ }
1615
+ /**
1616
+ * @description: 清理多余的翻译配置JSON文件
1617
+ * @return {void} 无返回值
1618
+ */
1619
+ function cleanupUnusedTranslations() {
1620
+ if (!exports.option.isClear) return;
1621
+ console.log('🧹 进入清理流程');
1622
+ // 获取当前的语言对象,如果不存在则使用空对象
1623
+ const langObj = getLangObj() || {};
1624
+
1625
+ // 创建一个Set用于存储当前语言对象的所有key,便于快速查找
1626
+ let langSet = new Set(Object.keys(langObj));
1627
+
1628
+ // 获取基础对象:优先使用传入的insertObj,否则从翻译文件中读取
1629
+ const baseObj = JSON.parse(getLangTranslateJSONFile());
1630
+
1631
+ // 获取基础对象的所有key
1632
+ const baseObjKeys = Object.keys(baseObj);
1633
+ // 遍历所有key,删除在当前语言对象中不存在的配置
1634
+ baseObjKeys.forEach(key => {
1635
+ if (!langSet.has(key)) {
1636
+ baseObj[key] && delete baseObj[key];
1637
+ }
1638
+ });
1639
+ setLangTranslateJSONFile(baseObj);
1640
+ }
1641
+
1642
+ var translate = /*#__PURE__*/Object.freeze({
1643
+ __proto__: null,
1644
+ SEPARATOR: SEPARATOR,
1645
+ SPLIT_SEPARATOR_REGEX: SPLIT_SEPARATOR_REGEX,
1646
+ autoTranslate: autoTranslate,
1647
+ cleanupUnusedTranslations: cleanupUnusedTranslations,
1648
+ completionTranslateAndWriteConfigFile: completionTranslateAndWriteConfigFile,
1649
+ getLangKeyByValue: getLangKeyByValue,
1650
+ getLangObj: getLangObj,
1651
+ initLangObj: initLangObj,
1652
+ get langObj () { return langObj; },
1653
+ languageConfigCompletion: languageConfigCompletion,
1654
+ resolveLangKey: resolveLangKey,
1655
+ setLangObj: setLangObj
1656
+ });
1657
+
1658
+ /*
1659
+ * @Author: xiaoshanwen
1660
+ * @Date: 2023-10-11 10:01:43
1661
+ * @LastEditTime: 2025-03-28 19:07:17
1662
+ * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/base.ts
1663
+ */
1664
+ function getOriginRegex() {
1665
+ const originLang = FunctionFactoryOption.originLang;
1666
+ return REGEX_MAP[originLang];
1667
+ }
1668
+
1669
+ /**
1670
+ * @description: 是否包含来源语言字符
1671
+ * @param {string} code
1672
+ * @return {*}
1673
+ */
1674
+ function hasOriginSymbols(code) {
1675
+ return getOriginRegex().test(code);
1676
+ }
1677
+
1678
+ /**
1679
+ * @description: 过滤注释
1680
+ * @param {string} code
1681
+ * @return {*}
1682
+ */
1683
+ const removeComments = function (code) {
1684
+ // 使用正则表达式匹配并删除单行注释
1685
+ code = code.replace(/\/\/.*?\n/g, '');
1686
+ // 使用正则表达式匹配并删除多行注释
1687
+ code = code.replace(/\/\*[\s\S]*?\*\//g, '');
1688
+ // 使用正则表达式匹配并删除HTML注释
1689
+ code = code.replace(/<!--[\s\S]*?-->/g, '');
1690
+ return code;
1691
+ };
1692
+
1693
+ /**
1694
+ * @description: 用于判断提供的值是否符合正则表达式数组中的任一规则,符合则跳过
1695
+ * @param {*} value
1696
+ * @param {*} regexArray
1697
+ * @return {*}
1698
+ */
1699
+ function checkAgainstRegexArray(value, regexArray) {
1700
+ for (let i = 0; i < regexArray.length; i++) {
1701
+ const regex = typeof regexArray[i] === 'string' ? new RegExp(regexArray[i]) : regexArray[i];
1702
+ if (regex.test(value)) {
1703
+ return true; // 如果符合任何一个规则,返回 true
1704
+ }
1705
+ }
1706
+ return false; // 如果所有规则都不符合,返回 false
1707
+ }
1708
+
1709
+ /**
1710
+ * @description: 用于解析抽象语法树中的调用表达式,并提取出调用的名称,如a.b.c() 取 c。
1711
+ * @param {any} node
1712
+ * @return {*}
1713
+ */
1714
+ function extractFunctionName(node) {
1715
+ let callName = '';
1716
+ function callObjName(callObj, name) {
1717
+ name += '.' + callObj.property.name;
1718
+ if (types.isMemberExpression(callObj.object)) {
1719
+ // isMemberExpression: 是否是成员表达式
1720
+ return callObjName(callObj.object, name);
1721
+ }
1722
+ name = callObj.object.name + name;
1723
+ return name;
1724
+ }
1725
+ if (types.isCallExpression(node)) {
1726
+ // isCallExpression: 是否是调用表达式
1727
+ if (types.isMemberExpression(node.callee)) {
1728
+ callName = callObjName(node.callee, '');
1729
+ } else {
1730
+ callName = node.callee.name || '';
1731
+ }
1732
+ }
1733
+ return callName;
1734
+ }
1735
+
1736
+ /**
1737
+ * @description: 提取文件的中文部分
1738
+ * @param {string} fileContent
1739
+ * @return {*}
1740
+ */
1741
+ const extractCnStrings = fileContent => {
1742
+ const regex = /[^\x00-\xff]+/g;
1743
+ return extractStrings(fileContent, regex);
1744
+ };
1745
+
1746
+ /**
1747
+ * @description: 提取文件指定部分内容
1748
+ * @param {string} fileContent
1749
+ * @param {any} regex
1750
+ * @return {*}
1751
+ */
1752
+ function extractStrings(fileContent, regex) {
1753
+ const matches = fileContent.match(regex);
1754
+ return matches ? matches.filter((item, index) => matches.indexOf(item) === index) : [];
1755
+ }
1756
+
1757
+ /**
1758
+ * @description: 生成i8n翻译函数
1759
+ * @param {string} value
1760
+ * @param {boolean} isExpression
1761
+ * @param {string} key
1762
+ * @return {*}
1763
+ */
1764
+ function createI18nTranslator(createOption) {
1765
+ const {
1766
+ value,
1767
+ isExpression = false,
1768
+ key,
1769
+ insertOption
1770
+ } = createOption;
1771
+
1772
+ // 从全局配置对象 option 中获取命名空间
1773
+ const nameSpace = exports.option.namespace;
1774
+ const {
1775
+ trimmedValue,
1776
+ valStr
1777
+ } = normalizeTranslateValue(value);
1778
+ // 若 key 存在则使用 key,否则调用 generateId 函数根据 valStr 生成唯一的键
1779
+ const generatedKey = key || resolveLangKey(generateId(valStr), trimmedValue);
1780
+ // 提取公共配置对象,避免重复代码
1781
+ const config = {
1782
+ option: exports.option,
1783
+ hash: generatedKey,
1784
+ value: trimmedValue,
1785
+ uncodeValue: valStr,
1786
+ namespace: nameSpace
1787
+ };
1788
+ if (exports.option.translateExtends) {
1789
+ const {
1790
+ handleCodeCall,
1791
+ handleCodeString
1792
+ } = exports.option.translateExtends;
1793
+ return isExpression ? handleCodeCall(config, insertOption) : handleCodeString(config, insertOption);
1794
+ }
1795
+ if (isExpression) {
1796
+ const valueExp = types.stringLiteral(trimmedValue);
1797
+ valueExp.extra = {
1798
+ raw: `'${valStr}'`,
1799
+ // 防止转码为unicode
1800
+ rawValue: trimmedValue
1801
+ };
1802
+ return exports.option.useValueAsKey ? types.callExpression(types.identifier(exports.option.translateKey), [valueExp]) : types.callExpression(types.identifier(exports.option.translateKey), [types.stringLiteral(generatedKey), valueExp]);
1803
+ } else {
1804
+ return `${exports.option.translateKey}('${generatedKey}','${valStr}')`;
1805
+ }
1806
+ }
1807
+ function normalizeTranslateValue(value) {
1808
+ const trimmedValue = exports.option.isClearSpace ? value : value.trim();
1809
+ const valStr = trimmedValue.replace(/'/g, '"').replace(/(\n)/g, '\\n');
1810
+ return {
1811
+ trimmedValue,
1812
+ valStr
1813
+ };
1818
1814
  }
1819
1815
 
1820
- // 分块翻译流程函数
1821
- async function translateChunks(transLangObj, translateKey) {
1822
- const {
1823
- translator
1824
- } = exports.option;
1825
- // 获取分块后的文本列表
1826
- const translationChunks = createTextSplitter(Object.values(transLangObj), translator.option.maxChunkSize);
1827
- // 并行执行分块翻译
1828
- const translatePromises = [];
1829
- for (let i = 0; i < translationChunks.length; i++) {
1830
- translatePromises.push(translator.translate(translationChunks[i], exports.option.originLang, translateKey, SEPARATOR));
1816
+ /**
1817
+ * @description: 生成唯一id
1818
+ * @param {string} key
1819
+ * @return {*}
1820
+ */
1821
+ function generateId(key) {
1822
+ let hash = 0;
1823
+ for (let i = 0; i < key.length; i++) {
1824
+ const charCode = key.charCodeAt(i);
1825
+ hash = (hash << 5) - hash + charCode;
1826
+ hash = hash & hash;
1831
1827
  }
1828
+ const id = Math.abs(hash).toString(36) + key.length.toString(36);
1829
+ return id;
1830
+ }
1832
1831
 
1833
- // 等待所有分块完成并合并结果
1834
- const chunkResults = await Promise.all(translatePromises);
1835
- return chunkResults.map(item => {
1836
- // 提取分割逻辑到单独的函数中,提高代码复用性
1837
- const splitTranslation = (text, separatorRegex) => {
1838
- return text.split(separatorRegex).map(v => v.trim());
1839
- };
1832
+ /**
1833
+ * @description: unicode转普通字符串
1834
+ * @param {string} str
1835
+ * @return {*}
1836
+ */
1837
+ const unicodeToString = str => {
1838
+ return str.replace(/\\u[\dA-Fa-f]{4}/g, match => {
1839
+ return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16));
1840
+ });
1841
+ };
1840
1842
 
1841
- // 分割符可能会被翻译,所以这里做了兼容处理
1842
- if (SPLIT_SEPARATOR_REGEX.test(item)) {
1843
- return splitTranslation(item, SPLIT_SEPARATOR_REGEX);
1844
- } else {
1845
- const lines = item.split('\n');
1846
- const separator = lines.find(line => line.length === 3);
1847
- let value = [];
1848
- if (separator) {
1849
- value = splitTranslation(item, new RegExp(`\\n${separator}\\n`));
1850
- }
1851
- const realList = value.filter(Boolean);
1852
- if (realList.length > 1) {
1853
- return realList;
1854
- }
1855
- return splitTranslation(item, SPLIT_SEPARATOR_REGEX);
1856
- }
1857
- }).flat();
1858
- }
1859
1843
  /**
1860
- * @description: 清理多余的翻译配置JSON文件
1861
- * @return {void} 无返回值
1844
+ * @description: 有道翻译 标识截取
1845
+ * @param {string} q
1846
+ * @return {*}
1862
1847
  */
1863
- function cleanupUnusedTranslations() {
1864
- if (!exports.option.isClear) return;
1865
- console.log('🧹 进入清理流程');
1866
- // 获取当前的语言对象,如果不存在则使用空对象
1867
- const langObj = getLangObj() || {};
1848
+ function truncate(q) {
1849
+ // 检查输入字符串的长度
1850
+ if (q.length <= 20) {
1851
+ // 如果长度小于等于20,直接返回原字符串
1852
+ return q;
1853
+ } else {
1854
+ // 如果长度大于20,截取前10个字符和后10个字符,并在中间插入长度信息
1855
+ const len = q.length;
1856
+ return q.substring(0, 10) + len + q.substring(len - 10);
1857
+ }
1858
+ }
1868
1859
 
1869
- // 创建一个Set用于存储当前语言对象的所有key,便于快速查找
1870
- let langSet = new Set(Object.keys(langObj));
1860
+ // 导出一个深拷贝函数,用于克隆对象
1861
+ function cloneDeep(value) {
1862
+ let cache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new WeakMap();
1863
+ // 处理基本类型和 null
1864
+ if (typeof value !== 'object' || value === null) {
1865
+ return value;
1866
+ }
1871
1867
 
1872
- // 获取基础对象:优先使用传入的insertObj,否则从翻译文件中读取
1873
- const baseObj = JSON.parse(getLangTranslateJSONFile());
1868
+ // 处理循环引用
1869
+ if (cache.has(value)) {
1870
+ return cache.get(value);
1871
+ }
1874
1872
 
1875
- // 获取基础对象的所有key
1876
- const baseObjKeys = Object.keys(baseObj);
1877
- // 遍历所有key,删除在当前语言对象中不存在的配置
1878
- baseObjKeys.forEach(key => {
1879
- if (!langSet.has(key)) {
1880
- baseObj[key] && delete baseObj[key];
1881
- }
1882
- });
1883
- setLangTranslateJSONFile(baseObj);
1873
+ // 处理特殊对象类型
1874
+ if (value instanceof Date) {
1875
+ return new Date(value);
1876
+ }
1877
+ if (value instanceof RegExp) {
1878
+ return new RegExp(value.source, value.flags);
1879
+ }
1880
+
1881
+ // 初始化克隆容器
1882
+ const clone = Array.isArray(value) ? [] : {};
1883
+
1884
+ // 缓存对象防止循环引用
1885
+ cache.set(value, clone);
1886
+
1887
+ // 处理 Symbol 和普通键的枚举
1888
+ const keys = [...Object.keys(value), ...Object.getOwnPropertySymbols(value).filter(sym => value.propertyIsEnumerable(sym))];
1889
+
1890
+ // 递归克隆属性
1891
+ for (const key of keys) {
1892
+ clone[key] = cloneDeep(value[key], cache);
1893
+ }
1894
+ return clone;
1884
1895
  }
1885
1896
 
1886
- var translate = /*#__PURE__*/Object.freeze({
1897
+ var base = /*#__PURE__*/Object.freeze({
1887
1898
  __proto__: null,
1888
- SEPARATOR: SEPARATOR,
1889
- SPLIT_SEPARATOR_REGEX: SPLIT_SEPARATOR_REGEX,
1890
- autoTranslate: autoTranslate,
1891
- cleanupUnusedTranslations: cleanupUnusedTranslations,
1892
- completionTranslateAndWriteConfigFile: completionTranslateAndWriteConfigFile,
1893
- getLangObj: getLangObj,
1894
- initLangObj: initLangObj,
1895
- get langObj () { return langObj; },
1896
- languageConfigCompletion: languageConfigCompletion,
1897
- setLangObj: setLangObj
1899
+ checkAgainstRegexArray: checkAgainstRegexArray,
1900
+ cloneDeep: cloneDeep,
1901
+ createI18nTranslator: createI18nTranslator,
1902
+ extractCnStrings: extractCnStrings,
1903
+ extractFunctionName: extractFunctionName,
1904
+ extractStrings: extractStrings,
1905
+ generateId: generateId,
1906
+ getOriginRegex: getOriginRegex,
1907
+ hasOriginSymbols: hasOriginSymbols,
1908
+ normalizeTranslateValue: normalizeTranslateValue,
1909
+ removeComments: removeComments,
1910
+ truncate: truncate,
1911
+ unicodeToString: unicodeToString
1898
1912
  });
1899
1913
 
1900
- /*
1901
- * @Date: 2025-02-17 17:11:26
1902
- * @LastEditors: xiaoshan
1903
- * @LastEditTime: 2025-02-17 18:00:37
1904
- * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/chunk.ts
1905
- */
1906
-
1914
+ // 代码灵感来自https://github.com/dadidi9900/auto-plugins-json-translate/blob/main/src/services/translationService.ts
1907
1915
  /**
1908
- * 智能文本分块处理器
1909
- * @param values 待分块的原始文本数组
1910
- * @param maxChunkSize 最大分块长度
1911
- * @returns 包含分块文本和重组方法的对象
1912
- *
1913
- * 功能特性:
1914
- * 1. 自动合并小文本为最大可能块
1915
- * 2. 处理超长文本并给出警告
1916
- * 3. 保证块长度不超过限制
1917
- * 4. 保留原始顺序和分隔符语义
1916
+ * 火山引擎翻译器,内置豆包、deepseek等模型
1917
+ *
1918
+ * 火山引擎大模型介绍:https://www.volcengine.com/docs/82379/1099455
1919
+ *
1920
+ * api文档:https://www.volcengine.com/docs/82379/1298454
1921
+ *
1922
+ * 使用方式:
1923
+ * ```ts
1924
+ * vitePluginsAutoI18n({
1925
+ ...
1926
+ translator: new VolcengineTranslator({
1927
+ apiKey: '你申请的apiKey',
1928
+ model: '你要调用的模型,如:`doubao-1-5-pro-32k-250115`,请确保使用前已在控制台开通了对应模型'
1929
+ })
1930
+ })
1931
+ * ```
1918
1932
  */
1919
- function createTextSplitter(values, maxChunkSize) {
1920
- // 分隔符定义(用于合并/拆分时保持语义)
1921
- const SEP_LENGTH = SEPARATOR.length;
1922
-
1923
- // 结果存储和缓冲区
1924
- const result = []; // 最终分块结果
1925
- let buffer = []; // 当前累积块缓冲区
1926
- let currentSize = 0; // 当前缓冲区字符数(含分隔符)
1927
-
1928
- /**
1929
- * 提交缓冲区内容到结果集
1930
- * - 将缓冲区内容用分隔符连接
1931
- * - 重置缓冲区和计数器
1932
- */
1933
- const commitBuffer = () => {
1934
- if (buffer.length > 0) {
1935
- // 计算实际连接长度用于验证
1936
- const actualLength = buffer.join(SEPARATOR).length;
1937
- if (actualLength > maxChunkSize) {
1938
- console.warn(`缓冲区提交异常:生成块长度 ${actualLength} 超过限制`);
1939
- }
1940
- result.push(buffer.join(SEPARATOR));
1941
- buffer = [];
1942
- currentSize = 0;
1943
- }
1944
- };
1945
-
1946
- // 主处理循环:遍历所有原始文本项
1947
- for (const value of values) {
1948
- // 计算需要新增的空间:文本长度 + 分隔符(非首项)
1949
- const neededSpace = value.length + (buffer.length > 0 ? SEP_LENGTH : 0);
1950
-
1951
- // ─── 超长文本处理策略 ───
1952
- if (value.length > maxChunkSize) {
1953
- // 优先提交现有缓冲区内容
1954
- if (buffer.length > 0) commitBuffer();
1955
-
1956
- /**
1957
- * 超长文本处理逻辑:
1958
- * - 长度超过1.5倍限制时发出强警告
1959
- * - 强制单独成块(即使超过限制)
1960
- * - 后续需要特殊处理这些异常块
1961
- */
1962
- if (value.length > maxChunkSize * 1.5) {
1963
- console.warn(`超长文本告警:检测到长度 ${value.length} 字符的文本项,可能影响翻译质量`);
1964
- }
1965
- // 结果直接新增一个超长文本
1966
- result.push(value);
1967
- continue;
1968
- }
1933
+ class VolcengineTranslator extends Translator {
1934
+ constructor(option) {
1935
+ super({
1936
+ name: '火山引擎ai翻译',
1937
+ fetchMethod: async (text, fromKey, toKey, separator) => {
1938
+ let salt = new Date().getTime();
1939
+ const textArr = text.split(separator);
1940
+ const sourceMap = Object.fromEntries(textArr.map(text => [generateId(text), text]));
1941
+ const data = {
1942
+ model: option.model,
1943
+ messages: [{
1944
+ role: 'system',
1945
+ content: `
1946
+ ###
1947
+ 假如你是一个无情的翻译接口,你将根据一个文本组成的JSON对象,来解决将数组每个成员从源语言A翻译成目标语言B并返回翻译后的JSON对象的任务。需要注意的是,待翻译的文本均来自一个${option.desc ? option.desc + '的' : ''}web平台,遇到歧义时需要做好处理。根据以下规则一步步执行:
1948
+ 1. 明确源语言A和目标语言B。
1949
+ 2. 对JSON对象中数组的每个成员进行从源语言A到目标语言B的翻译。
1950
+ 3. 将翻译后的内容以JSON对象格式返回,确保返回的内容可以被JSON.parse解析。
1969
1951
 
1970
- // ─── 正常分块逻辑 ───
1971
- // 空间不足时提交当前缓冲区
1972
- if (currentSize + neededSpace > maxChunkSize) {
1973
- commitBuffer();
1974
- }
1952
+ 参考例子:
1953
+ 示例1:
1954
+ 输入:zh-cn -> en { "awfgx": "你好", "qwfga": "世界" }
1955
+ 输出:{ "awfgx": "Hello", "qwfga": "World" }
1975
1956
 
1976
- // 更新缓冲区状态(累加长度需包含分隔符)
1977
- currentSize += neededSpace;
1978
- buffer.push(value);
1979
- }
1957
+ 示例2:
1958
+ 输入:de -> fr { "gweaq": "Hallo", "wtrts": "Welt" }
1959
+ 输出:{ "gweaq": "Bonjour", "wtrts": "Monde" }
1980
1960
 
1981
- // 提交最终未完成的缓冲区内容
1982
- commitBuffer();
1961
+ 请回答问题:
1962
+ 输入:源语言A -> 目标语言B { "wghhj": "XXX" }
1963
+ 输出:
1983
1964
 
1984
- // 返回分块结果
1985
- return result;
1965
+ 要求:
1966
+ 1 以JSON对象格式输出
1967
+ 2 JSON对象中每个成员为翻译后的内容
1968
+ ###
1969
+ `
1970
+ }, {
1971
+ role: 'user',
1972
+ content: `${fromKey} -> ${toKey} ${JSON.stringify(sourceMap)}`
1973
+ }],
1974
+ ...(option.insertOption || {})
1975
+ };
1976
+ const response = await axios.post(`https://ark.cn-beijing.volces.com/api/v3/chat/completions?t=${salt}`, data, {
1977
+ headers: {
1978
+ 'Content-Type': 'application/json',
1979
+ Authorization: `Bearer ${option.apiKey}`
1980
+ },
1981
+ proxy: option.proxy
1982
+ });
1983
+ let resultTextArr = Array.from(textArr).fill('');
1984
+ const content = response.data.choices[0].message.content;
1985
+ try {
1986
+ let resultMap;
1987
+ try {
1988
+ resultMap = JSON.parse(content);
1989
+ } catch (error) {
1990
+ throw new Error('大模型返回文本解析失败');
1991
+ }
1992
+ if (typeof resultMap !== 'object' || !resultMap) {
1993
+ throw new Error('大模型返回文本解析后类型不正确');
1994
+ }
1995
+ const isMiss = Object.keys(resultMap).some(key => !(key in sourceMap));
1996
+ if (isMiss) {
1997
+ throw new Error('大模型返回文本内容不完整');
1998
+ }
1999
+ resultTextArr = textArr.map(text => resultMap[generateId(text)]); // 用textArr遍历,保证顺序
2000
+ } catch (error) {
2001
+ const message = error instanceof Error ? error.message : '未知错误';
2002
+ console.warn('⚠', message);
2003
+ console.warn('⚠ 返回的文本内容:', content);
2004
+ console.warn('⚠ 原文本内容:', JSON.stringify(sourceMap));
2005
+ }
2006
+ return resultTextArr.join(separator);
2007
+ },
2008
+ onError: (error, cb) => {
2009
+ cb(error);
2010
+ console.error('请确保在火山引擎控制台开通了对应模型,且有足够的token余额。控制台地址:https://console.volcengine.com/ark/');
2011
+ },
2012
+ maxChunkSize: 1000,
2013
+ // 太长可能会导致返回文本不完整
2014
+ interval: option.interval ?? 1000
2015
+ });
2016
+ }
1986
2017
  }
1987
2018
 
1988
2019
  /*
1989
- * @Date: 2025-03-26 20:28:21
1990
- * @LastEditors: xiaoshan
1991
- * @LastEditTime: 2025-03-31 10:29:49
1992
- * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/split.ts
2020
+ * @Author: xiaoshanwen
2021
+ * @Date: 2024-04-03 18:12:45
2022
+ * @LastEditTime: 2024-04-03 18:33:53
2023
+ * @FilePath: /i18n_translation_vite/autoI18nPluginCore/src/enums/translate.ts
1993
2024
  */
1994
- // 插件核心文件
1995
- // 字符串切割与转换函数
1996
- // import generate from '@babel/generator'
2025
+ let TranslateApiEnum = /*#__PURE__*/function (TranslateApiEnum) {
2026
+ TranslateApiEnum["google"] = "Google";
2027
+ TranslateApiEnum["youdao"] = "Youdao";
2028
+ return TranslateApiEnum;
2029
+ }({});
1997
2030
 
1998
- // todo 这个切割函数可以优化,性能可能很差
1999
- /**
2000
- * 根据正则表达式分割字符串,并将符合正则的连续字符拼接起来。
2001
- * @param str - 要分割的字符串。
2002
- * @param separatorRegex - 用于分割字符串的正则表达式。
2003
- * @returns 分割并拼接后的字符串数组。
2031
+ /*
2032
+ * @Date: 2025-03-16 14:12:30
2033
+ * @LastEditors: xiaoshan
2034
+ * @LastEditTime: 2025-03-16 14:13:42
2035
+ * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/enums/option.ts
2004
2036
  */
2005
2037
  /**
2006
- * 这个函数的主要功能是根据给定的正则表达式分割字符串,并对分割结果进行特殊处理。
2007
- * 处理过程分为三个主要步骤:
2008
- *
2009
- * 1. 首先根据分隔符正则和标点符号正则进行初步分割
2010
- * 2. 然后将连续的标点符号和符合分隔符正则的部分重新连接
2011
- * 3. 最后将不符合分隔符正则的相邻部分合并
2012
- *
2013
- * @param str - 需要分割的源字符串
2014
- * @param separatorRegex - 用于分割的正则表达式
2015
- * @returns 处理后的字符串数组
2038
+ * 翻译类型枚举
2016
2039
  */
2017
- function splitByRegex(str, separatorRegex) {
2018
- if (str.includes('\n')) console.log(str, separatorRegex);
2019
-
2020
- // 定义标点符号的正则表达式
2021
- const punctuationRegex = /[,。?!《》,..:!?""'';'"、0-9\n\r\t\v\f]/;
2022
- // 创建一个新的正则表达式,用于分割字符串
2023
- const splitRegex = new RegExp(`(${separatorRegex.source}|${punctuationRegex.source})`, separatorRegex.flags);
2024
-
2025
- // 使用正则表达式分割字符串,并过滤掉空字符串
2026
- const splitArr = str.split(splitRegex).filter(Boolean);
2027
- const result = [];
2028
- let currentMatch = '';
2040
+ let TranslateTypeEnum = /*#__PURE__*/function (TranslateTypeEnum) {
2041
+ TranslateTypeEnum["FULL_AUTO"] = "full-auto";
2042
+ TranslateTypeEnum["SEMI_AUTO"] = "semi-auto";
2043
+ return TranslateTypeEnum;
2044
+ }({});
2029
2045
 
2030
- // 定义连接标点符号的正则表达式
2031
- const connectPunctuationRegex = /[,。?!《》,..:!?;'"、0-9]/;
2032
- // 创建一个新的正则表达式,用于检测是否需要连接
2033
- const connectRegex = new RegExp(`(${separatorRegex.source}|${connectPunctuationRegex.source})`, separatorRegex.flags);
2046
+ /*
2047
+ * @Author: xiaoshanwen
2048
+ * @Date: 2023-10-26 17:34:47
2049
+ * @LastEditTime: 2025-03-31 19:58:37
2050
+ * @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/option.ts
2051
+ */
2034
2052
 
2035
- // 遍历分割后的数组
2036
- for (const item of splitArr) {
2037
- if (connectRegex.test(item)) {
2038
- // 如果当前项符合连接条件,则将其添加到当前匹配字符串中
2039
- currentMatch += item;
2040
- } else {
2041
- // 如果当前匹配字符串不为空,则将其添加到结果数组中
2042
- if (currentMatch) {
2043
- result.push(currentMatch);
2044
- currentMatch = '';
2053
+ const EXCLUDED_CALL = ['$deepScan', 'console.info', 'console.warn', 'console.error', '$i8n', 'console.log', '$t', 'require', '$$i8n', '$$t', '_createCommentVNode'];
2054
+ /**
2055
+ * 默认插件配置选项
2056
+ */
2057
+ const DEFAULT_OPTION = {
2058
+ appCode: '',
2059
+ /** 是否启用插件,默认启用 */
2060
+ enabled: true,
2061
+ /** 翻译调用函数,默认为 $t */
2062
+ translateKey: '$t',
2063
+ /** 标记不翻译调用函数列表,避免某些调用被错误翻译 */
2064
+ excludedCall: [],
2065
+ /** 标记不用翻译的字符串模式数组,默认是匹配文件扩展名 */
2066
+ excludedPattern: [/\.\w+$/],
2067
+ /** 排查不需要翻译的目录下的文件路径(黑名单), 默认不处理node_modules */
2068
+ excludedPath: ['node_modules'],
2069
+ /** 指定需要翻译文件的目录路径正则(白名单) */
2070
+ includePath: [/src\//, /src\\/],
2071
+ /** 配置文件生成位置,默认为 './lang' */
2072
+ globalPath: './lang',
2073
+ /** 打包后生成文件的位置,例如 './dist/assets' */
2074
+ distPath: '',
2075
+ /** 打包后生成文件的主文件名称,默认是 'index' */
2076
+ distKey: 'index',
2077
+ /** 来源语言,默认是中文 */
2078
+ originLang: OriginLangKeyEnum.ZH,
2079
+ /** 翻译目标语言列表,默认包含英文 */
2080
+ targetLangList: ['en'],
2081
+ /** 语言key,用于请求谷歌api和生成配置文件下对应语言的内容文件 */
2082
+ langKey: [],
2083
+ /** 命名空间,防止全局命名冲突 */
2084
+ namespace: 'lang',
2085
+ /** 单位代码 */
2086
+ orgCode: 'GREENCLOUD',
2087
+ /** 是否启用源码的值作为key */
2088
+ useValueAsKey: false,
2089
+ /** 是否在构建结束之后将最新的翻译重新打包到主包中,默认不打包 */
2090
+ buildToDist: false,
2091
+ /** 是否启用上传翻译文件到服务器,默认开启 */
2092
+ uploadEnabled: true,
2093
+ /** 翻译器,决定自动翻译使用的api与调用方式,默认使用 Google 翻译器并使用7890(clash)端口代理 */
2094
+ translator: new GoogleTranslator({
2095
+ proxyOption: {
2096
+ port: 7890,
2097
+ host: '127.0.0.1',
2098
+ headers: {
2099
+ 'User-Agent': 'Node'
2045
2100
  }
2046
- // 将当前项添加到结果数组中
2047
- result.push(item);
2048
2101
  }
2049
- }
2102
+ }),
2103
+ /** 翻译器配置选项,优先级低于translator */
2104
+ translatorOption: undefined,
2105
+ /**
2106
+ * 翻译类型,支持全自动和半自动两种模式
2107
+ * 全自动:所有翻译任务自动完成
2108
+ * 半自动:需要人工标识,类似于 $t('key') 的方式
2109
+ * 默认值为全自动
2110
+ */
2111
+ translateType: TranslateTypeEnum.FULL_AUTO,
2112
+ /**
2113
+ * 是否重写配置文件,默认为true
2114
+ */
2115
+ rewriteConfig: true,
2116
+ /**
2117
+ * 通用翻译key,默认使用namespace,如果commonTranslateKey不为空,则使用commonTranslateKey
2118
+ */
2119
+ commonTranslateKey: '',
2120
+ /**
2121
+ * 实验性属性,表示是否进行深层扫描字符串,默认为 false
2122
+ * 当设置为 true 时,会对代码中的字符串进行更深入的扫描
2123
+ */
2124
+ deepScan: false,
2125
+ /**
2126
+ * 自定义文件拓展名数组
2127
+ */
2128
+ insertFileExtensions: [],
2129
+ /**
2130
+ * 自定义拓展类,插件默认翻译函数挂载在window上,如果希望自定义翻译函数挂载在其他对象上,可以使用该属性
2131
+ * 注意:该属性需要继承BaseExtends类,并且需要实现handleInitFile和handleCodeCall和handleCodeString方法
2132
+ */
2133
+ translateExtends: null,
2134
+ isClear: false,
2135
+ // 是否清除已经不在上下文中的内容(清除项目中不再使用到的源语言键值对)
2136
+
2137
+ /**
2138
+ * 是否保留空格
2139
+ */
2140
+ isClearSpace: true
2141
+ };
2142
+
2143
+ /**
2144
+ * 类型定义:插件配置选项类型
2145
+ */
2146
+
2147
+ /**
2148
+ * 全局插件配置实例,复制自默认配置
2149
+ */
2150
+ exports.option = {
2151
+ ...DEFAULT_OPTION
2152
+ };
2050
2153
 
2051
- // 如果最后一个匹配字符串不为空,则将其添加到结果数组中
2052
- if (currentMatch) {
2053
- result.push(currentMatch);
2054
- }
2154
+ /**
2155
+ * 类型定义:用户传入的配置选项
2156
+ */
2055
2157
 
2056
- // 再遍历一次,把不符合separatorRegex 这个正则的拼起来
2057
- const finalResult = [];
2058
- let tempStr = '';
2059
- for (let i = 0; i < result.length; i++) {
2060
- const item = result[i];
2061
- if (separatorRegex.test(item)) {
2062
- if (tempStr) {
2063
- finalResult.push(tempStr);
2064
- tempStr = '';
2065
- }
2066
- finalResult.push(item);
2067
- } else {
2068
- tempStr += item;
2069
- if (i === result.length - 1 || separatorRegex.test(result[i + 1])) {
2070
- finalResult.push(tempStr);
2071
- tempStr = '';
2072
- }
2073
- }
2074
- }
2075
- if (tempStr) {
2076
- finalResult.push(tempStr);
2077
- }
2078
- return finalResult;
2158
+ /**
2159
+ * 通过深度克隆提供的选项信息生成一个用户选项对象,
2160
+ * 确保原始配置不被修改。它还根据用户的配置初始化翻译器。
2161
+ * @param optionInfo - 包含用户选项和翻译器细节的选项信息。
2162
+ * @returns 一个新的、可能已初始化翻译器的用户选项对象。
2163
+ */
2164
+ function generateUserOption(optionInfo) {
2165
+ // 深拷贝用户传入的配置,防止修改原配置对象
2166
+ const userOption = cloneDeep(optionInfo);
2167
+ userOption.translator = optionInfo?.translator;
2168
+
2169
+ // 如果用户配置了translatorOption则初始化translator,如果都没有则不设置translator
2170
+ userOption.translator ||= userOption.translatorOption ? new Translator(userOption.translatorOption) : undefined;
2171
+ if (!userOption.translator) delete userOption.translator;
2172
+ return userOption;
2079
2173
  }
2080
2174
 
2081
2175
  /**
2082
- * 检查字符串是否需要切割。
2083
- * @param str - 要检查的字符串。
2084
- * @returns 如果字符串需要切割,则返回 true,否则返回 false。
2176
+ * 初始化插件配置选项
2177
+ * @param optionInfo 用户提供的配置选项
2085
2178
  */
2086
- function checkNeedSplit(str) {
2087
- // 检查字符串中是否包含需要切割的特殊字符
2088
- return str.includes('\n') || str.includes('\\') || str.includes('\r') || str.includes('\t') || str.includes('\v') || str.includes('\f') || str.includes('>') || str.includes('<');
2179
+ function initOption(optionInfo) {
2180
+ // 合并默认配置和用户配置
2181
+ exports.option = {
2182
+ ...DEFAULT_OPTION,
2183
+ ...generateUserOption(optionInfo)
2184
+ };
2185
+
2186
+ // 初始化语言key数组,包含来源语言和目标语言
2187
+ exports.option.langKey = [exports.option.originLang, ...exports.option.targetLangList];
2188
+ // 初始化排除调用函数列表,包含默认排除和调用函数主动排除
2189
+ exports.option.excludedCall = [...exports.option.excludedCall, ...EXCLUDED_CALL, ...[exports.option.translateKey, '$' + exports.option.translateKey]];
2190
+ return exports.option;
2089
2191
  }
2090
2192
 
2091
2193
  /**
2092
- * @description: 将字符串数组转换为babel的模板字符串节点
2093
- * @param {string[]} strArray - 字符串数组
2094
- * @return {types.CallExpression} - babel的深度扫描的表达式
2194
+ * 校验插件配置选项是否完整有效
2195
+ * @returns {boolean} 校验结果,完整返回 true,否则返回 false
2095
2196
  */
2096
- function convertToTemplateLiteral(strArray, option) {
2097
- const quasis = [];
2098
- const expressions = [];
2099
- strArray.forEach((str, index) => {
2100
- if (index === 0) {
2101
- if (getOriginRegex().test(str)) {
2102
- quasis.push(types__namespace.templateElement({
2103
- raw: '',
2104
- cooked: ''
2105
- }, false));
2106
- expressions.push(createI18nTranslator({
2107
- value: str,
2108
- isExpression: true,
2109
- insertOption: option
2110
- }));
2111
- } else {
2112
- quasis.push(types__namespace.templateElement({
2113
- raw: str,
2114
- cooked: str
2115
- }, false));
2116
- }
2117
- } else {
2118
- if (getOriginRegex().test(str)) {
2119
- expressions.push(createI18nTranslator({
2120
- value: str,
2121
- isExpression: true,
2122
- insertOption: option
2123
- }));
2124
- } else {
2125
- quasis.push(types__namespace.templateElement({
2126
- raw: str,
2127
- cooked: str
2128
- }, false));
2129
- }
2130
- }
2131
- });
2132
- if (quasis.length === expressions.length) {
2133
- quasis.push(types__namespace.templateElement({
2134
- raw: '',
2135
- cooked: ''
2136
- }, true));
2137
- } else if (quasis.length > expressions.length) {
2138
- quasis[quasis.length - 1].tail = true;
2197
+ function checkOption() {
2198
+ // 校验翻译调用函数是否配置
2199
+ if (!exports.option.translateKey) {
2200
+ console.error('❌请配置翻译调用函数');
2201
+ return false;
2139
2202
  }
2140
- const templateLiteral = types__namespace.templateLiteral(quasis, expressions);
2141
- const deepScanCall = types__namespace.callExpression(types__namespace.identifier('$deepScan'), [templateLiteral]);
2142
- // 打印转换结果
2143
- // console.log('deepScanCall', (generate as any).default(deepScanCall).code)
2144
- return deepScanCall;
2145
- }
2146
2203
 
2147
- // 上传到远程服务器
2148
- // import { getAppCode, saveConfig, convertToUploadJson } from './config.js'
2149
- async function upload(json) {
2150
- const {
2151
- appCode,
2152
- targetLangList
2153
- } = exports.option;
2154
- let data = [];
2155
- _.map(json, (value, key) => {
2156
- _.map(targetLangList, item => {
2157
- const parts = item.split('-');
2158
- const lang = parts[0] + '-' + parts[1].toUpperCase();
2159
- data.push({
2160
- page: 'common',
2161
- key,
2162
- lang,
2163
- value: _.get(value, item, key)
2164
- });
2165
- });
2166
- });
2167
- const res = await axios.post('http://192.168.0.104:8108/i18n-web/kv_translate/batch', {
2168
- appCode,
2169
- data
2170
- }, {
2171
- headers: {
2172
- 'Content-Type': 'application/json',
2173
- Authorization: '728635D658AE11F18F33000C29A621CA'
2174
- }
2175
- });
2176
- if (res.status === 200 && res.data.result === 0) {
2177
- console.info('上传翻译服务成功✔');
2178
- } else {
2179
- console.error('上传翻译服务失败❌');
2180
- console.log(`请求数据为:`, JSON.stringify(data));
2181
- console.log(`返回报错信息为:`, res.data);
2204
+ // 校验命名空间是否配置
2205
+ if (!exports.option.namespace) {
2206
+ console.error('❌请配置命名空间');
2207
+ return false;
2182
2208
  }
2183
- }
2184
2209
 
2185
- var upload$1 = /*#__PURE__*/Object.freeze({
2186
- __proto__: null,
2187
- upload: upload
2188
- });
2210
+ // 校验是否配置了打包后生成文件的主文件名称(如果需要打包到主包中)
2211
+ if (exports.option.buildToDist && !exports.option.distKey) {
2212
+ console.log('❌请配置打包后生成文件的主文件名称');
2213
+ return false;
2214
+ }
2215
+
2216
+ // 校验是否配置了打包后生成文件的位置(如果需要打包到主包中)
2217
+ if (exports.option.buildToDist && !exports.option.distPath) {
2218
+ console.log('❌请配置打包后生成文件的位置');
2219
+ return false;
2220
+ }
2221
+
2222
+ // 校验来源语言是否配置
2223
+ if (!exports.option.originLang) {
2224
+ console.error('❌请配置来源语言');
2225
+ return false;
2226
+ }
2227
+
2228
+ // 校验目标翻译语言数组是否配置
2229
+ if (!exports.option.targetLangList || !exports.option.targetLangList.length) {
2230
+ console.error('❌请配置目标翻译语言数组');
2231
+ return false;
2232
+ }
2233
+
2234
+ // 如果所有校验通过,返回 true
2235
+ return true;
2236
+ }
2189
2237
 
2190
2238
  class Vue2Extends {
2191
2239
  constructor() {
@@ -2295,8 +2343,17 @@ function TemplateLiteral (insertOption) {
2295
2343
  };
2296
2344
  }
2297
2345
  function handleTemplateLiteralWithExpressions(node, path) {
2298
- const placeholders = node.expressions.map(expression => getExpressionPlaceholder(expression));
2299
- if (placeholders.some(placeholder => !placeholder)) {
2346
+ let hasOptimizedExpression = false;
2347
+ const expressionLabels = node.expressions.map((expression, index) => {
2348
+ const optimizedExpression = optimizeConditionalExpression(expression);
2349
+ if (optimizedExpression) {
2350
+ hasOptimizedExpression = true;
2351
+ node.expressions[index] = optimizedExpression.expression;
2352
+ return optimizedExpression.label;
2353
+ }
2354
+ return getExpressionPlaceholder(expression);
2355
+ });
2356
+ if (expressionLabels.some(placeholder => placeholder === null)) {
2300
2357
  return false;
2301
2358
  }
2302
2359
  const fullValue = node.quasis.reduce((result, quasi, index) => {
@@ -2308,32 +2365,26 @@ function handleTemplateLiteralWithExpressions(node, path) {
2308
2365
  console.log('转换异常');
2309
2366
  }
2310
2367
  }
2311
- return result + value + (index < node.expressions.length ? `\${${placeholders[index]}}` : '');
2368
+ return result + value + (index < node.expressions.length ? formatExpressionPlaceholder(expressionLabels[index]) : '');
2312
2369
  }, '');
2313
2370
  if (!fullValue || !hasOriginSymbols(fullValue) || !exports.option.excludedPattern.length || checkAgainstRegexArray(fullValue, [...exports.option.excludedPattern])) {
2371
+ if (hasOptimizedExpression) {
2372
+ path.skip();
2373
+ return true;
2374
+ }
2314
2375
  return false;
2315
2376
  }
2316
2377
  const {
2317
2378
  trimmedValue,
2318
2379
  valStr
2319
2380
  } = normalizeTranslateValue(fullValue);
2320
- const id = generateId(valStr);
2321
- const translateNode = createTranslateNode(path, id, valStr, placeholders, node.expressions);
2381
+ const id = resolveLangKey(generateId(valStr), trimmedValue);
2382
+ const translateNode = createTranslateNode(path, id, valStr, expressionLabels, node.expressions);
2322
2383
  path.replaceWith(translateNode);
2323
- path.skip();
2324
2384
  setLangObj(id, trimmedValue);
2325
2385
  return true;
2326
2386
  }
2327
2387
  function getExpressionPlaceholder(expression) {
2328
- if (types.isIdentifier(expression)) {
2329
- return expression.name;
2330
- }
2331
- if (types.isMemberExpression(expression)) {
2332
- const objectName = getExpressionPlaceholder(expression.object);
2333
- const propertyName = expression.computed ? getExpressionPlaceholder(expression.property) : types.isIdentifier(expression.property) ? expression.property.name : '';
2334
- const value = [objectName, propertyName].filter(Boolean).join('.');
2335
- return cleanVueRuntimePrefix(value);
2336
- }
2337
2388
  if (types.isStringLiteral(expression) || types.isNumericLiteral(expression)) {
2338
2389
  return String(expression.value);
2339
2390
  }
@@ -2344,21 +2395,142 @@ function getExpressionPlaceholder(expression) {
2344
2395
  return getExpressionPlaceholder(expression.expression);
2345
2396
  }
2346
2397
  const generateCode = generate__namespace.default?.default || generate__namespace.default || generate__namespace;
2347
- return cleanVueRuntimePrefix(generateCode(expression).code);
2398
+ return generateCode(expression, {
2399
+ comments: false
2400
+ }).code;
2401
+ }
2402
+ function optimizeConditionalExpression(expression) {
2403
+ if (!types.isConditionalExpression(expression)) return null;
2404
+ const consequent = translateConditionalStringBranch(expression.consequent);
2405
+ const alternate = translateConditionalStringBranch(expression.alternate);
2406
+ if (!consequent.changed && !alternate.changed) return null;
2407
+ return {
2408
+ label: getConditionalExpressionLabel(expression),
2409
+ expression: types.conditionalExpression(types.cloneNode(expression.test), consequent.expression, alternate.expression)
2410
+ };
2411
+ }
2412
+ function translateConditionalStringBranch(expression) {
2413
+ if (types.isStringLiteral(expression) && hasOriginSymbols(expression.value)) {
2414
+ return {
2415
+ changed: true,
2416
+ expression: createTrackedTranslateCall(expression.value)
2417
+ };
2418
+ }
2419
+ if (types.isTemplateLiteral(expression)) {
2420
+ const translateCall = createTemplateLiteralTranslateCall(expression);
2421
+ if (translateCall) {
2422
+ return {
2423
+ changed: true,
2424
+ expression: translateCall
2425
+ };
2426
+ }
2427
+ }
2428
+ if (types.isConditionalExpression(expression)) {
2429
+ const optimizedExpression = optimizeConditionalExpression(expression);
2430
+ if (optimizedExpression) {
2431
+ return {
2432
+ changed: true,
2433
+ expression: optimizedExpression.expression
2434
+ };
2435
+ }
2436
+ }
2437
+ return {
2438
+ changed: false,
2439
+ expression: types.cloneNode(expression)
2440
+ };
2441
+ }
2442
+ function createTrackedTranslateCall(value) {
2443
+ const translateCall = createI18nTranslator({
2444
+ value,
2445
+ isExpression: true
2446
+ });
2447
+ const {
2448
+ trimmedValue,
2449
+ valStr
2450
+ } = normalizeTranslateValue(value);
2451
+ const id = resolveLangKey(generateId(valStr), trimmedValue);
2452
+ if (id && trimmedValue) {
2453
+ setLangObj(id, trimmedValue);
2454
+ }
2455
+ return translateCall;
2456
+ }
2457
+ function createTemplateLiteralTranslateCall(node) {
2458
+ const expressions = node.expressions.map(expression => {
2459
+ const optimizedExpression = optimizeConditionalExpression(expression);
2460
+ return optimizedExpression ? optimizedExpression.expression : expression;
2461
+ });
2462
+ const expressionLabels = expressions.map(expression => getExpressionPlaceholder(expression));
2463
+ if (expressionLabels.some(placeholder => placeholder === null)) {
2464
+ return null;
2465
+ }
2466
+ const fullValue = node.quasis.reduce((result, quasi, index) => {
2467
+ let value = quasi.value.raw || quasi.value.cooked || '';
2468
+ if (asianLangs.some(lang => exports.option.originLang.includes(lang) || exports.option.originLang === lang)) {
2469
+ try {
2470
+ value = unicodeToString(value);
2471
+ } catch (error) {
2472
+ console.log('转换异常');
2473
+ }
2474
+ }
2475
+ return result + value + (index < expressions.length ? formatExpressionPlaceholder(expressionLabels[index]) : '');
2476
+ }, '');
2477
+ if (!fullValue || !hasOriginSymbols(fullValue) || !exports.option.excludedPattern.length || checkAgainstRegexArray(fullValue, [...exports.option.excludedPattern])) {
2478
+ return null;
2479
+ }
2480
+ const {
2481
+ trimmedValue,
2482
+ valStr
2483
+ } = normalizeTranslateValue(fullValue);
2484
+ const id = resolveLangKey(generateId(valStr), trimmedValue);
2485
+ if (id && trimmedValue) {
2486
+ setLangObj(id, trimmedValue);
2487
+ }
2488
+ return createTranslateCall(id, valStr, expressionLabels, expressions);
2489
+ }
2490
+ function getConditionalExpressionLabel(expression) {
2491
+ const test = expression.test;
2492
+ if (types.isBinaryExpression(test) || types.isLogicalExpression(test)) {
2493
+ return getExpressionPlaceholder(test.left);
2494
+ }
2495
+ return getExpressionPlaceholder(test);
2496
+ }
2497
+ function formatExpressionPlaceholder(placeholder) {
2498
+ if (placeholder === null) return '';
2499
+ return placeholder.trim() ? `\${${placeholder}}` : placeholder;
2348
2500
  }
2349
2501
  function createTranslateNode(path, id, valStr) {
2350
2502
  let placeholders = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
2351
2503
  let expressions = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
2352
- const args = [types.stringLiteral(id), types.stringLiteral(valStr)];
2353
- if (placeholders.length) {
2354
- args.push(types.objectExpression(placeholders.map((placeholder, index) => types.objectProperty(types.stringLiteral(placeholder), types.cloneNode(expressions[index])))));
2355
- }
2356
- const translateCall = types.callExpression(types.identifier(exports.option.translateKey), args);
2504
+ const translateCall = createTranslateCall(id, valStr, placeholders, expressions);
2357
2505
  if (!isVueTemplateRender(path)) {
2358
2506
  return translateCall;
2359
2507
  }
2360
2508
  return types.sequenceExpression([createVueTemplateLanguageDependency(), translateCall]);
2361
2509
  }
2510
+ function createTranslateCall(id, valStr) {
2511
+ let placeholders = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
2512
+ let expressions = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
2513
+ const args = [types.stringLiteral(id), types.stringLiteral(valStr)];
2514
+ if (placeholders.length) {
2515
+ const placeholderEntries = placeholders.map((placeholder, index) => ({
2516
+ placeholder,
2517
+ expression: expressions[index]
2518
+ })).filter(_ref => {
2519
+ let {
2520
+ placeholder
2521
+ } = _ref;
2522
+ return placeholder && placeholder.trim();
2523
+ });
2524
+ args.push(types.objectExpression(placeholderEntries.map(_ref2 => {
2525
+ let {
2526
+ placeholder,
2527
+ expression
2528
+ } = _ref2;
2529
+ return types.objectProperty(types.stringLiteral(placeholder), types.cloneNode(expression));
2530
+ })));
2531
+ }
2532
+ return types.callExpression(types.identifier(exports.option.translateKey), args);
2533
+ }
2362
2534
  function isVueTemplateRender(path) {
2363
2535
  return Boolean(path.scope?.getBinding('_vm'));
2364
2536
  }
@@ -2372,9 +2544,6 @@ function createVueTemplateLanguageDependency() {
2372
2544
  const i18nLocale = types.logicalExpression('&&', types.cloneNode(i18n), types.memberExpression(types.cloneNode(i18n), types.identifier('locale')));
2373
2545
  return types.logicalExpression('||', storeLanguage, i18nLocale);
2374
2546
  }
2375
- function cleanVueRuntimePrefix(value) {
2376
- return value.replace(/^(?:\$setup|_ctx|_vm|this)\./, '').replace(/([^\w$.])(?:\$setup|_ctx|_vm|this)\./g, '$1');
2377
- }
2378
2547
 
2379
2548
  // 处理模板元素
2380
2549
  function handleTemplateElement(node, insertOption) {
@@ -2397,9 +2566,10 @@ function handleTemplateElement(node, insertOption) {
2397
2566
  // 替换为字符类型翻译节点
2398
2567
  node.value.raw = node.value.cooked = `\${${newNode}}`;
2399
2568
  const {
2569
+ trimmedValue,
2400
2570
  valStr
2401
2571
  } = normalizeTranslateValue(value);
2402
- let id = generateId(valStr);
2572
+ let id = resolveLangKey(generateId(valStr), trimmedValue);
2403
2573
  if (id && value) {
2404
2574
  setLangObj(id, value);
2405
2575
  }