vite-gc-i18n-plugin 1.1.4 → 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 +1419 -1381
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.mjs +1419 -1381
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -597,1606 +597,1643 @@ const REGEX_MAP = {
|
|
|
597
597
|
[OriginLangKeyEnum.RU]: /[йцукенгшщзхъфывапролджэячсмитьбюё .-]{1,}/ // 俄语字母
|
|
598
598
|
};
|
|
599
599
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
*
|
|
613
|
-
* @param {string} code
|
|
614
|
-
* @return {*}
|
|
705
|
+
* JSON 格式化配置类型
|
|
615
706
|
*/
|
|
616
|
-
function hasOriginSymbols(code) {
|
|
617
|
-
return getOriginRegex().test(code);
|
|
618
|
-
}
|
|
619
707
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
*
|
|
637
|
-
* @param
|
|
638
|
-
* @param
|
|
639
|
-
* @
|
|
738
|
+
* 格式化 JSON 字符串
|
|
739
|
+
* @param json JSON 字符串
|
|
740
|
+
* @param indentType 缩进类型
|
|
741
|
+
* @returns 格式化的 JSON 字符串
|
|
640
742
|
*/
|
|
641
|
-
function
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
653
|
-
* @param
|
|
654
|
-
* @
|
|
785
|
+
* 格式化 JSON 的主函数
|
|
786
|
+
* @param json 需要格式化的 JSON 对象或字符串
|
|
787
|
+
* @param config 配置选项
|
|
788
|
+
* @returns 格式化后的 JSON 字符串
|
|
655
789
|
*/
|
|
656
|
-
function
|
|
657
|
-
let
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
680
|
-
* @
|
|
681
|
-
* @
|
|
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
|
-
*
|
|
690
|
-
* @param
|
|
691
|
-
* @param
|
|
692
|
-
* @
|
|
816
|
+
* 智能文本分块处理器
|
|
817
|
+
* @param values 待分块的原始文本数组
|
|
818
|
+
* @param maxChunkSize 最大分块长度
|
|
819
|
+
* @returns 包含分块文本和重组方法的对象
|
|
820
|
+
*
|
|
821
|
+
* 功能特性:
|
|
822
|
+
* 1. 自动合并小文本为最大可能块
|
|
823
|
+
* 2. 处理超长文本并给出警告
|
|
824
|
+
* 3. 保证块长度不超过限制
|
|
825
|
+
* 4. 保留原始顺序和分隔符语义
|
|
693
826
|
*/
|
|
694
|
-
function
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
827
|
+
function createTextSplitter(values, maxChunkSize) {
|
|
828
|
+
// 分隔符定义(用于合并/拆分时保持语义)
|
|
829
|
+
const SEP_LENGTH = SEPARATOR.length;
|
|
698
830
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
valStr
|
|
755
|
-
};
|
|
888
|
+
|
|
889
|
+
// 提交最终未完成的缓冲区内容
|
|
890
|
+
commitBuffer();
|
|
891
|
+
|
|
892
|
+
// 返回分块结果
|
|
893
|
+
return result;
|
|
756
894
|
}
|
|
757
895
|
|
|
758
|
-
|
|
759
|
-
* @
|
|
760
|
-
* @
|
|
761
|
-
* @
|
|
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
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
*
|
|
776
|
-
* @param
|
|
777
|
-
* @
|
|
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
|
-
*
|
|
787
|
-
*
|
|
788
|
-
*
|
|
914
|
+
* 这个函数的主要功能是根据给定的正则表达式分割字符串,并对分割结果进行特殊处理。
|
|
915
|
+
* 处理过程分为三个主要步骤:
|
|
916
|
+
*
|
|
917
|
+
* 1. 首先根据分隔符正则和标点符号正则进行初步分割
|
|
918
|
+
* 2. 然后将连续的标点符号和符合分隔符正则的部分重新连接
|
|
919
|
+
* 3. 最后将不符合分隔符正则的相邻部分合并
|
|
920
|
+
*
|
|
921
|
+
* @param str - 需要分割的源字符串
|
|
922
|
+
* @param separatorRegex - 用于分割的正则表达式
|
|
923
|
+
* @returns 处理后的字符串数组
|
|
789
924
|
*/
|
|
790
|
-
function
|
|
791
|
-
//
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
936
|
+
// 定义连接标点符号的正则表达式
|
|
937
|
+
const connectPunctuationRegex = /[,。?!《》,..:!?;'"、0-9]/;
|
|
938
|
+
// 创建一个新的正则表达式,用于检测是否需要连接
|
|
939
|
+
const connectRegex = new RegExp(`(${separatorRegex.source}|${connectPunctuationRegex.source})`, separatorRegex.flags);
|
|
814
940
|
|
|
815
|
-
//
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
cache.set(value, clone);
|
|
957
|
+
// 如果最后一个匹配字符串不为空,则将其添加到结果数组中
|
|
958
|
+
if (currentMatch) {
|
|
959
|
+
result.push(currentMatch);
|
|
960
|
+
}
|
|
828
961
|
|
|
829
|
-
//
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
+
}
|
|
835
980
|
}
|
|
836
|
-
|
|
981
|
+
if (tempStr) {
|
|
982
|
+
finalResult.push(tempStr);
|
|
983
|
+
}
|
|
984
|
+
return finalResult;
|
|
837
985
|
}
|
|
838
986
|
|
|
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
987
|
/**
|
|
858
|
-
*
|
|
859
|
-
*
|
|
860
|
-
*
|
|
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
|
-
* ```
|
|
988
|
+
* 检查字符串是否需要切割。
|
|
989
|
+
* @param str - 要检查的字符串。
|
|
990
|
+
* @returns 如果字符串需要切割,则返回 true,否则返回 false。
|
|
874
991
|
*/
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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" }
|
|
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('<');
|
|
995
|
+
}
|
|
902
996
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
997
|
+
/**
|
|
998
|
+
* @description: 将字符串数组转换为babel的模板字符串节点
|
|
999
|
+
* @param {string[]} strArray - 字符串数组
|
|
1000
|
+
* @return {types.CallExpression} - babel的深度扫描的表达式
|
|
1001
|
+
*/
|
|
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;
|
|
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
|
+
}
|
|
906
1052
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
|
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
|
+
});
|
|
957
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);
|
|
958
1088
|
}
|
|
959
1089
|
}
|
|
960
1090
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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
|
-
}({});
|
|
1091
|
+
var upload$1 = /*#__PURE__*/Object.freeze({
|
|
1092
|
+
__proto__: null,
|
|
1093
|
+
upload: upload
|
|
1094
|
+
});
|
|
972
1095
|
|
|
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
1096
|
/**
|
|
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
|
|
1097
|
+
* @description: 新建国际化配置文件夹
|
|
1098
|
+
* @return {*}
|
|
993
1099
|
*/
|
|
994
|
-
|
|
995
|
-
|
|
1100
|
+
function initLangFile() {
|
|
1101
|
+
if (!fs.existsSync(exports.option.globalPath)) {
|
|
1102
|
+
fs.mkdirSync(exports.option.globalPath); // 创建lang文件夹
|
|
1103
|
+
}
|
|
1104
|
+
initLangTranslateJSONFile();
|
|
1105
|
+
initTranslateBasicFnFile();
|
|
1106
|
+
}
|
|
996
1107
|
/**
|
|
997
|
-
*
|
|
1108
|
+
* @description: 初始化翻译基础函数文件
|
|
1109
|
+
* @returns {void}
|
|
998
1110
|
*/
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
}
|
|
1111
|
+
async function initTranslateBasicFnFile() {
|
|
1112
|
+
// 从配置选项中获取必要的配置信息
|
|
1113
|
+
const {
|
|
1114
|
+
targetLangList
|
|
1115
|
+
} = exports.option;
|
|
1116
|
+
// 生成语言映射列表
|
|
1117
|
+
let languages = [];
|
|
1118
|
+
let langList = targetLangList.map(item => {
|
|
1119
|
+
const parts = item.split('-');
|
|
1120
|
+
const lang = parts[0] + '-' + parts[1].toUpperCase();
|
|
1121
|
+
return _.find(allLanguage, {
|
|
1122
|
+
code: lang
|
|
1123
|
+
});
|
|
1124
|
+
});
|
|
1125
|
+
langList
|
|
1126
|
+
// 构建语言映射项
|
|
1127
|
+
.map(item => {
|
|
1128
|
+
languages.push(`{
|
|
1129
|
+
code: "${item.code}",
|
|
1130
|
+
name: "${item.name}"
|
|
1131
|
+
}`);
|
|
1132
|
+
});
|
|
1133
|
+
// 用逗号和换行符连接所有映射项
|
|
1134
|
+
const translateBasicFnText = `
|
|
1135
|
+
import gc_i18n from 'gc_i18n'
|
|
1136
|
+
export const languages = [${languages.toString()}]
|
|
1137
|
+
const gcI18nInstance = new gc_i18n({
|
|
1138
|
+
appCode: 'TEST',
|
|
1139
|
+
env: 'dev', // local 本地部署 dev 多语言测试环境 prod 多语言生产环境
|
|
1140
|
+
orgCode: 'GREENCLOUD',
|
|
1141
|
+
messages: {} // 如果你需要自己引入语言包
|
|
1142
|
+
})
|
|
1143
|
+
export default {
|
|
1144
|
+
install(app, router) {
|
|
1145
|
+
gcI18nInstance.setRouter(router)
|
|
1043
1146
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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
|
-
};
|
|
1147
|
+
}`;
|
|
1148
|
+
// 构建翻译基础函数文件的路径
|
|
1149
|
+
const indexPath = path.join(exports.option.globalPath, 'index.js');
|
|
1084
1150
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
*/
|
|
1151
|
+
// 文件已存在 同时 不重写配置,那么这里就结束
|
|
1152
|
+
if (fs.existsSync(indexPath) && !exports.option.rewriteConfig) return;
|
|
1088
1153
|
|
|
1089
|
-
|
|
1090
|
-
* 全局插件配置实例,复制自默认配置
|
|
1091
|
-
*/
|
|
1092
|
-
exports.option = {
|
|
1093
|
-
...DEFAULT_OPTION
|
|
1094
|
-
};
|
|
1154
|
+
// 新增哈希比对逻辑
|
|
1095
1155
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1156
|
+
// 标记是否需要更新文件
|
|
1157
|
+
let needUpdate = true;
|
|
1158
|
+
if (fs.existsSync(indexPath)) {
|
|
1159
|
+
// 检查文件是否已存在
|
|
1160
|
+
// 读取现有文件内容
|
|
1161
|
+
const existingContent = fs.readFileSync(indexPath, 'utf-8');
|
|
1162
|
+
// 生成现有内容的哈希值
|
|
1163
|
+
const existingHash = generateId(existingContent);
|
|
1164
|
+
// 判断是否需要更新文件
|
|
1165
|
+
// needUpdate = currentHash !== existingHash
|
|
1166
|
+
needUpdate = !existingHash;
|
|
1167
|
+
}
|
|
1099
1168
|
|
|
1169
|
+
// 如果需要更新文件,则写入新内容
|
|
1170
|
+
if (needUpdate) {
|
|
1171
|
+
fs.writeFileSync(indexPath, translateBasicFnText);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1100
1174
|
/**
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1103
|
-
* @param optionInfo - 包含用户选项和翻译器细节的选项信息。
|
|
1104
|
-
* @returns 一个新的、可能已初始化翻译器的用户选项对象。
|
|
1175
|
+
* @description: 生成国际化JSON文件
|
|
1176
|
+
* @return {*}
|
|
1105
1177
|
*/
|
|
1106
|
-
function
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
userOption.translator ||= userOption.translatorOption ? new Translator(userOption.translatorOption) : undefined;
|
|
1113
|
-
if (!userOption.translator) delete userOption.translator;
|
|
1114
|
-
return userOption;
|
|
1178
|
+
function initLangTranslateJSONFile() {
|
|
1179
|
+
const indexPath = path.join(exports.option.globalPath, 'index.json');
|
|
1180
|
+
if (!fs.existsSync(indexPath)) {
|
|
1181
|
+
// 不存在就创建
|
|
1182
|
+
fs.writeFileSync(indexPath, JSON.stringify({})); // 创建
|
|
1183
|
+
}
|
|
1115
1184
|
}
|
|
1116
1185
|
|
|
1117
1186
|
/**
|
|
1118
|
-
*
|
|
1119
|
-
* @
|
|
1187
|
+
* @description: 读取国际化JSON文件
|
|
1188
|
+
* @return {*}
|
|
1120
1189
|
*/
|
|
1121
|
-
function
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1190
|
+
function getLangTranslateJSONFile() {
|
|
1191
|
+
const filePath = path.join(exports.option.globalPath, 'index.json');
|
|
1192
|
+
try {
|
|
1193
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
1194
|
+
return content;
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
if (error.code === 'ENOENT') {
|
|
1197
|
+
console.log('❌读取JSON配置文件异常,文件不存在');
|
|
1198
|
+
} else {
|
|
1199
|
+
console.log('❌读取JSON配置文件异常,无法读取文件');
|
|
1200
|
+
}
|
|
1201
|
+
return JSON.stringify({});
|
|
1202
|
+
}
|
|
1133
1203
|
}
|
|
1134
1204
|
|
|
1135
1205
|
/**
|
|
1136
|
-
*
|
|
1137
|
-
* @
|
|
1206
|
+
* @description: 基于langKey获取JSON配置文件中对应语言对象
|
|
1207
|
+
* @param {string} key
|
|
1208
|
+
* @return {*}
|
|
1138
1209
|
*/
|
|
1139
|
-
function
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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
|
-
}
|
|
1210
|
+
function getLangObjByJSONFileWithLangKey(key) {
|
|
1211
|
+
let insertJSONObj = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
|
|
1212
|
+
// 获取翻译配置对象,优先使用传入的配置,否则从本地文件读取
|
|
1213
|
+
const JSONObj = insertJSONObj || JSON.parse(getLangTranslateJSONFile());
|
|
1163
1214
|
|
|
1164
|
-
//
|
|
1165
|
-
|
|
1166
|
-
console.error('❌请配置来源语言');
|
|
1167
|
-
return false;
|
|
1168
|
-
}
|
|
1215
|
+
// 初始化语言映射对象,用于存储不同语言的hash: value映射
|
|
1216
|
+
const langObj = {};
|
|
1169
1217
|
|
|
1170
|
-
//
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1218
|
+
// 遍历hash,提取hash对应语言key的值,并写入到langObj
|
|
1219
|
+
Object.keys(JSONObj).forEach(langCode => {
|
|
1220
|
+
langObj[langCode] = JSONObj[langCode][key];
|
|
1221
|
+
});
|
|
1175
1222
|
|
|
1176
|
-
//
|
|
1177
|
-
|
|
1223
|
+
// 返回当前语言的hash: value映射对象
|
|
1224
|
+
// 例如: 'zh-cn' > {'hash1': '你好', 'hash2': '世界'}
|
|
1225
|
+
// 'en' > {'hash1': 'hello', 'hash2': 'world'}
|
|
1226
|
+
return langObj;
|
|
1178
1227
|
}
|
|
1179
1228
|
|
|
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
1229
|
/**
|
|
1285
|
-
* JSON
|
|
1230
|
+
* @description: 设置国际化JSON文件
|
|
1231
|
+
* @return {*}
|
|
1286
1232
|
*/
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
space: {
|
|
1295
|
-
char: ' ',
|
|
1296
|
-
size: 4
|
|
1233
|
+
function setLangTranslateJSONFile(obj) {
|
|
1234
|
+
const filePath = path.join(exports.option.globalPath, 'index.json');
|
|
1235
|
+
const jsonObj = jsonFormatter(obj);
|
|
1236
|
+
if (fs.existsSync(filePath)) {
|
|
1237
|
+
fs.writeFileSync(filePath, jsonObj);
|
|
1238
|
+
} else {
|
|
1239
|
+
console.log('❌JSON配置文件写入异常,文件不存在');
|
|
1297
1240
|
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
// 默认格式化配置
|
|
1301
|
-
const configDefault = {
|
|
1302
|
-
type: 'tab'
|
|
1303
|
-
};
|
|
1241
|
+
}
|
|
1304
1242
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1243
|
+
/**
|
|
1244
|
+
* @description: 构建时把lang配置文件设置到打包后到主文件中
|
|
1245
|
+
* @return {*}
|
|
1246
|
+
*/
|
|
1247
|
+
function uploadFile() {
|
|
1248
|
+
const JSONObj = JSON.parse(getLangTranslateJSONFile());
|
|
1249
|
+
upload(JSONObj);
|
|
1250
|
+
// if (!option.buildToDist) return
|
|
1251
|
+
// let langObjMap: any = {}
|
|
1252
|
+
// option.langKey.forEach(item => {
|
|
1253
|
+
// langObjMap[item] = getLangObjByJSONFileWithLangKey(item)
|
|
1254
|
+
// })
|
|
1255
|
+
// if (fs.existsSync(option.distPath)) {
|
|
1256
|
+
// fs.readdir(option.distPath, (err, files) => {
|
|
1257
|
+
// if (err) {
|
|
1258
|
+
// console.error('❌构建文件夹为空,翻译配置无法写入')
|
|
1259
|
+
// return
|
|
1260
|
+
// }
|
|
1307
1261
|
|
|
1308
|
-
//
|
|
1309
|
-
|
|
1310
|
-
const
|
|
1262
|
+
// files.forEach(file => {
|
|
1263
|
+
// if (file.startsWith(option.distKey) && file.endsWith('.js')) {
|
|
1264
|
+
// const filePath = path.join(option.distPath, file)
|
|
1265
|
+
// fs.readFile(filePath, 'utf-8', (err, data) => {
|
|
1266
|
+
// if (err) {
|
|
1267
|
+
// console.log(filePath)
|
|
1268
|
+
// console.error('❌构建主文件不存在,翻译配置无法写入')
|
|
1269
|
+
// return
|
|
1270
|
+
// }
|
|
1271
|
+
// let buildLangConfigString = ''
|
|
1272
|
+
// Object.keys(langObjMap).forEach(item => {
|
|
1273
|
+
// buildLangConfigString =
|
|
1274
|
+
// buildLangConfigString +
|
|
1275
|
+
// `globalThis['${option.namespace}']['${item}']=${JSON.stringify(langObjMap[item])};`
|
|
1276
|
+
// })
|
|
1277
|
+
// try {
|
|
1278
|
+
// // 翻译配置写入主文件
|
|
1279
|
+
// fs.writeFileSync(
|
|
1280
|
+
// filePath,
|
|
1281
|
+
// `globalThis['${option.namespace}']={};${buildLangConfigString}` +
|
|
1282
|
+
// data
|
|
1283
|
+
// )
|
|
1284
|
+
// console.info('恭喜:翻译配置写入构建主文件成功🌟🌟🌟')
|
|
1285
|
+
// } catch (err) {
|
|
1286
|
+
// console.error('翻译配置写入构建主文件失败:', err)
|
|
1287
|
+
// }
|
|
1288
|
+
// })
|
|
1289
|
+
// }
|
|
1290
|
+
// })
|
|
1291
|
+
// })
|
|
1292
|
+
// }
|
|
1293
|
+
}
|
|
1311
1294
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1295
|
+
var file = /*#__PURE__*/Object.freeze({
|
|
1296
|
+
__proto__: null,
|
|
1297
|
+
getLangObjByJSONFileWithLangKey: getLangObjByJSONFileWithLangKey,
|
|
1298
|
+
getLangTranslateJSONFile: getLangTranslateJSONFile,
|
|
1299
|
+
initLangFile: initLangFile,
|
|
1300
|
+
initLangTranslateJSONFile: initLangTranslateJSONFile,
|
|
1301
|
+
initTranslateBasicFnFile: initTranslateBasicFnFile,
|
|
1302
|
+
setLangTranslateJSONFile: setLangTranslateJSONFile,
|
|
1303
|
+
uploadFile: uploadFile
|
|
1304
|
+
});
|
|
1316
1305
|
|
|
1317
|
-
|
|
1318
|
-
*
|
|
1319
|
-
* @
|
|
1320
|
-
* @
|
|
1321
|
-
* @
|
|
1306
|
+
/*
|
|
1307
|
+
* @Author: xiaoshanwen
|
|
1308
|
+
* @Date: 2023-10-30 18:23:03
|
|
1309
|
+
* @LastEditTime: 2025-03-16 19:12:54
|
|
1310
|
+
* @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/translate.ts
|
|
1322
1311
|
*/
|
|
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
1312
|
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1313
|
+
const SEPARATOR = '\n┇┇┇\n';
|
|
1314
|
+
const SPLIT_SEPARATOR_REGEX = /\n┇ *┇ *┇\n/;
|
|
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];
|
|
1354
1339
|
}
|
|
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;
|
|
1340
|
+
});
|
|
1362
1341
|
}
|
|
1363
1342
|
|
|
1364
1343
|
/**
|
|
1365
|
-
*
|
|
1366
|
-
* @param
|
|
1367
|
-
* @param
|
|
1368
|
-
* @
|
|
1344
|
+
* @description: 设置翻译对象属性
|
|
1345
|
+
* @param {string} key
|
|
1346
|
+
* @param {string} value
|
|
1347
|
+
* @return {*}
|
|
1369
1348
|
*/
|
|
1370
|
-
function
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
// 获取缩进配置
|
|
1376
|
-
const indent = indentConfig[config.type];
|
|
1377
|
-
if (!indent) {
|
|
1378
|
-
throw new Error(`Unrecognized indent type: "${config.type}"`);
|
|
1349
|
+
function setLangObj(key, value) {
|
|
1350
|
+
if (isTemplateValue(value)) {
|
|
1351
|
+
removeTemplateStaticSegments(value);
|
|
1352
|
+
} else if (isTemplateStaticSegment(value)) {
|
|
1353
|
+
return;
|
|
1379
1354
|
}
|
|
1355
|
+
const existingKey = getLangKeyByValue(value);
|
|
1356
|
+
if (existingKey && existingKey !== key) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
if (!langObj[key]) {
|
|
1360
|
+
langObj[key] = value;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1380
1363
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1364
|
+
/**
|
|
1365
|
+
* @description: 读取翻译对象
|
|
1366
|
+
* @return {*}
|
|
1367
|
+
*/
|
|
1368
|
+
function getLangObj() {
|
|
1369
|
+
return langObj;
|
|
1386
1370
|
}
|
|
1387
1371
|
|
|
1388
1372
|
/**
|
|
1389
|
-
* @description:
|
|
1373
|
+
* @description: 初始化翻译对象
|
|
1374
|
+
* @param {langObj} obj
|
|
1390
1375
|
* @return {*}
|
|
1391
1376
|
*/
|
|
1392
|
-
function
|
|
1393
|
-
if (!
|
|
1394
|
-
|
|
1377
|
+
function initLangObj(obj) {
|
|
1378
|
+
if (!Object.keys(langObj).length) {
|
|
1379
|
+
langObj = obj;
|
|
1395
1380
|
}
|
|
1396
|
-
initLangTranslateJSONFile();
|
|
1397
|
-
initTranslateBasicFnFile();
|
|
1398
1381
|
}
|
|
1382
|
+
|
|
1383
|
+
// todo 类型修复
|
|
1399
1384
|
/**
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1385
|
+
* 自动生成多语言配置文件的核心方法
|
|
1386
|
+
*
|
|
1387
|
+
* 主要流程:
|
|
1388
|
+
* 1. 加载现有翻译文件
|
|
1389
|
+
* 2. 对比找出新增需要翻译的内容
|
|
1390
|
+
* 3. 分块并行翻译所有目标语言
|
|
1391
|
+
* 4. 合并翻译结果并生成最终配置文件
|
|
1392
|
+
*
|
|
1393
|
+
* 异常处理:
|
|
1394
|
+
* - 翻译结果不完整时中断流程
|
|
1395
|
+
* - 文件读写失败时明确报错
|
|
1402
1396
|
*/
|
|
1403
|
-
async function
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1397
|
+
async function autoTranslate() {
|
|
1398
|
+
const enabled = typeof exports.option.enabled === 'function' ? exports.option.enabled() : exports.option.enabled;
|
|
1399
|
+
if (!enabled) return;
|
|
1400
|
+
|
|
1401
|
+
// 初始化现有翻译文件缓存
|
|
1402
|
+
const originLangObjMap = {};
|
|
1403
|
+
|
|
1404
|
+
// 加载所有语言的现有翻译内容
|
|
1405
|
+
// 获取当前待翻译内容(深拷贝避免污染原始数据)
|
|
1406
|
+
const currentLangObj = JSON.parse(JSON.stringify(getLangObj()));
|
|
1407
|
+
exports.option.langKey.forEach(lang => {
|
|
1408
|
+
const keyMap = getLangObjByJSONFileWithLangKey(lang);
|
|
1409
|
+
originLangObjMap[lang] = keyMap;
|
|
1416
1410
|
});
|
|
1417
|
-
|
|
1418
|
-
//
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1411
|
+
|
|
1412
|
+
// 筛选需要翻译的新增内容
|
|
1413
|
+
const transLangObj = {};
|
|
1414
|
+
Object.keys(currentLangObj).forEach(key => {
|
|
1415
|
+
if (!originLangObjMap[exports.option.originLang][key]) {
|
|
1416
|
+
transLangObj[key] = currentLangObj[key];
|
|
1417
|
+
}
|
|
1424
1418
|
});
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1419
|
+
|
|
1420
|
+
// 无新内容提前退出
|
|
1421
|
+
if (Object.keys(transLangObj).length === 0) {
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// 初始化翻译结果存储结构
|
|
1426
|
+
const newLangObjMap = {};
|
|
1427
|
+
|
|
1428
|
+
// 遍历所有目标语言进行处理
|
|
1429
|
+
for (let langIndex = 0; langIndex < exports.option.langKey.length; langIndex++) {
|
|
1430
|
+
const currentLang = exports.option.langKey[langIndex];
|
|
1431
|
+
|
|
1432
|
+
// 原始语言直接存储原文,读取扫出来的元素翻译内容
|
|
1433
|
+
if (langIndex === 0) {
|
|
1434
|
+
newLangObjMap[exports.option.originLang] = Object.values(transLangObj);
|
|
1435
|
+
continue;
|
|
1438
1436
|
}
|
|
1439
|
-
|
|
1440
|
-
// 构建翻译基础函数文件的路径
|
|
1441
|
-
const indexPath = path.join(exports.option.globalPath, 'index.js');
|
|
1437
|
+
console.info('开始自动翻译...');
|
|
1442
1438
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1439
|
+
// ─── 分块翻译流程开始 ───
|
|
1440
|
+
const translatedValues = await translateChunks(transLangObj, exports.option.langKey[langIndex]);
|
|
1441
|
+
// ─── 分块翻译流程结束 ───=
|
|
1445
1442
|
|
|
1446
|
-
|
|
1443
|
+
// ─── 翻译结果校验 ───
|
|
1444
|
+
if (translatedValues.length !== Object.keys(transLangObj).length) {
|
|
1445
|
+
console.error('❌ 使用付费翻译时,请检查翻译API额度是否充足,或是否已申请对应翻译API使用权限');
|
|
1446
|
+
console.error(`❌ 翻译结果不完整
|
|
1447
|
+
预期数量: ${Object.keys(transLangObj).length}
|
|
1448
|
+
实际数量: ${translatedValues.length}
|
|
1449
|
+
样例数据: ${JSON.stringify(translatedValues.slice(0, 3))}`);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1447
1452
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
// 检查文件是否已存在
|
|
1452
|
-
// 读取现有文件内容
|
|
1453
|
-
const existingContent = fs.readFileSync(indexPath, 'utf-8');
|
|
1454
|
-
// 生成现有内容的哈希值
|
|
1455
|
-
const existingHash = generateId(existingContent);
|
|
1456
|
-
// 判断是否需要更新文件
|
|
1457
|
-
// needUpdate = currentHash !== existingHash
|
|
1458
|
-
needUpdate = !existingHash;
|
|
1453
|
+
// 存储当前语言翻译结果
|
|
1454
|
+
newLangObjMap[currentLang] = translatedValues;
|
|
1455
|
+
console.info(`✅ ${currentLang} 翻译完成`);
|
|
1459
1456
|
}
|
|
1460
1457
|
|
|
1461
|
-
//
|
|
1462
|
-
|
|
1463
|
-
|
|
1458
|
+
// ─── 合并翻译结果到配置 ───
|
|
1459
|
+
Object.keys(transLangObj).forEach((key, index) => {
|
|
1460
|
+
exports.option.langKey.forEach(item => {
|
|
1461
|
+
originLangObjMap[item][key] = newLangObjMap[item][index];
|
|
1462
|
+
});
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
// ─── 生成最终配置文件结构 ───
|
|
1466
|
+
console.log('📄 构建配置文件数据结构...');
|
|
1467
|
+
const configLangObj = {};
|
|
1468
|
+
Object.keys(originLangObjMap[exports.option.originLang]).forEach(key => {
|
|
1469
|
+
configLangObj[key] = {};
|
|
1470
|
+
exports.option.langKey.forEach(lang => {
|
|
1471
|
+
configLangObj[key][lang] = originLangObjMap[lang][key];
|
|
1472
|
+
});
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
// ─── 写入文件系统 ───
|
|
1476
|
+
try {
|
|
1477
|
+
setLangTranslateJSONFile(configLangObj);
|
|
1478
|
+
console.info('🎉 多语言配置文件已成功更新');
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
console.error('❌ 配置文件写入失败,原因:', error);
|
|
1481
|
+
// todo 可添加重试逻辑或回滚机制
|
|
1464
1482
|
}
|
|
1465
1483
|
}
|
|
1484
|
+
|
|
1466
1485
|
/**
|
|
1467
|
-
* @description:
|
|
1486
|
+
* @description: 新增语言类型配置补全
|
|
1487
|
+
* @param {any} obj
|
|
1468
1488
|
* @return {*}
|
|
1469
1489
|
*/
|
|
1470
|
-
function
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1490
|
+
function languageConfigCompletion(obj) {
|
|
1491
|
+
if (!Object.keys(obj)) return;
|
|
1492
|
+
const enabled = typeof exports.option.enabled === 'function' ? exports.option.enabled() : exports.option.enabled;
|
|
1493
|
+
if (!enabled) return;
|
|
1494
|
+
let needCompletionList = [];
|
|
1495
|
+
const JSONobj = JSON.parse(getLangTranslateJSONFile());
|
|
1496
|
+
exports.option.targetLangList.forEach(item => {
|
|
1497
|
+
// 获取目标语言 hash:value 对象 和 语言的复合对象,如果当前语言不存在,是langObj的value卡都为空
|
|
1498
|
+
let langObj = getLangObjByJSONFileWithLangKey(item, JSONobj);
|
|
1499
|
+
needCompletionList.push({
|
|
1500
|
+
key: item,
|
|
1501
|
+
curLangObj: langObj
|
|
1502
|
+
});
|
|
1503
|
+
});
|
|
1504
|
+
needCompletionList.forEach(async item => {
|
|
1505
|
+
await completionTranslateAndWriteConfigFile(obj, item.curLangObj, item.key);
|
|
1506
|
+
});
|
|
1476
1507
|
}
|
|
1477
1508
|
|
|
1478
1509
|
/**
|
|
1479
|
-
* @description:
|
|
1480
|
-
* @
|
|
1510
|
+
* @description: 补全新增语言翻译写入函数
|
|
1511
|
+
* @param langObj
|
|
1512
|
+
* @param curLangObj
|
|
1513
|
+
* @param translateKey
|
|
1514
|
+
* @return
|
|
1481
1515
|
*/
|
|
1482
|
-
function
|
|
1483
|
-
|
|
1516
|
+
async function completionTranslateAndWriteConfigFile(langObj, curLangObj, translateKey) {
|
|
1517
|
+
// 构建需要翻译的语言映射对象
|
|
1518
|
+
// langObj: 源语言的键值对映射,格式为 { hash: sourceText }
|
|
1519
|
+
// curLangObj: 目标语言的键值对映射,格式为 { hash: targetText },未翻译的值为空
|
|
1520
|
+
|
|
1521
|
+
// 创建待翻译内容对象,仅包含未翻译的条目,key是hash,value是源语言的对应hash的文本
|
|
1522
|
+
const transLangObj = {};
|
|
1523
|
+
Object.keys(langObj).forEach(key => {
|
|
1524
|
+
// 如果目标语言中对应的翻译为空,则将 源语言的对应hash的文本 加入待翻译内容对象 中
|
|
1525
|
+
if (curLangObj[key] === undefined) {
|
|
1526
|
+
transLangObj[key] = langObj[key];
|
|
1527
|
+
}
|
|
1528
|
+
});
|
|
1529
|
+
if (!Object.values(transLangObj).length) return;
|
|
1530
|
+
|
|
1531
|
+
// ─── 分块翻译流程开始 ───
|
|
1532
|
+
|
|
1533
|
+
console.info('进入新增语言补全翻译...');
|
|
1534
|
+
|
|
1535
|
+
// 调用抽离的函数
|
|
1536
|
+
const resultValues = await translateChunks(transLangObj, translateKey);
|
|
1537
|
+
// ─── 分块翻译流程结束 ───
|
|
1538
|
+
|
|
1539
|
+
if (resultValues.length !== Object.values(langObj).length) {
|
|
1540
|
+
console.error('翻译异常,翻译结果缺失❌');
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
let newLangObjMap = resultValues;
|
|
1544
|
+
console.info('翻译成功⭐️⭐️⭐️');
|
|
1545
|
+
Object.keys(transLangObj).forEach((key, index) => {
|
|
1546
|
+
curLangObj[key] = newLangObjMap[index];
|
|
1547
|
+
});
|
|
1548
|
+
console.log('开始写入JSON配置文件...');
|
|
1549
|
+
const configLangObj = JSON.parse(getLangTranslateJSONFile());
|
|
1550
|
+
Object.keys(transLangObj).forEach(key => {
|
|
1551
|
+
configLangObj[key][translateKey] = curLangObj[key];
|
|
1552
|
+
});
|
|
1484
1553
|
try {
|
|
1485
|
-
|
|
1486
|
-
|
|
1554
|
+
setLangTranslateJSONFile(configLangObj);
|
|
1555
|
+
console.info('JSON配置文件写入成功⭐️⭐️⭐️');
|
|
1487
1556
|
} catch (error) {
|
|
1488
|
-
|
|
1489
|
-
console.log('❌读取JSON配置文件异常,文件不存在');
|
|
1490
|
-
} else {
|
|
1491
|
-
console.log('❌读取JSON配置文件异常,无法读取文件');
|
|
1492
|
-
}
|
|
1493
|
-
return JSON.stringify({});
|
|
1557
|
+
console.error('❌JSON配置文件写入失败' + error);
|
|
1494
1558
|
}
|
|
1559
|
+
console.info('新增语言翻译补全成功⭐️⭐️⭐️');
|
|
1495
1560
|
}
|
|
1496
1561
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
//
|
|
1505
|
-
const
|
|
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
|
+
}
|
|
1506
1576
|
|
|
1507
|
-
//
|
|
1508
|
-
const
|
|
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
|
+
};
|
|
1509
1585
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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}__`;
|
|
1513
1608
|
});
|
|
1514
|
-
|
|
1515
|
-
// 返回当前语言的hash: value映射对象
|
|
1516
|
-
// 例如: 'zh-cn' > {'hash1': '你好', 'hash2': '世界'}
|
|
1517
|
-
// 'en' > {'hash1': 'hello', 'hash2': 'world'}
|
|
1518
|
-
return langObj;
|
|
1519
1609
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
*/
|
|
1525
|
-
function setLangTranslateJSONFile(obj) {
|
|
1526
|
-
const filePath = path.join(exports.option.globalPath, 'index.json');
|
|
1527
|
-
const jsonObj = jsonFormatter(obj);
|
|
1528
|
-
if (fs.existsSync(filePath)) {
|
|
1529
|
-
fs.writeFileSync(filePath, jsonObj);
|
|
1530
|
-
} else {
|
|
1531
|
-
console.log('❌JSON配置文件写入异常,文件不存在');
|
|
1532
|
-
}
|
|
1610
|
+
function restoreInterpolationPlaceholders(value, placeholders) {
|
|
1611
|
+
return value.replace(/__GC_I18N_PH_(\d+)__/g, (match, index) => {
|
|
1612
|
+
return placeholders[Number(index)] || match;
|
|
1613
|
+
});
|
|
1533
1614
|
}
|
|
1534
|
-
|
|
1535
1615
|
/**
|
|
1536
|
-
* @description:
|
|
1537
|
-
* @return {
|
|
1616
|
+
* @description: 清理多余的翻译配置JSON文件
|
|
1617
|
+
* @return {void} 无返回值
|
|
1538
1618
|
*/
|
|
1539
|
-
function
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
//
|
|
1543
|
-
|
|
1544
|
-
// option.langKey.forEach(item => {
|
|
1545
|
-
// langObjMap[item] = getLangObjByJSONFileWithLangKey(item)
|
|
1546
|
-
// })
|
|
1547
|
-
// if (fs.existsSync(option.distPath)) {
|
|
1548
|
-
// fs.readdir(option.distPath, (err, files) => {
|
|
1549
|
-
// if (err) {
|
|
1550
|
-
// console.error('❌构建文件夹为空,翻译配置无法写入')
|
|
1551
|
-
// return
|
|
1552
|
-
// }
|
|
1619
|
+
function cleanupUnusedTranslations() {
|
|
1620
|
+
if (!exports.option.isClear) return;
|
|
1621
|
+
console.log('🧹 进入清理流程');
|
|
1622
|
+
// 获取当前的语言对象,如果不存在则使用空对象
|
|
1623
|
+
const langObj = getLangObj() || {};
|
|
1553
1624
|
|
|
1554
|
-
//
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
//
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
//
|
|
1561
|
-
|
|
1562
|
-
//
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
// try {
|
|
1570
|
-
// // 翻译配置写入主文件
|
|
1571
|
-
// fs.writeFileSync(
|
|
1572
|
-
// filePath,
|
|
1573
|
-
// `globalThis['${option.namespace}']={};${buildLangConfigString}` +
|
|
1574
|
-
// data
|
|
1575
|
-
// )
|
|
1576
|
-
// console.info('恭喜:翻译配置写入构建主文件成功🌟🌟🌟')
|
|
1577
|
-
// } catch (err) {
|
|
1578
|
-
// console.error('翻译配置写入构建主文件失败:', err)
|
|
1579
|
-
// }
|
|
1580
|
-
// })
|
|
1581
|
-
// }
|
|
1582
|
-
// })
|
|
1583
|
-
// })
|
|
1584
|
-
// }
|
|
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);
|
|
1585
1640
|
}
|
|
1586
1641
|
|
|
1587
|
-
var
|
|
1642
|
+
var translate = /*#__PURE__*/Object.freeze({
|
|
1588
1643
|
__proto__: null,
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
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
|
|
1596
1656
|
});
|
|
1597
1657
|
|
|
1598
1658
|
/*
|
|
1599
1659
|
* @Author: xiaoshanwen
|
|
1600
|
-
* @Date: 2023-10-
|
|
1601
|
-
* @LastEditTime: 2025-03-
|
|
1602
|
-
* @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/
|
|
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
|
|
1603
1663
|
*/
|
|
1604
|
-
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1664
|
+
function getOriginRegex() {
|
|
1665
|
+
const originLang = FunctionFactoryOption.originLang;
|
|
1666
|
+
return REGEX_MAP[originLang];
|
|
1667
|
+
}
|
|
1608
1668
|
|
|
1609
1669
|
/**
|
|
1610
|
-
* @description:
|
|
1611
|
-
* @param {string}
|
|
1612
|
-
* @param {string} value
|
|
1670
|
+
* @description: 是否包含来源语言字符
|
|
1671
|
+
* @param {string} code
|
|
1613
1672
|
* @return {*}
|
|
1614
1673
|
*/
|
|
1615
|
-
function
|
|
1616
|
-
|
|
1617
|
-
langObj[key] = value;
|
|
1618
|
-
}
|
|
1674
|
+
function hasOriginSymbols(code) {
|
|
1675
|
+
return getOriginRegex().test(code);
|
|
1619
1676
|
}
|
|
1620
1677
|
|
|
1621
1678
|
/**
|
|
1622
|
-
* @description:
|
|
1679
|
+
* @description: 过滤注释
|
|
1680
|
+
* @param {string} code
|
|
1623
1681
|
* @return {*}
|
|
1624
1682
|
*/
|
|
1625
|
-
function
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
+
};
|
|
1628
1692
|
|
|
1629
1693
|
/**
|
|
1630
|
-
* @description:
|
|
1631
|
-
* @param {
|
|
1694
|
+
* @description: 用于判断提供的值是否符合正则表达式数组中的任一规则,符合则跳过
|
|
1695
|
+
* @param {*} value
|
|
1696
|
+
* @param {*} regexArray
|
|
1632
1697
|
* @return {*}
|
|
1633
1698
|
*/
|
|
1634
|
-
function
|
|
1635
|
-
|
|
1636
|
-
|
|
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
|
+
}
|
|
1637
1705
|
}
|
|
1706
|
+
return false; // 如果所有规则都不符合,返回 false
|
|
1638
1707
|
}
|
|
1639
1708
|
|
|
1640
|
-
// todo 类型修复
|
|
1641
1709
|
/**
|
|
1642
|
-
*
|
|
1643
|
-
*
|
|
1644
|
-
*
|
|
1645
|
-
* 1. 加载现有翻译文件
|
|
1646
|
-
* 2. 对比找出新增需要翻译的内容
|
|
1647
|
-
* 3. 分块并行翻译所有目标语言
|
|
1648
|
-
* 4. 合并翻译结果并生成最终配置文件
|
|
1649
|
-
*
|
|
1650
|
-
* 异常处理:
|
|
1651
|
-
* - 翻译结果不完整时中断流程
|
|
1652
|
-
* - 文件读写失败时明确报错
|
|
1710
|
+
* @description: 用于解析抽象语法树中的调用表达式,并提取出调用的名称,如a.b.c() 取 c。
|
|
1711
|
+
* @param {any} node
|
|
1712
|
+
* @return {*}
|
|
1653
1713
|
*/
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
// 加载所有语言的现有翻译内容
|
|
1662
|
-
// 获取当前待翻译内容(深拷贝避免污染原始数据)
|
|
1663
|
-
const currentLangObj = JSON.parse(JSON.stringify(getLangObj()));
|
|
1664
|
-
exports.option.langKey.forEach(lang => {
|
|
1665
|
-
const keyMap = getLangObjByJSONFileWithLangKey(lang);
|
|
1666
|
-
originLangObjMap[lang] = keyMap;
|
|
1667
|
-
});
|
|
1668
|
-
|
|
1669
|
-
// 筛选需要翻译的新增内容
|
|
1670
|
-
const transLangObj = {};
|
|
1671
|
-
Object.keys(currentLangObj).forEach(key => {
|
|
1672
|
-
if (!originLangObjMap[exports.option.originLang][key]) {
|
|
1673
|
-
transLangObj[key] = currentLangObj[key];
|
|
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);
|
|
1674
1721
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
// 无新内容提前退出
|
|
1678
|
-
if (Object.keys(transLangObj).length === 0) {
|
|
1679
|
-
return;
|
|
1722
|
+
name = callObj.object.name + name;
|
|
1723
|
+
return name;
|
|
1680
1724
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
const currentLang = exports.option.langKey[langIndex];
|
|
1688
|
-
|
|
1689
|
-
// 原始语言直接存储原文,读取扫出来的元素翻译内容
|
|
1690
|
-
if (langIndex === 0) {
|
|
1691
|
-
newLangObjMap[exports.option.originLang] = Object.values(transLangObj);
|
|
1692
|
-
continue;
|
|
1693
|
-
}
|
|
1694
|
-
console.info('开始自动翻译...');
|
|
1695
|
-
|
|
1696
|
-
// ─── 分块翻译流程开始 ───
|
|
1697
|
-
const translatedValues = await translateChunks(transLangObj, exports.option.langKey[langIndex]);
|
|
1698
|
-
// ─── 分块翻译流程结束 ───=
|
|
1699
|
-
|
|
1700
|
-
// ─── 翻译结果校验 ───
|
|
1701
|
-
if (translatedValues.length !== Object.keys(transLangObj).length) {
|
|
1702
|
-
console.error('❌ 使用付费翻译时,请检查翻译API额度是否充足,或是否已申请对应翻译API使用权限');
|
|
1703
|
-
console.error(`❌ 翻译结果不完整
|
|
1704
|
-
预期数量: ${Object.keys(transLangObj).length}
|
|
1705
|
-
实际数量: ${translatedValues.length}
|
|
1706
|
-
样例数据: ${JSON.stringify(translatedValues.slice(0, 3))}`);
|
|
1707
|
-
return;
|
|
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 || '';
|
|
1708
1731
|
}
|
|
1709
|
-
|
|
1710
|
-
// 存储当前语言翻译结果
|
|
1711
|
-
newLangObjMap[currentLang] = translatedValues;
|
|
1712
|
-
console.info(`✅ ${currentLang} 翻译完成`);
|
|
1713
1732
|
}
|
|
1733
|
+
return callName;
|
|
1734
|
+
}
|
|
1714
1735
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
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
|
+
};
|
|
1721
1745
|
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
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
|
+
}
|
|
1731
1756
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
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);
|
|
1739
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
|
+
};
|
|
1740
1814
|
}
|
|
1741
1815
|
|
|
1742
1816
|
/**
|
|
1743
|
-
* @description:
|
|
1744
|
-
* @param {
|
|
1817
|
+
* @description: 生成唯一id
|
|
1818
|
+
* @param {string} key
|
|
1745
1819
|
* @return {*}
|
|
1746
1820
|
*/
|
|
1747
|
-
function
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
needCompletionList.push({
|
|
1757
|
-
key: item,
|
|
1758
|
-
curLangObj: langObj
|
|
1759
|
-
});
|
|
1760
|
-
});
|
|
1761
|
-
needCompletionList.forEach(async item => {
|
|
1762
|
-
await completionTranslateAndWriteConfigFile(obj, item.curLangObj, item.key);
|
|
1763
|
-
});
|
|
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;
|
|
1827
|
+
}
|
|
1828
|
+
const id = Math.abs(hash).toString(36) + key.length.toString(36);
|
|
1829
|
+
return id;
|
|
1764
1830
|
}
|
|
1765
1831
|
|
|
1766
1832
|
/**
|
|
1767
|
-
* @description:
|
|
1768
|
-
* @param
|
|
1769
|
-
* @
|
|
1770
|
-
* @param translateKey
|
|
1771
|
-
* @return
|
|
1833
|
+
* @description: unicode转普通字符串
|
|
1834
|
+
* @param {string} str
|
|
1835
|
+
* @return {*}
|
|
1772
1836
|
*/
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
// curLangObj: 目标语言的键值对映射,格式为 { hash: targetText },未翻译的值为空
|
|
1777
|
-
|
|
1778
|
-
// 创建待翻译内容对象,仅包含未翻译的条目,key是hash,value是源语言的对应hash的文本
|
|
1779
|
-
const transLangObj = {};
|
|
1780
|
-
Object.keys(langObj).forEach(key => {
|
|
1781
|
-
// 如果目标语言中对应的翻译为空,则将 源语言的对应hash的文本 加入待翻译内容对象 中
|
|
1782
|
-
if (curLangObj[key] === undefined) {
|
|
1783
|
-
transLangObj[key] = langObj[key];
|
|
1784
|
-
}
|
|
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));
|
|
1785
1840
|
});
|
|
1786
|
-
|
|
1841
|
+
};
|
|
1787
1842
|
|
|
1788
|
-
|
|
1843
|
+
/**
|
|
1844
|
+
* @description: 有道翻译 标识截取
|
|
1845
|
+
* @param {string} q
|
|
1846
|
+
* @return {*}
|
|
1847
|
+
*/
|
|
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
|
+
}
|
|
1789
1859
|
|
|
1790
|
-
|
|
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
|
+
}
|
|
1791
1867
|
|
|
1792
|
-
//
|
|
1793
|
-
|
|
1794
|
-
|
|
1868
|
+
// 处理循环引用
|
|
1869
|
+
if (cache.has(value)) {
|
|
1870
|
+
return cache.get(value);
|
|
1871
|
+
}
|
|
1795
1872
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
return;
|
|
1873
|
+
// 处理特殊对象类型
|
|
1874
|
+
if (value instanceof Date) {
|
|
1875
|
+
return new Date(value);
|
|
1799
1876
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
Object.keys(transLangObj).forEach((key, index) => {
|
|
1803
|
-
curLangObj[key] = newLangObjMap[index];
|
|
1804
|
-
});
|
|
1805
|
-
console.log('开始写入JSON配置文件...');
|
|
1806
|
-
const configLangObj = JSON.parse(getLangTranslateJSONFile());
|
|
1807
|
-
Object.keys(transLangObj).forEach(key => {
|
|
1808
|
-
configLangObj[key][translateKey] = curLangObj[key];
|
|
1809
|
-
});
|
|
1810
|
-
try {
|
|
1811
|
-
setLangTranslateJSONFile(configLangObj);
|
|
1812
|
-
console.info('JSON配置文件写入成功⭐️⭐️⭐️');
|
|
1813
|
-
} catch (error) {
|
|
1814
|
-
console.error('❌JSON配置文件写入失败' + error);
|
|
1877
|
+
if (value instanceof RegExp) {
|
|
1878
|
+
return new RegExp(value.source, value.flags);
|
|
1815
1879
|
}
|
|
1816
|
-
console.info('新增语言翻译补全成功⭐️⭐️⭐️');
|
|
1817
|
-
}
|
|
1818
1880
|
|
|
1819
|
-
//
|
|
1820
|
-
|
|
1821
|
-
const {
|
|
1822
|
-
translator
|
|
1823
|
-
} = exports.option;
|
|
1824
|
-
const placeholders = [];
|
|
1825
|
-
const protectedValues = Object.values(transLangObj).map(value => protectInterpolationPlaceholders(value, placeholders));
|
|
1826
|
-
// 获取分块后的文本列表
|
|
1827
|
-
const translationChunks = createTextSplitter(protectedValues, translator.option.maxChunkSize);
|
|
1828
|
-
// 并行执行分块翻译
|
|
1829
|
-
const translatePromises = [];
|
|
1830
|
-
for (let i = 0; i < translationChunks.length; i++) {
|
|
1831
|
-
translatePromises.push(translator.translate(translationChunks[i], exports.option.originLang, translateKey, SEPARATOR));
|
|
1832
|
-
}
|
|
1881
|
+
// 初始化克隆容器
|
|
1882
|
+
const clone = Array.isArray(value) ? [] : {};
|
|
1833
1883
|
|
|
1834
|
-
//
|
|
1835
|
-
|
|
1836
|
-
return chunkResults.map(item => {
|
|
1837
|
-
item = restoreInterpolationPlaceholders(item, placeholders);
|
|
1838
|
-
// 提取分割逻辑到单独的函数中,提高代码复用性
|
|
1839
|
-
const splitTranslation = (text, separatorRegex) => {
|
|
1840
|
-
return text.split(separatorRegex).map(v => v.trim());
|
|
1841
|
-
};
|
|
1884
|
+
// 缓存对象防止循环引用
|
|
1885
|
+
cache.set(value, clone);
|
|
1842
1886
|
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
value = splitTranslation(item, new RegExp(`\\n${separator}\\n`));
|
|
1852
|
-
}
|
|
1853
|
-
const realList = value.filter(Boolean);
|
|
1854
|
-
if (realList.length > 1) {
|
|
1855
|
-
return realList;
|
|
1856
|
-
}
|
|
1857
|
-
return splitTranslation(item, SPLIT_SEPARATOR_REGEX);
|
|
1858
|
-
}
|
|
1859
|
-
}).flat();
|
|
1860
|
-
}
|
|
1861
|
-
function protectInterpolationPlaceholders(value, placeholders) {
|
|
1862
|
-
return value.replace(/\$\{[^}]*\}/g, match => {
|
|
1863
|
-
const index = placeholders.push(match) - 1;
|
|
1864
|
-
return `__GC_I18N_PH_${index}__`;
|
|
1865
|
-
});
|
|
1866
|
-
}
|
|
1867
|
-
function restoreInterpolationPlaceholders(value, placeholders) {
|
|
1868
|
-
return value.replace(/__GC_I18N_PH_(\d+)__/g, (match, index) => {
|
|
1869
|
-
return placeholders[Number(index)] || match;
|
|
1870
|
-
});
|
|
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;
|
|
1871
1895
|
}
|
|
1896
|
+
|
|
1897
|
+
var base = /*#__PURE__*/Object.freeze({
|
|
1898
|
+
__proto__: null,
|
|
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
|
|
1912
|
+
});
|
|
1913
|
+
|
|
1914
|
+
// 代码灵感来自https://github.com/dadidi9900/auto-plugins-json-translate/blob/main/src/services/translationService.ts
|
|
1872
1915
|
/**
|
|
1873
|
-
*
|
|
1874
|
-
*
|
|
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
|
+
* ```
|
|
1875
1932
|
*/
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
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解析。
|
|
1884
1951
|
|
|
1885
|
-
|
|
1886
|
-
|
|
1952
|
+
参考例子:
|
|
1953
|
+
示例1:
|
|
1954
|
+
输入:zh-cn -> en { "awfgx": "你好", "qwfga": "世界" }
|
|
1955
|
+
输出:{ "awfgx": "Hello", "qwfga": "World" }
|
|
1887
1956
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1957
|
+
示例2:
|
|
1958
|
+
输入:de -> fr { "gweaq": "Hallo", "wtrts": "Welt" }
|
|
1959
|
+
输出:{ "gweaq": "Bonjour", "wtrts": "Monde" }
|
|
1960
|
+
|
|
1961
|
+
请回答问题:
|
|
1962
|
+
输入:源语言A -> 目标语言B { "wghhj": "XXX" }
|
|
1963
|
+
输出:
|
|
1964
|
+
|
|
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
|
+
}
|
|
1897
2017
|
}
|
|
1898
2018
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
setLangObj: setLangObj
|
|
1911
|
-
});
|
|
2019
|
+
/*
|
|
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
|
|
2024
|
+
*/
|
|
2025
|
+
let TranslateApiEnum = /*#__PURE__*/function (TranslateApiEnum) {
|
|
2026
|
+
TranslateApiEnum["google"] = "Google";
|
|
2027
|
+
TranslateApiEnum["youdao"] = "Youdao";
|
|
2028
|
+
return TranslateApiEnum;
|
|
2029
|
+
}({});
|
|
1912
2030
|
|
|
1913
2031
|
/*
|
|
1914
|
-
* @Date: 2025-
|
|
2032
|
+
* @Date: 2025-03-16 14:12:30
|
|
1915
2033
|
* @LastEditors: xiaoshan
|
|
1916
|
-
* @LastEditTime: 2025-
|
|
1917
|
-
* @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/
|
|
2034
|
+
* @LastEditTime: 2025-03-16 14:13:42
|
|
2035
|
+
* @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/enums/option.ts
|
|
1918
2036
|
*/
|
|
1919
|
-
|
|
1920
2037
|
/**
|
|
1921
|
-
*
|
|
1922
|
-
* @param values 待分块的原始文本数组
|
|
1923
|
-
* @param maxChunkSize 最大分块长度
|
|
1924
|
-
* @returns 包含分块文本和重组方法的对象
|
|
1925
|
-
*
|
|
1926
|
-
* 功能特性:
|
|
1927
|
-
* 1. 自动合并小文本为最大可能块
|
|
1928
|
-
* 2. 处理超长文本并给出警告
|
|
1929
|
-
* 3. 保证块长度不超过限制
|
|
1930
|
-
* 4. 保留原始顺序和分隔符语义
|
|
2038
|
+
* 翻译类型枚举
|
|
1931
2039
|
*/
|
|
1932
|
-
function
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
const result = []; // 最终分块结果
|
|
1938
|
-
let buffer = []; // 当前累积块缓冲区
|
|
1939
|
-
let currentSize = 0; // 当前缓冲区字符数(含分隔符)
|
|
1940
|
-
|
|
1941
|
-
/**
|
|
1942
|
-
* 提交缓冲区内容到结果集
|
|
1943
|
-
* - 将缓冲区内容用分隔符连接
|
|
1944
|
-
* - 重置缓冲区和计数器
|
|
1945
|
-
*/
|
|
1946
|
-
const commitBuffer = () => {
|
|
1947
|
-
if (buffer.length > 0) {
|
|
1948
|
-
// 计算实际连接长度用于验证
|
|
1949
|
-
const actualLength = buffer.join(SEPARATOR).length;
|
|
1950
|
-
if (actualLength > maxChunkSize) {
|
|
1951
|
-
console.warn(`缓冲区提交异常:生成块长度 ${actualLength} 超过限制`);
|
|
1952
|
-
}
|
|
1953
|
-
result.push(buffer.join(SEPARATOR));
|
|
1954
|
-
buffer = [];
|
|
1955
|
-
currentSize = 0;
|
|
1956
|
-
}
|
|
1957
|
-
};
|
|
1958
|
-
|
|
1959
|
-
// 主处理循环:遍历所有原始文本项
|
|
1960
|
-
for (const value of values) {
|
|
1961
|
-
// 计算需要新增的空间:文本长度 + 分隔符(非首项)
|
|
1962
|
-
const neededSpace = value.length + (buffer.length > 0 ? SEP_LENGTH : 0);
|
|
2040
|
+
let TranslateTypeEnum = /*#__PURE__*/function (TranslateTypeEnum) {
|
|
2041
|
+
TranslateTypeEnum["FULL_AUTO"] = "full-auto";
|
|
2042
|
+
TranslateTypeEnum["SEMI_AUTO"] = "semi-auto";
|
|
2043
|
+
return TranslateTypeEnum;
|
|
2044
|
+
}({});
|
|
1963
2045
|
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
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
|
+
*/
|
|
1968
2052
|
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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'
|
|
1977
2100
|
}
|
|
1978
|
-
// 结果直接新增一个超长文本
|
|
1979
|
-
result.push(value);
|
|
1980
|
-
continue;
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
// ─── 正常分块逻辑 ───
|
|
1984
|
-
// 空间不足时提交当前缓冲区
|
|
1985
|
-
if (currentSize + neededSpace > maxChunkSize) {
|
|
1986
|
-
commitBuffer();
|
|
1987
2101
|
}
|
|
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
|
+
// 是否清除已经不在上下文中的内容(清除项目中不再使用到的源语言键值对)
|
|
1988
2136
|
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
// 提交最终未完成的缓冲区内容
|
|
1995
|
-
commitBuffer();
|
|
2137
|
+
/**
|
|
2138
|
+
* 是否保留空格
|
|
2139
|
+
*/
|
|
2140
|
+
isClearSpace: true
|
|
2141
|
+
};
|
|
1996
2142
|
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2143
|
+
/**
|
|
2144
|
+
* 类型定义:插件配置选项类型
|
|
2145
|
+
*/
|
|
2000
2146
|
|
|
2001
|
-
|
|
2002
|
-
*
|
|
2003
|
-
* @LastEditors: xiaoshan
|
|
2004
|
-
* @LastEditTime: 2025-03-31 10:29:49
|
|
2005
|
-
* @FilePath: /i18n_translation_vite/packages/autoI18nPluginCore/src/utils/split.ts
|
|
2147
|
+
/**
|
|
2148
|
+
* 全局插件配置实例,复制自默认配置
|
|
2006
2149
|
*/
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2150
|
+
exports.option = {
|
|
2151
|
+
...DEFAULT_OPTION
|
|
2152
|
+
};
|
|
2010
2153
|
|
|
2011
|
-
// todo 这个切割函数可以优化,性能可能很差
|
|
2012
2154
|
/**
|
|
2013
|
-
*
|
|
2014
|
-
* @param str - 要分割的字符串。
|
|
2015
|
-
* @param separatorRegex - 用于分割字符串的正则表达式。
|
|
2016
|
-
* @returns 分割并拼接后的字符串数组。
|
|
2155
|
+
* 类型定义:用户传入的配置选项
|
|
2017
2156
|
*/
|
|
2157
|
+
|
|
2018
2158
|
/**
|
|
2019
|
-
*
|
|
2020
|
-
*
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2023
|
-
* 2. 然后将连续的标点符号和符合分隔符正则的部分重新连接
|
|
2024
|
-
* 3. 最后将不符合分隔符正则的相邻部分合并
|
|
2025
|
-
*
|
|
2026
|
-
* @param str - 需要分割的源字符串
|
|
2027
|
-
* @param separatorRegex - 用于分割的正则表达式
|
|
2028
|
-
* @returns 处理后的字符串数组
|
|
2159
|
+
* 通过深度克隆提供的选项信息生成一个用户选项对象,
|
|
2160
|
+
* 确保原始配置不被修改。它还根据用户的配置初始化翻译器。
|
|
2161
|
+
* @param optionInfo - 包含用户选项和翻译器细节的选项信息。
|
|
2162
|
+
* @returns 一个新的、可能已初始化翻译器的用户选项对象。
|
|
2029
2163
|
*/
|
|
2030
|
-
function
|
|
2031
|
-
//
|
|
2032
|
-
const
|
|
2033
|
-
|
|
2034
|
-
const splitRegex = new RegExp(`(${separatorRegex.source}|${punctuationRegex.source})`, separatorRegex.flags);
|
|
2164
|
+
function generateUserOption(optionInfo) {
|
|
2165
|
+
// 深拷贝用户传入的配置,防止修改原配置对象
|
|
2166
|
+
const userOption = cloneDeep(optionInfo);
|
|
2167
|
+
userOption.translator = optionInfo?.translator;
|
|
2035
2168
|
|
|
2036
|
-
//
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2169
|
+
// 如果用户配置了translatorOption则初始化translator,如果都没有则不设置translator
|
|
2170
|
+
userOption.translator ||= userOption.translatorOption ? new Translator(userOption.translatorOption) : undefined;
|
|
2171
|
+
if (!userOption.translator) delete userOption.translator;
|
|
2172
|
+
return userOption;
|
|
2173
|
+
}
|
|
2040
2174
|
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2175
|
+
/**
|
|
2176
|
+
* 初始化插件配置选项
|
|
2177
|
+
* @param optionInfo 用户提供的配置选项
|
|
2178
|
+
*/
|
|
2179
|
+
function initOption(optionInfo) {
|
|
2180
|
+
// 合并默认配置和用户配置
|
|
2181
|
+
exports.option = {
|
|
2182
|
+
...DEFAULT_OPTION,
|
|
2183
|
+
...generateUserOption(optionInfo)
|
|
2184
|
+
};
|
|
2045
2185
|
|
|
2046
|
-
//
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
// 如果当前匹配字符串不为空,则将其添加到结果数组中
|
|
2053
|
-
if (currentMatch) {
|
|
2054
|
-
result.push(currentMatch);
|
|
2055
|
-
currentMatch = '';
|
|
2056
|
-
}
|
|
2057
|
-
// 将当前项添加到结果数组中
|
|
2058
|
-
result.push(item);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
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;
|
|
2191
|
+
}
|
|
2061
2192
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2193
|
+
/**
|
|
2194
|
+
* 校验插件配置选项是否完整有效
|
|
2195
|
+
* @returns {boolean} 校验结果,完整返回 true,否则返回 false
|
|
2196
|
+
*/
|
|
2197
|
+
function checkOption() {
|
|
2198
|
+
// 校验翻译调用函数是否配置
|
|
2199
|
+
if (!exports.option.translateKey) {
|
|
2200
|
+
console.error('❌请配置翻译调用函数');
|
|
2201
|
+
return false;
|
|
2065
2202
|
}
|
|
2066
2203
|
|
|
2067
|
-
//
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
const item = result[i];
|
|
2072
|
-
if (separatorRegex.test(item)) {
|
|
2073
|
-
if (tempStr) {
|
|
2074
|
-
finalResult.push(tempStr);
|
|
2075
|
-
tempStr = '';
|
|
2076
|
-
}
|
|
2077
|
-
finalResult.push(item);
|
|
2078
|
-
} else {
|
|
2079
|
-
tempStr += item;
|
|
2080
|
-
if (i === result.length - 1 || separatorRegex.test(result[i + 1])) {
|
|
2081
|
-
finalResult.push(tempStr);
|
|
2082
|
-
tempStr = '';
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2204
|
+
// 校验命名空间是否配置
|
|
2205
|
+
if (!exports.option.namespace) {
|
|
2206
|
+
console.error('❌请配置命名空间');
|
|
2207
|
+
return false;
|
|
2085
2208
|
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2209
|
+
|
|
2210
|
+
// 校验是否配置了打包后生成文件的主文件名称(如果需要打包到主包中)
|
|
2211
|
+
if (exports.option.buildToDist && !exports.option.distKey) {
|
|
2212
|
+
console.log('❌请配置打包后生成文件的主文件名称');
|
|
2213
|
+
return false;
|
|
2088
2214
|
}
|
|
2089
|
-
return finalResult;
|
|
2090
|
-
}
|
|
2091
2215
|
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
function checkNeedSplit(str) {
|
|
2098
|
-
// 检查字符串中是否包含需要切割的特殊字符
|
|
2099
|
-
return str.includes('\n') || str.includes('\\') || str.includes('\r') || str.includes('\t') || str.includes('\v') || str.includes('\f') || str.includes('>') || str.includes('<');
|
|
2100
|
-
}
|
|
2216
|
+
// 校验是否配置了打包后生成文件的位置(如果需要打包到主包中)
|
|
2217
|
+
if (exports.option.buildToDist && !exports.option.distPath) {
|
|
2218
|
+
console.log('❌请配置打包后生成文件的位置');
|
|
2219
|
+
return false;
|
|
2220
|
+
}
|
|
2101
2221
|
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
*/
|
|
2107
|
-
function convertToTemplateLiteral(strArray, option) {
|
|
2108
|
-
const quasis = [];
|
|
2109
|
-
const expressions = [];
|
|
2110
|
-
strArray.forEach((str, index) => {
|
|
2111
|
-
if (index === 0) {
|
|
2112
|
-
if (getOriginRegex().test(str)) {
|
|
2113
|
-
quasis.push(types__namespace.templateElement({
|
|
2114
|
-
raw: '',
|
|
2115
|
-
cooked: ''
|
|
2116
|
-
}, false));
|
|
2117
|
-
expressions.push(createI18nTranslator({
|
|
2118
|
-
value: str,
|
|
2119
|
-
isExpression: true,
|
|
2120
|
-
insertOption: option
|
|
2121
|
-
}));
|
|
2122
|
-
} else {
|
|
2123
|
-
quasis.push(types__namespace.templateElement({
|
|
2124
|
-
raw: str,
|
|
2125
|
-
cooked: str
|
|
2126
|
-
}, false));
|
|
2127
|
-
}
|
|
2128
|
-
} else {
|
|
2129
|
-
if (getOriginRegex().test(str)) {
|
|
2130
|
-
expressions.push(createI18nTranslator({
|
|
2131
|
-
value: str,
|
|
2132
|
-
isExpression: true,
|
|
2133
|
-
insertOption: option
|
|
2134
|
-
}));
|
|
2135
|
-
} else {
|
|
2136
|
-
quasis.push(types__namespace.templateElement({
|
|
2137
|
-
raw: str,
|
|
2138
|
-
cooked: str
|
|
2139
|
-
}, false));
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
});
|
|
2143
|
-
if (quasis.length === expressions.length) {
|
|
2144
|
-
quasis.push(types__namespace.templateElement({
|
|
2145
|
-
raw: '',
|
|
2146
|
-
cooked: ''
|
|
2147
|
-
}, true));
|
|
2148
|
-
} else if (quasis.length > expressions.length) {
|
|
2149
|
-
quasis[quasis.length - 1].tail = true;
|
|
2222
|
+
// 校验来源语言是否配置
|
|
2223
|
+
if (!exports.option.originLang) {
|
|
2224
|
+
console.error('❌请配置来源语言');
|
|
2225
|
+
return false;
|
|
2150
2226
|
}
|
|
2151
|
-
const templateLiteral = types__namespace.templateLiteral(quasis, expressions);
|
|
2152
|
-
const deepScanCall = types__namespace.callExpression(types__namespace.identifier('$deepScan'), [templateLiteral]);
|
|
2153
|
-
// 打印转换结果
|
|
2154
|
-
// console.log('deepScanCall', (generate as any).default(deepScanCall).code)
|
|
2155
|
-
return deepScanCall;
|
|
2156
|
-
}
|
|
2157
2227
|
|
|
2158
|
-
//
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
appCode,
|
|
2163
|
-
targetLangList
|
|
2164
|
-
} = exports.option;
|
|
2165
|
-
let data = [];
|
|
2166
|
-
_.map(json, (value, key) => {
|
|
2167
|
-
_.map(targetLangList, item => {
|
|
2168
|
-
const parts = item.split('-');
|
|
2169
|
-
const lang = parts[0] + '-' + parts[1].toUpperCase();
|
|
2170
|
-
data.push({
|
|
2171
|
-
page: 'common',
|
|
2172
|
-
key,
|
|
2173
|
-
lang,
|
|
2174
|
-
value: _.get(value, item, key)
|
|
2175
|
-
});
|
|
2176
|
-
});
|
|
2177
|
-
});
|
|
2178
|
-
const res = await axios.post('http://192.168.0.104:8108/i18n-web/kv_translate/batch', {
|
|
2179
|
-
appCode,
|
|
2180
|
-
data
|
|
2181
|
-
}, {
|
|
2182
|
-
headers: {
|
|
2183
|
-
'Content-Type': 'application/json',
|
|
2184
|
-
Authorization: '728635D658AE11F18F33000C29A621CA'
|
|
2185
|
-
}
|
|
2186
|
-
});
|
|
2187
|
-
if (res.status === 200 && res.data.result === 0) {
|
|
2188
|
-
console.info('上传翻译服务成功✔');
|
|
2189
|
-
} else {
|
|
2190
|
-
console.error('上传翻译服务失败❌');
|
|
2191
|
-
console.log(`请求数据为:`, JSON.stringify(data));
|
|
2192
|
-
console.log(`返回报错信息为:`, res.data);
|
|
2228
|
+
// 校验目标翻译语言数组是否配置
|
|
2229
|
+
if (!exports.option.targetLangList || !exports.option.targetLangList.length) {
|
|
2230
|
+
console.error('❌请配置目标翻译语言数组');
|
|
2231
|
+
return false;
|
|
2193
2232
|
}
|
|
2194
|
-
}
|
|
2195
2233
|
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
});
|
|
2234
|
+
// 如果所有校验通过,返回 true
|
|
2235
|
+
return true;
|
|
2236
|
+
}
|
|
2200
2237
|
|
|
2201
2238
|
class Vue2Extends {
|
|
2202
2239
|
constructor() {
|
|
@@ -2341,7 +2378,7 @@ function handleTemplateLiteralWithExpressions(node, path) {
|
|
|
2341
2378
|
trimmedValue,
|
|
2342
2379
|
valStr
|
|
2343
2380
|
} = normalizeTranslateValue(fullValue);
|
|
2344
|
-
const id = generateId(valStr);
|
|
2381
|
+
const id = resolveLangKey(generateId(valStr), trimmedValue);
|
|
2345
2382
|
const translateNode = createTranslateNode(path, id, valStr, expressionLabels, node.expressions);
|
|
2346
2383
|
path.replaceWith(translateNode);
|
|
2347
2384
|
setLangObj(id, trimmedValue);
|
|
@@ -2411,7 +2448,7 @@ function createTrackedTranslateCall(value) {
|
|
|
2411
2448
|
trimmedValue,
|
|
2412
2449
|
valStr
|
|
2413
2450
|
} = normalizeTranslateValue(value);
|
|
2414
|
-
const id = generateId(valStr);
|
|
2451
|
+
const id = resolveLangKey(generateId(valStr), trimmedValue);
|
|
2415
2452
|
if (id && trimmedValue) {
|
|
2416
2453
|
setLangObj(id, trimmedValue);
|
|
2417
2454
|
}
|
|
@@ -2444,7 +2481,7 @@ function createTemplateLiteralTranslateCall(node) {
|
|
|
2444
2481
|
trimmedValue,
|
|
2445
2482
|
valStr
|
|
2446
2483
|
} = normalizeTranslateValue(fullValue);
|
|
2447
|
-
const id = generateId(valStr);
|
|
2484
|
+
const id = resolveLangKey(generateId(valStr), trimmedValue);
|
|
2448
2485
|
if (id && trimmedValue) {
|
|
2449
2486
|
setLangObj(id, trimmedValue);
|
|
2450
2487
|
}
|
|
@@ -2529,9 +2566,10 @@ function handleTemplateElement(node, insertOption) {
|
|
|
2529
2566
|
// 替换为字符类型翻译节点
|
|
2530
2567
|
node.value.raw = node.value.cooked = `\${${newNode}}`;
|
|
2531
2568
|
const {
|
|
2569
|
+
trimmedValue,
|
|
2532
2570
|
valStr
|
|
2533
2571
|
} = normalizeTranslateValue(value);
|
|
2534
|
-
let id = generateId(valStr);
|
|
2572
|
+
let id = resolveLangKey(generateId(valStr), trimmedValue);
|
|
2535
2573
|
if (id && value) {
|
|
2536
2574
|
setLangObj(id, value);
|
|
2537
2575
|
}
|