Files
Xtranslate/test/free-engines.test.js
hopechen067andClaude Opus 5.5 e92897a794 perf: 免费引擎提速
- 微软:启动和呼出浮窗时后台预热 token,首次翻译从约 4.7s 降到约 1s;token 改从 cn.bing.com 获取(0.6s vs 2.3s);并发请求共用一次 token 获取
- 新增腾讯交互翻译(TranSmart)免费引擎,约 0.1s,质量一般,设置里可选
- 停顿预览默认从 0.5s 改为 0.3s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 03:12:43 -04:00

205 lines
6.6 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const { translate } = require('../src/main/engines');
const {
translateMicrosoft,
parseMicrosoft,
parseBingAuth,
translateUrlFor,
resetTokenCache,
} = require('../src/main/engines/microsoft');
const { parseGoogle } = require('../src/main/engines/google');
const PAGE_HTML = [
'var params_AbusePreventionHelper = [123456,"tok-abc",3600000];',
'IG:"AABBCCDD"',
'data-iid="translator.5026"',
].join('\n');
function jsonResponse(status, body, extra = {}) {
return {
ok: status >= 200 && status < 300,
status,
url: extra.url || '',
json: async () => body,
text: async () => (typeof body === 'string' ? body : JSON.stringify(body)),
};
}
function pageResponse() {
return jsonResponse(200, PAGE_HTML, { url: 'https://cn.bing.com/translator' });
}
test('空白输入不发请求', async () => {
let called = 0;
const result = await translate({
text: ' \n',
engine: 'free',
config: { free: { provider: 'microsoft' } },
fetchImpl: () => {
called += 1;
throw new Error('should not fetch');
},
});
assert.equal(result.text, '');
assert.equal(called, 0);
});
test('微软翻译:中到英、英到中,页面 token 会缓存,并跟着跳转到区域域名', async () => {
resetTokenCache();
const calls = [];
const fetchImpl = async (url, options) => {
const href = String(url);
calls.push({ href, body: options && options.body });
if (href.includes('/translator') && !href.includes('ttranslatev3')) return pageResponse();
const params = new URLSearchParams(options.body);
const translated = params.get('to') === 'en' ? 'hello' : '你好';
return jsonResponse(200, [{ translations: [{ text: translated }] }]);
};
const zh = await translateMicrosoft({ text: '你好', direction: 'zh2en', fetchImpl });
const en = await translateMicrosoft({ text: 'hello', direction: 'en2zh', fetchImpl });
assert.equal(zh, 'hello');
assert.equal(en, '你好');
assert.equal(calls.filter((call) => call.href.includes('/translator') && !call.href.includes('ttranslate')).length, 1);
assert.match(calls[1].href, /^https:\/\/cn\.bing\.com\/ttranslatev3/);
assert.match(calls[1].href, /IG=AABBCCDD/);
assert.match(calls[1].href, /IID=translator\.5026/);
assert.match(calls[1].body, /fromLang=zh-Hans/);
assert.match(calls[1].body, /to=en/);
assert.match(calls[2].body, /fromLang=en/);
assert.match(calls[2].body, /to=zh-Hans/);
assert.equal(parseMicrosoft([{ translations: [{ text: 'ok' }] }]), 'ok');
assert.equal(parseBingAuth(PAGE_HTML, 1000).token, 'tok-abc');
assert.equal(translateUrlFor('https://cn.bing.com/translator'), 'https://cn.bing.com/ttranslatev3');
});
test('微软翻译 token 失效(statusCode 205)时重取一次', async () => {
resetTokenCache();
let pages = 0;
let posts = 0;
const fetchImpl = async (url, options) => {
const href = String(url);
if (href.includes('/translator') && !href.includes('ttranslatev3')) {
pages += 1;
return pageResponse();
}
posts += 1;
if (posts === 1) return jsonResponse(200, { statusCode: 205 });
assert.equal(options.body.includes('tok-abc'), true);
return jsonResponse(200, [{ translations: [{ text: 'hi' }] }]);
};
const text = await translateMicrosoft({ text: '你好', direction: 'zh2en', fetchImpl });
assert.equal(text, 'hi');
assert.equal(pages, 2);
});
test('Google 翻译解析多段结果,并按方向设置 tl', async () => {
assert.equal(parseGoogle([[['Hello', '你好'], ['!', '']]]), 'Hello!');
const calls = [];
const fetchImpl = async (url) => {
calls.push(String(url));
return jsonResponse(200, [[['Hello', '你好']]]);
};
const result = await translate({
text: '你好',
direction: 'zh2en',
engine: 'free',
config: { free: { provider: 'google' } },
fetchImpl,
});
assert.equal(result.text, 'Hello');
assert.match(calls[0], /tl=en/);
assert.match(calls[0], /sl=zh-CN/);
await translate({
text: 'hello',
direction: 'en2zh',
engine: 'free',
config: { free: { provider: 'google' } },
fetchImpl,
});
assert.match(calls[1], /tl=zh-CN/);
assert.match(calls[1], /sl=en/);
});
test('Google 连不上时给出可切换的提示', async () => {
await assert.rejects(
() => translate({
text: 'hi',
direction: 'en2zh',
engine: 'free',
config: { free: { provider: 'google' } },
fetchImpl: async () => {
throw new Error('getaddrinfo');
},
}),
(err) => {
assert.equal(err.message, '网络连不上 Google,换微软或大模型试试');
return true;
},
);
});
test('微软预热后,并发翻译只拿一次 token', async () => {
resetTokenCache();
const { warmupMicrosoft } = require('../src/main/engines/microsoft');
let pages = 0;
let posts = 0;
const fetchImpl = async (url) => {
if (String(url).includes('/translator')) {
pages += 1;
await new Promise((r) => setTimeout(r, 20));
return pageResponse();
}
posts += 1;
return jsonResponse(200, [{ translations: [{ text: 'hi' }] }]);
};
const warm = warmupMicrosoft(fetchImpl);
const [a, b] = await Promise.all([
translateMicrosoft({ text: '你好', direction: 'zh2en', fetchImpl }),
translateMicrosoft({ text: '嗨', direction: 'zh2en', fetchImpl }),
]);
assert.equal(await warm, true);
assert.equal(a, 'hi');
assert.equal(b, 'hi');
assert.equal(pages, 1);
assert.equal(posts, 2);
resetTokenCache();
});
test('腾讯:请求格式与结果解析', async () => {
const { parseTencent } = require('../src/main/engines/tencent');
let sent;
const r = await translate({
text: 'No worries',
engine: 'free',
config: { free: { provider: 'tencent' } },
fetchImpl: async (url, opts) => {
sent = { url: String(url), body: JSON.parse(opts.body) };
return jsonResponse(200, { auto_translation: ['别担心'] });
},
});
assert.equal(r.text, '别担心');
assert.equal(r.direction, 'en2zh');
assert.equal(sent.url, 'https://transmart.qq.com/api/imt');
assert.equal(sent.body.source.lang, 'en');
assert.equal(sent.body.target.lang, 'zh');
assert.deepEqual(sent.body.source.text_list, ['No worries']);
assert.throws(() => parseTencent({ header: { ret_code: 'error' } }));
});
test('腾讯:网络错误给出腾讯的提示', async () => {
await assert.rejects(
translate({
text: '你好',
engine: 'free',
config: { free: { provider: 'tencent' } },
fetchImpl: async () => { throw new TypeError('fetch failed'); },
}),
/腾讯翻译/,
);
});