- 微软:启动和呼出浮窗时后台预热 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>
205 lines
6.6 KiB
JavaScript
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'); },
|
|
}),
|
|
/腾讯翻译/,
|
|
);
|
|
});
|