daodou-command 1.3.0 → 1.4.1
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/.daodourc.example +85 -0
- package/.idea/UniappTool.xml +10 -0
- package/.idea/copilot.data.migration.agent.xml +6 -0
- package/.idea/copilot.data.migration.ask.xml +6 -0
- package/.idea/copilot.data.migration.ask2agent.xml +6 -0
- package/.idea/copilot.data.migration.edit.xml +6 -0
- package/.idea/daodou-command.iml +12 -0
- package/.idea/editorJumperProjectSettings.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +72 -0
- package/CHANGELOG.md +17 -3
- package/README.md +45 -2
- package/lib/commands/config.js +57 -3
- package/lib/commands/lang.js +2 -1
- package/lib/translation/TranslationService.js +208 -0
- package/lib/translation/engines/BaseTranslator.js +90 -0
- package/lib/translation/engines/ali/AliTranslator.js +316 -0
- package/lib/translation/engines/ali/index.js +8 -0
- package/lib/translation/engines/baidu/BaiduTranslator.js +209 -0
- package/lib/translation/engines/baidu/index.js +8 -0
- package/lib/translation/engines/deepl/DeeplTranslator.js +234 -0
- package/lib/translation/engines/deepl/index.js +8 -0
- package/lib/translation/engines/google/GoogleTranslator.js +251 -0
- package/lib/translation/engines/google/index.js +8 -0
- package/lib/translation/engines/index.js +69 -0
- package/lib/translation/engines/microsoft/MicrosoftTranslator.js +190 -0
- package/lib/translation/engines/microsoft/index.js +6 -0
- package/lib/translation/engines/microsoft/services/EdgeAuthService.js +166 -0
- package/lib/translation/engines/microsoft/services/HttpClient.js +93 -0
- package/lib/translation/engines/microsoft/services/TranslatorService.js +167 -0
- package/lib/translation/engines/microsoft/types/index.js +147 -0
- package/lib/translation/engines/openai/OpenaiTranslator.js +363 -0
- package/lib/translation/engines/openai/index.js +8 -0
- package/lib/translation/engines/youdao/YoudaoTranslator.js +361 -0
- package/lib/translation/engines/youdao/index.js +8 -0
- package/lib/translation/index.js +10 -0
- package/lib/utils/config.js +99 -2
- package/lib/utils/translation.js +58 -100
- package/package.json +1 -3
- package/.daodourc +0 -25
- package/lib/utils/proxy-manager.js +0 -364
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 微软翻译引擎实现
|
|
3
|
+
* 基于Edge浏览器认证的免费翻译服务
|
|
4
|
+
*/
|
|
5
|
+
const BaseTranslator = require('../BaseTranslator');
|
|
6
|
+
const MicrosoftTranslatorService = require('./services/TranslatorService');
|
|
7
|
+
const { LANG_CODES, TEXT_TYPES } = require('./types');
|
|
8
|
+
|
|
9
|
+
class MicrosoftTranslator extends BaseTranslator {
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
super(config);
|
|
12
|
+
this.name = 'Microsoft Translator';
|
|
13
|
+
this.priority = 1; // 最高优先级
|
|
14
|
+
this.intervalLimit = 1000; // 1秒间隔限制
|
|
15
|
+
this.contentLengthLimit = 50000; // 50KB内容长度限制
|
|
16
|
+
|
|
17
|
+
// 初始化翻译服务
|
|
18
|
+
this.service = new MicrosoftTranslatorService();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 获取翻译器名称
|
|
23
|
+
*/
|
|
24
|
+
getName() {
|
|
25
|
+
return this.name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 获取优先级
|
|
30
|
+
*/
|
|
31
|
+
getPriority() {
|
|
32
|
+
return this.priority;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 检查翻译器是否可用
|
|
37
|
+
*/
|
|
38
|
+
async isAvailable() {
|
|
39
|
+
try {
|
|
40
|
+
// 尝试获取访问令牌来检查服务是否可用
|
|
41
|
+
await this.service.httpClient.authService.getAccessToken();
|
|
42
|
+
return true;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 翻译文本
|
|
50
|
+
*/
|
|
51
|
+
async translate(text, sourceLang, targetLang, options = {}) {
|
|
52
|
+
if (!text || !targetLang) {
|
|
53
|
+
throw new Error('文本和目标语言不能为空');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return await this.withRetry(async () => {
|
|
57
|
+
// 语言代码转换
|
|
58
|
+
const msSourceLang = this.convertLangCode(sourceLang);
|
|
59
|
+
const msTargetLang = this.convertLangCode(targetLang);
|
|
60
|
+
|
|
61
|
+
// 执行翻译
|
|
62
|
+
const result = await this.service.translate(text, msSourceLang, msTargetLang, TEXT_TYPES.PLAIN);
|
|
63
|
+
|
|
64
|
+
if (!result || !result.translations || result.translations.length === 0) {
|
|
65
|
+
throw new Error('翻译结果为空');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const translation = result.translations[0];
|
|
69
|
+
const detectedLang = result.detectedLanguage?.language || sourceLang;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
text: translation.text || text,
|
|
73
|
+
sourceLang: detectedLang,
|
|
74
|
+
targetLang: targetLang,
|
|
75
|
+
engine: this.name,
|
|
76
|
+
success: true,
|
|
77
|
+
confidence: translation.confidence || 1.0,
|
|
78
|
+
alternatives: []
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 翻译HTML文档
|
|
85
|
+
*/
|
|
86
|
+
async translateHtml(htmlContent, sourceLang, targetLang, options = {}) {
|
|
87
|
+
if (!htmlContent || !targetLang) {
|
|
88
|
+
throw new Error('HTML内容和目标语言不能为空');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return await this.withRetry(async () => {
|
|
92
|
+
// 语言代码转换
|
|
93
|
+
const msSourceLang = this.convertLangCode(sourceLang);
|
|
94
|
+
const msTargetLang = this.convertLangCode(targetLang);
|
|
95
|
+
|
|
96
|
+
// 执行HTML翻译
|
|
97
|
+
const result = await this.service.translate(htmlContent, msSourceLang, msTargetLang, TEXT_TYPES.HTML);
|
|
98
|
+
|
|
99
|
+
if (!result || !result.translations || result.translations.length === 0) {
|
|
100
|
+
throw new Error('HTML翻译结果为空');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const translation = result.translations[0];
|
|
104
|
+
const detectedLang = result.detectedLanguage?.language || sourceLang;
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
text: translation.text || htmlContent,
|
|
108
|
+
sourceLang: detectedLang,
|
|
109
|
+
targetLang: targetLang,
|
|
110
|
+
engine: this.name,
|
|
111
|
+
success: true,
|
|
112
|
+
confidence: translation.confidence || 1.0,
|
|
113
|
+
alternatives: []
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 获取支持的语言列表
|
|
120
|
+
*/
|
|
121
|
+
async getSupportedLanguages() {
|
|
122
|
+
try {
|
|
123
|
+
const languages = await this.service.getSupportedLanguages();
|
|
124
|
+
return Object.keys(languages);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// 返回默认支持的语言列表
|
|
127
|
+
return Object.values(LANG_CODES);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 语言代码转换
|
|
133
|
+
* 将标准语言代码转换为微软翻译API支持的语言代码
|
|
134
|
+
*/
|
|
135
|
+
convertLangCode(langCode) {
|
|
136
|
+
const langMap = {
|
|
137
|
+
'zh': LANG_CODES.CHINESE_SIMPLIFIED,
|
|
138
|
+
'zh-CN': LANG_CODES.CHINESE_SIMPLIFIED,
|
|
139
|
+
'zh-TW': LANG_CODES.CHINESE_TRADITIONAL,
|
|
140
|
+
'en': LANG_CODES.ENGLISH,
|
|
141
|
+
'ja': LANG_CODES.JAPANESE,
|
|
142
|
+
'ko': LANG_CODES.KOREAN,
|
|
143
|
+
'fr': LANG_CODES.FRENCH,
|
|
144
|
+
'de': LANG_CODES.GERMAN,
|
|
145
|
+
'es': LANG_CODES.SPANISH,
|
|
146
|
+
'it': LANG_CODES.ITALIAN,
|
|
147
|
+
'pt': LANG_CODES.PORTUGUESE,
|
|
148
|
+
'ru': LANG_CODES.RUSSIAN,
|
|
149
|
+
'ar': LANG_CODES.ARABIC,
|
|
150
|
+
'th': LANG_CODES.THAI,
|
|
151
|
+
'vi': LANG_CODES.VIETNAMESE,
|
|
152
|
+
'auto': LANG_CODES.AUTO
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return langMap[langCode] || langCode;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 翻译文档
|
|
160
|
+
*/
|
|
161
|
+
async translateDocumentation(text, sourceLang, targetLang, options = {}) {
|
|
162
|
+
try {
|
|
163
|
+
const msSourceLang = this.convertLangCode(sourceLang);
|
|
164
|
+
const msTargetLang = this.convertLangCode(targetLang);
|
|
165
|
+
|
|
166
|
+
const result = await this.service.translate(text, msSourceLang, msTargetLang, TEXT_TYPES.HTML);
|
|
167
|
+
|
|
168
|
+
if (!result || !result.translations || result.translations.length === 0) {
|
|
169
|
+
throw new Error('文档翻译结果为空');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const translation = result.translations[0];
|
|
173
|
+
const detectedLang = result.detectedLanguage?.language || sourceLang;
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
text: translation.text || text,
|
|
177
|
+
sourceLang: detectedLang,
|
|
178
|
+
targetLang: targetLang,
|
|
179
|
+
engine: this.name,
|
|
180
|
+
success: true,
|
|
181
|
+
confidence: translation.confidence || 1.0,
|
|
182
|
+
alternatives: []
|
|
183
|
+
};
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw new Error(`微软翻译文档失败: ${error.message}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = MicrosoftTranslator;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge浏览器认证服务
|
|
3
|
+
* 模拟Edge浏览器的认证机制,获取免费的微软翻译API访问令牌
|
|
4
|
+
*/
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
|
|
7
|
+
class MicrosoftEdgeAuthService {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.accessToken = null;
|
|
10
|
+
this.expireAt = -1;
|
|
11
|
+
this.tokenPromise = null;
|
|
12
|
+
|
|
13
|
+
// 常量定义
|
|
14
|
+
this.TIMEOUT = 60 * 1000; // 1分钟超时
|
|
15
|
+
this.PRE_EXPIRATION = 2 * 60 * 1000; // 提前2分钟刷新
|
|
16
|
+
this.DEFAULT_EXPIRATION = 10 * 60 * 1000; // 默认10分钟过期
|
|
17
|
+
this.AUTH_URL = 'https://edge.microsoft.com/translate/auth';
|
|
18
|
+
this.JWT_REGEX = /^[a-zA-Z0-9\-_]+(\.[a-zA-Z0-9\-_]+){2}$/;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 获取有效的访问令牌
|
|
23
|
+
* @returns {Promise<string>} 访问令牌
|
|
24
|
+
*/
|
|
25
|
+
async getAccessToken() {
|
|
26
|
+
// 检查当前令牌是否仍然有效
|
|
27
|
+
const validToken = this.getValidAccessToken();
|
|
28
|
+
if (validToken) {
|
|
29
|
+
return validToken;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 获取新的令牌
|
|
33
|
+
const promise = this.getTokenPromise();
|
|
34
|
+
try {
|
|
35
|
+
const token = await promise;
|
|
36
|
+
if (!token) {
|
|
37
|
+
throw new Error('无法获取访问令牌');
|
|
38
|
+
}
|
|
39
|
+
return token;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
this.clearTokenPromise(promise);
|
|
42
|
+
throw new Error(`认证失败: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 获取有效的访问令牌(同步检查)
|
|
48
|
+
* @returns {string|null} 有效的访问令牌或null
|
|
49
|
+
*/
|
|
50
|
+
getValidAccessToken() {
|
|
51
|
+
if (this.accessToken && Date.now() < this.expireAt) {
|
|
52
|
+
return this.accessToken;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 更新访问令牌
|
|
59
|
+
* @param {string} token 新的访问令牌
|
|
60
|
+
*/
|
|
61
|
+
updateAccessToken(token) {
|
|
62
|
+
const expirationTime = this.getExpirationTimeFromToken(token);
|
|
63
|
+
console.log(`更新访问令牌: ********, 过期时间: ${new Date(expirationTime)}`);
|
|
64
|
+
|
|
65
|
+
this.accessToken = token;
|
|
66
|
+
this.expireAt = expirationTime - this.PRE_EXPIRATION;
|
|
67
|
+
this.tokenPromise = null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 获取令牌Promise
|
|
72
|
+
* @returns {Promise<string>} 令牌Promise
|
|
73
|
+
*/
|
|
74
|
+
getTokenPromise() {
|
|
75
|
+
if (this.tokenPromise) {
|
|
76
|
+
return this.tokenPromise;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const promise = this.fetchAccessToken();
|
|
80
|
+
this.tokenPromise = promise;
|
|
81
|
+
return promise;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 从Edge认证服务获取访问令牌
|
|
86
|
+
* @returns {Promise<string>} 访问令牌
|
|
87
|
+
*/
|
|
88
|
+
async fetchAccessToken() {
|
|
89
|
+
try {
|
|
90
|
+
const response = await axios.get(this.AUTH_URL, {
|
|
91
|
+
headers: {
|
|
92
|
+
'Accept': '*/*',
|
|
93
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'
|
|
94
|
+
},
|
|
95
|
+
timeout: this.TIMEOUT
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const token = response.data;
|
|
99
|
+
|
|
100
|
+
// 验证JWT格式
|
|
101
|
+
if (!this.JWT_REGEX.test(token)) {
|
|
102
|
+
throw new Error('认证失败: 无效的令牌格式');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.updateAccessToken(token);
|
|
106
|
+
return token;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.warn('获取访问令牌失败:', error.message);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 清除令牌Promise
|
|
115
|
+
* @param {Promise} whosePromise 要清除的Promise
|
|
116
|
+
*/
|
|
117
|
+
clearTokenPromise(whosePromise) {
|
|
118
|
+
if (whosePromise === this.tokenPromise) {
|
|
119
|
+
this.tokenPromise = null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 从JWT令牌中提取过期时间
|
|
125
|
+
* @param {string} token JWT令牌
|
|
126
|
+
* @returns {number} 过期时间戳(毫秒)
|
|
127
|
+
*/
|
|
128
|
+
getExpirationTimeFromToken(token) {
|
|
129
|
+
try {
|
|
130
|
+
const parts = token.split('.');
|
|
131
|
+
if (parts.length !== 3) {
|
|
132
|
+
throw new Error('无效的JWT格式');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const payload = parts[1];
|
|
136
|
+
const decoded = Buffer.from(payload, 'base64url').toString('utf-8');
|
|
137
|
+
const parsed = JSON.parse(decoded);
|
|
138
|
+
|
|
139
|
+
// JWT的exp字段是秒,需要转换为毫秒
|
|
140
|
+
return parsed.exp * 1000;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.warn('解析JWT令牌失败:', error.message);
|
|
143
|
+
// 返回默认过期时间
|
|
144
|
+
return Date.now() + this.DEFAULT_EXPIRATION - this.PRE_EXPIRATION / 2;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 单例实例
|
|
150
|
+
let instance = null;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 获取MicrosoftEdgeAuthService单例实例
|
|
154
|
+
* @returns {MicrosoftEdgeAuthService} 服务实例
|
|
155
|
+
*/
|
|
156
|
+
function getAuthService() {
|
|
157
|
+
if (!instance) {
|
|
158
|
+
instance = new MicrosoftEdgeAuthService();
|
|
159
|
+
}
|
|
160
|
+
return instance;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
MicrosoftEdgeAuthService,
|
|
165
|
+
getAuthService
|
|
166
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 微软翻译API客户端
|
|
3
|
+
* 处理与微软翻译API的所有HTTP通信
|
|
4
|
+
*/
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
const { getAuthService } = require('./EdgeAuthService');
|
|
7
|
+
|
|
8
|
+
class MicrosoftHttpClient {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.baseURL = 'https://api.cognitive.microsofttranslator.com';
|
|
11
|
+
this.apiVersion = '3.0';
|
|
12
|
+
this.authService = getAuthService();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 发送POST请求到微软翻译API
|
|
17
|
+
* @param {string} endpoint API端点
|
|
18
|
+
* @param {any} data 请求数据
|
|
19
|
+
* @param {Object} options 请求选项
|
|
20
|
+
* @returns {Promise<any>} API响应
|
|
21
|
+
*/
|
|
22
|
+
async post(endpoint, data, options = {}) {
|
|
23
|
+
try {
|
|
24
|
+
const token = await this.authService.getAccessToken();
|
|
25
|
+
const url = `${this.baseURL}${endpoint}?api-version=${this.apiVersion}`;
|
|
26
|
+
|
|
27
|
+
const response = await axios.post(url, data, {
|
|
28
|
+
headers: {
|
|
29
|
+
'Authorization': `Bearer ${token}`,
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
'Accept': 'application/json',
|
|
32
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
33
|
+
},
|
|
34
|
+
params: {
|
|
35
|
+
...options.params
|
|
36
|
+
},
|
|
37
|
+
timeout: 30000,
|
|
38
|
+
...options
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return response.data;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
this.handleError(error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 处理API错误
|
|
49
|
+
* @param {Error} error 错误对象
|
|
50
|
+
*/
|
|
51
|
+
handleError(error) {
|
|
52
|
+
if (error.response) {
|
|
53
|
+
const status = error.response.status;
|
|
54
|
+
const statusText = error.response.statusText;
|
|
55
|
+
const errorData = error.response.data;
|
|
56
|
+
|
|
57
|
+
console.error(`API请求失败: ${status} ${statusText}`, errorData);
|
|
58
|
+
|
|
59
|
+
let message = `${status} ${statusText}`;
|
|
60
|
+
let code = status;
|
|
61
|
+
|
|
62
|
+
if (errorData && errorData.error) {
|
|
63
|
+
message += ` - ${errorData.error.message || errorData.error}`;
|
|
64
|
+
code = errorData.error.code || status;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error(`微软翻译API错误: ${message} (代码: ${code})`);
|
|
68
|
+
} else if (error.request) {
|
|
69
|
+
console.error('网络请求失败:', error.message);
|
|
70
|
+
throw new Error(`网络请求失败: ${error.message}`);
|
|
71
|
+
} else {
|
|
72
|
+
console.error('请求配置错误:', error.message);
|
|
73
|
+
throw new Error(`请求配置错误: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 构建查询参数
|
|
79
|
+
* @param {Object} params 参数对象
|
|
80
|
+
* @returns {string} 查询字符串
|
|
81
|
+
*/
|
|
82
|
+
buildQueryString(params) {
|
|
83
|
+
const searchParams = new URLSearchParams();
|
|
84
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
85
|
+
if (value !== undefined && value !== null) {
|
|
86
|
+
searchParams.append(key, value);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return searchParams.toString();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = MicrosoftHttpClient;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 微软翻译服务
|
|
3
|
+
* 提供翻译、词典查询、示例查询等功能
|
|
4
|
+
*/
|
|
5
|
+
const MicrosoftHttpClient = require('./HttpClient');
|
|
6
|
+
|
|
7
|
+
// 文本类型枚举
|
|
8
|
+
const TEXT_TYPES = {
|
|
9
|
+
PLAIN: 'Plain',
|
|
10
|
+
HTML: 'Html'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// 错误代码
|
|
14
|
+
const ERROR_CODES = {
|
|
15
|
+
INVALID_LANGUAGE_PAIR: 400023,
|
|
16
|
+
CONTENT_TOO_LONG: 400050,
|
|
17
|
+
AUTHENTICATION_FAILED: 401000,
|
|
18
|
+
QUOTA_EXCEEDED: 403000,
|
|
19
|
+
SERVICE_UNAVAILABLE: 503000
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
class MicrosoftTranslatorService {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.httpClient = new MicrosoftHttpClient();
|
|
25
|
+
this.MAX_DICT_INPUT_TEXT_LENGTH = 100;
|
|
26
|
+
this.MAX_DICT_EXAMPLE_INPUT_ITEM_COUNT = 10;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 翻译文本
|
|
31
|
+
* @param {string} text 要翻译的文本
|
|
32
|
+
* @param {string} from 源语言代码
|
|
33
|
+
* @param {string} to 目标语言代码
|
|
34
|
+
* @param {string} textType 文本类型 (Plain 或 Html)
|
|
35
|
+
* @returns {Promise<Object>} 翻译结果
|
|
36
|
+
*/
|
|
37
|
+
async translate(text, from, to, textType = TEXT_TYPES.PLAIN) {
|
|
38
|
+
const params = {
|
|
39
|
+
to: to,
|
|
40
|
+
textType: textType
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// 如果不是自动检测,添加源语言参数
|
|
44
|
+
if (from !== 'auto') {
|
|
45
|
+
params.from = from;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const data = [{ text: text }];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const response = await this.httpClient.post('/translate', data, { params });
|
|
52
|
+
return response[0] || null;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.message.includes('400050')) {
|
|
55
|
+
throw new Error('内容过长,无法翻译');
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 检查文本是否可以查询词典
|
|
63
|
+
* @param {string} text 要检查的文本
|
|
64
|
+
* @returns {boolean} 是否可以查询词典
|
|
65
|
+
*/
|
|
66
|
+
canLookupDictionary(text) {
|
|
67
|
+
return text.length <= this.MAX_DICT_INPUT_TEXT_LENGTH &&
|
|
68
|
+
text.split('').some(char => !char.match(/\s/));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 词典查询
|
|
73
|
+
* @param {string} text 要查询的文本
|
|
74
|
+
* @param {string} from 源语言代码
|
|
75
|
+
* @param {string} to 目标语言代码
|
|
76
|
+
* @returns {Promise<Object>} 词典查询结果
|
|
77
|
+
*/
|
|
78
|
+
async dictionaryLookup(text, from, to) {
|
|
79
|
+
const params = {
|
|
80
|
+
from: from,
|
|
81
|
+
to: to
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const data = [{ text: text }];
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const response = await this.httpClient.post('/dictionary/lookup', data, { params });
|
|
88
|
+
return response[0];
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error.message.includes('400023')) {
|
|
91
|
+
console.warn('不支持的语言对:', from, '->', to);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 词典示例查询
|
|
100
|
+
* @param {Object} dictionaryLookup 词典查询结果
|
|
101
|
+
* @param {string} from 源语言代码
|
|
102
|
+
* @param {string} to 目标语言代码
|
|
103
|
+
* @returns {Promise<Array>} 示例列表
|
|
104
|
+
*/
|
|
105
|
+
async dictionaryExamples(dictionaryLookup, from, to) {
|
|
106
|
+
if (!dictionaryLookup || !dictionaryLookup.translations) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 按置信度排序,取前几个翻译
|
|
111
|
+
const sortedTranslations = dictionaryLookup.translations
|
|
112
|
+
.sort((a, b) => b.confidence - a.confidence)
|
|
113
|
+
.filter(translation => translation.normalizedTarget.length <= this.MAX_DICT_INPUT_TEXT_LENGTH)
|
|
114
|
+
.slice(0, this.MAX_DICT_EXAMPLE_INPUT_ITEM_COUNT);
|
|
115
|
+
|
|
116
|
+
if (sortedTranslations.length === 0) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const request = sortedTranslations.map(translation => ({
|
|
121
|
+
text: dictionaryLookup.normalizedSource,
|
|
122
|
+
translation: translation.normalizedTarget
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
const params = {
|
|
126
|
+
from: from,
|
|
127
|
+
to: to
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const response = await this.httpClient.post('/dictionary/examples', request, { params });
|
|
132
|
+
return response;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn('获取词典示例失败:', error.message);
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 检查语言是否支持
|
|
141
|
+
* @param {string} language 语言代码
|
|
142
|
+
* @returns {boolean} 是否支持
|
|
143
|
+
*/
|
|
144
|
+
isLanguageSupported(language) {
|
|
145
|
+
// 这里可以添加语言支持检查逻辑
|
|
146
|
+
// 暂时返回true,实际使用时应该检查微软API支持的语言列表
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 获取支持的语言列表
|
|
152
|
+
* @returns {Promise<Array>} 支持的语言列表
|
|
153
|
+
*/
|
|
154
|
+
async getSupportedLanguages() {
|
|
155
|
+
try {
|
|
156
|
+
const response = await this.httpClient.post('/languages', {}, {
|
|
157
|
+
params: { scope: 'translation' }
|
|
158
|
+
});
|
|
159
|
+
return response.translation || {};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.warn('获取支持语言列表失败:', error.message);
|
|
162
|
+
return {};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = MicrosoftTranslatorService;
|