Skip to content

Instantly share code, notes, and snippets.

@yitong-ovo
Created October 6, 2020 13:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yitong-ovo/5e08452a5ce0418845704a98e779093e to your computer and use it in GitHub Desktop.
Save yitong-ovo/5e08452a5ce0418845704a98e779093e to your computer and use it in GitHub Desktop.
存档用。
// 使用 https://github.com/matheuss/google-translate-api
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var EXPORTED_SYMBOLS = ["GoogleTranslator"];
const {
Services
} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {
PromiseUtils
} = ChromeUtils.import(
"resource://gre/modules/PromiseUtils.jsm"
);
const {
httpRequest
} = ChromeUtils.import("resource://gre/modules/Http.jsm");
const {
XPCOMUtils
} = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { Async } = ChromeUtils.import("resource://services-common/async.js");
XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser"]);
// The maximum amount of net data allowed per request on Google's API.
// 每个请求在Google API上允许的最大净数据量。
const MAX_REQUEST_DATA = 850; // XXX This is the Bing value
// The maximum number of chunks allowed to be translated in a single
// request.
// 单个请求中允许翻译的最大块数。
const MAX_REQUEST_CHUNKS = 1; // Undocumented, but the de facto upper limit. //未记录,但实际上是上限。
// Self-imposed limit of 15 requests. This means that a page that would need
// to be broken in more than 15 requests won't be fully translated.
// The maximum amount of data that we will translate for a single page
// is MAX_REQUESTS * MAX_REQUEST_DATA.
// 自我限制的 15 个请求。 这意味着需要中断超过 15 个请求的页面无法完全翻译。
// 我们将为单个页面转换的最大数据量为 MAX_REQUESTS * MAX_REQUEST_DATA 。
let MAX_REQUESTS = Services.prefs.getIntPref("browser.translation.google.api_MAX_REQUESTS", "");
if (!MAX_REQUESTS) {
MAX_REQUESTS = 10;
// return Promise.reject("no API URL");
}
// const URL = "https://translation.googleapis.com/language/translate/v2";
/**
* Translates a webpage using Google's Translation API.
* 使用Google的翻译API翻译网页。
*
* @param translationDocument The TranslationDocument object that represents * 代表要翻译网页的翻译文档对象
* the webpage to be translated
* @param sourceLanguage The source language of the document * 文件的原始语言
* @param targetLanguage The target language for the translation * 翻译的目标语言
*
* @returns {Promise} A promise that will resolve when the translation
* task is finished.
* 在翻译任务完成时将解决的承诺
*/
var GoogleTranslator = function (
translationDocument,
sourceLanguage,
targetLanguage
) {
this.translationDocument = translationDocument;
this.sourceLanguage = sourceLanguage;
this.targetLanguage = targetLanguage;
this._pendingRequests = 0;
this._partialSuccess = false;
this._translatedCharacterCount = 0;
};
GoogleTranslator.prototype = {
/**
* Performs the translation, splitting the document into several chunks
* respecting the data limits of the API.
*
* 执行翻译,将文档分为几个部分,遵守API的数据限制。
*
* @returns {Promise} A promise that will resolve when the translation
* task is finished.
* 在翻译任务完成时将解决的承诺。
*/
async translate() {
let currentIndex = 0;
this._onFinishedDeferred = PromiseUtils.defer();
// Let's split the document into various requests to be sent to
// Google's Translation API.
// 让我们将文档分为各种请求,然后发送给Google的Translation API。
for (let requestCount = 0; requestCount < MAX_REQUESTS; requestCount++) {
// Generating the text for each request can be expensive, so
// let's take the opportunity of the chunkification process to
// allow for the event loop to attend other pending events
// before we continue.
//
// 为每个请求生成文本可能会很昂贵,
// 因此,让我们利用分块过程的机会,
// 允许事件循环在继续之前参加其他未决事件。
await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
// Determine the data for the next request.
// 确定下一个请求的数据。
let request = this._generateNextTranslationRequest(currentIndex);
// Create a real request to the server, and put it on the
// pending requests list.
// 创建对服务器的真实请求,并将其放在待处理的请求列表中。
let googleRequest = new GoogleRequest(
request.data,
this.sourceLanguage,
this.targetLanguage
);
// console.log(JSON.stringify(request.data));
// console.log(JSON.stringify(request));
this._pendingRequests++;
googleRequest
.fireRequest()
.then(this._chunkCompleted.bind(this), this._chunkFailed.bind(this));
currentIndex = request.lastIndex;
if (request.finished) {
break;
}
}
return this._onFinishedDeferred.promise;
},
/**
* Function called when a request sent to the server completed successfully.
* This function handles calling the function to parse the result and the
* function to resolve the promise returned by the public `translate()`
* method when there's no pending request left.
*
* 发送到服务器的请求成功完成时调用的函数。
* 当没有剩余的待处理请求时,
* 该函数处理调用该函数以解析结果的函数,
* 并解析该函数以解决由公共`translate()`方法返回的 promise。
*
* @param request The GoogleRequest sent to the server.
* Google 请求已发送到服务器。
*/
_chunkCompleted(googleRequest) {
if (this._parseChunkResult(googleRequest)) {
this._partialSuccess = true;
// Count the number of characters successfully translated.
// 计算成功翻译的字符数。
this._translatedCharacterCount += googleRequest.characterCount;
}
this._checkIfFinished();
},
/**
* Function called when a request sent to the server has failed.
* This function handles deciding if the error is transient or means the
* service is unavailable (zero balance on the key or request credentials are
* not in an active state) and calling the function to resolve the promise
* returned by the public `translate()` method when there's no pending.
* request left.
*
* 发送到服务器的请求失败时调用的函数。
* 该函数处理确定错误是暂时的还是意味着服务不可用(密钥上的零余额或请求凭证未处于活动状态)
* ,并在以下情况下调用该函数来解析公共`translate()`方法返回的承诺。 没有等待。 剩余要求。
*
* @param aError [optional] The XHR object of the request that failed.
* [可选] 失败的请求的XHR对象。
*/
_chunkFailed(aError) {
this._checkIfFinished();
},
/**
* Function called when a request sent to the server has completed.
* This function handles resolving the promise
* returned by the public `translate()` method when all chunks are completed.
*
* 发送到服务器的请求完成时调用的函数。
* 当所有块都完成时,此函数将解决由公共 `translate()` 方法返回的 promise。
*
*/
_checkIfFinished() {
// Check if all pending requests have been
// completed and then resolves the promise.
// If at least one chunk was successful, the
// promise will be resolved positively which will
// display the "Success" state for the infobar. Otherwise,
// the "Error" state will appear.
// 检查是否所有未完成的请求都已完成,然后兑现承诺。
// 如果至少一个块成功,则诺言将得到肯定解决,
// 这将显示信息栏的“成功”状态。
// 否则,将出现“错误”状态。
if (--this._pendingRequests == 0) {
if (this._partialSuccess) {
this._onFinishedDeferred.resolve({
characterCount: this._translatedCharacterCount,
});
} else {
this._onFinishedDeferred.reject("failure");
}
}
},
/**
* This function parses the result returned by Bing's Http.svc API,
* which is a XML file that contains a number of elements. To our
* particular interest, the only part of the response that matters
* are the <TranslatedText> nodes, which contains the resulting
* items that were sent to be translated.
*
* 此函数解析Bing的Http.svc API返回的结果,
* 该API是一个包含许多元素的XML文件。
* 为了我们的特别关注,
* 响应的唯一部分事项以 <TranslatedText> 节点,
* 其中包含已发送要翻译的结果条目。
*
* @param request The request sent to the server.
* @returns boolean True if parsing of this chunk was successful.
*/
_parseChunkResult(googleRequest) {
let results;
try {
let response = googleRequest.networkRequest.response;
// console.log(JSON.stringify(response));
// results = JSON.parse(response).data.translations;
results = JSON.parse(response)[0].extract.translation;
} catch (e) {
return false;
}
let len = 1;results.length;
// if (len != googleRequest.translationData.length) {
// // This should never happen, but if the service returns a different number
// // of items (from the number of items submitted), we can't use this chunk
// // because all items would be paired incorrectly.
// // 这永远都不会发生,
// // 但是如果服务返回的项目数(与提交的项目数不同),
// // 我们将无法使用此块,因为所有项目都将错误地配对。
// return false;
// }
let error = false;
for (let i = 0; i < len; i++) {
try {
let result = results //[i].translatedText;
let root = googleRequest.translationData[i][0];
console.log(result)
console.log(JSON.stringify(root));
// let root = googleRequest[0].extract.translation;
if (root.isSimpleRoot && result.includes("&")) {
// If the result contains HTML entities, we need to convert them as
// simple roots expect a plain text result.
// 如果结果包含HTML实体,
// 我们需要将它们转换为简单的 & ,希望得到纯文本结果。
let doc = new DOMParser().parseFromString(result, "text/html");
result = doc.body.firstChild.nodeValue;
}
root.parseResult(result);
} catch (e) {
error = true;
}
}
return !error;
},
/**
* This function will determine what is the data to be used for
* the Nth request we are generating, based on the input params.
* 此函数将根据输入参数确定用于生成的第N个请求的数据是什么。
*
* @param startIndex What is the index, in the roots list, that the
* chunk should start.
* 在根列表中,该块应该开始的索引是什么。
*/
_generateNextTranslationRequest(startIndex) {
let currentDataSize = 0;
let currentChunks = 0;
let output = [];
let rootsList = this.translationDocument.roots;
for (let i = startIndex; i < rootsList.length; i++) {
let root = rootsList[i];
let text = this.translationDocument.generateTextForItem(root);
if (!text) {
continue;
}
let newCurSize = currentDataSize + text.length;
let newChunks = currentChunks + 1;
if (newCurSize > MAX_REQUEST_DATA || newChunks > MAX_REQUEST_CHUNKS) {
// If we've reached the API limits, let's stop accumulating data
// for this request and return. We return information useful for
// the caller to pass back on the next call, so that the function
// can keep working from where it stopped.
// 如果达到了API限制,就停止为该请求累积数据并返回。
// 我们返回对调用者有用的信息,以便在下一次调用时返回,
// 从而使函数可以从停止处继续工作。
return {
data: output,
finished: false,
lastIndex: i,
};
}
currentDataSize = newCurSize;
currentChunks = newChunks;
output.push([root, text]);
}
return {
data: output,
finished: true,
lastIndex: 0,
};
},
};
/**
* Represents a request (for 1 chunk) sent off to Google's service.
* 表示发送到Google服务的请求(1个块)。
*
* @params translationData The data to be used for this translation,
* generated by the generateNextTranslationRequest...
* function.
* translationData 由 generateNextTranslationRequest ...
* 函数生成的用于此翻译的数据。
*
* @param sourceLanguage The source language of the document.
* 文档的源语言。
* @param targetLanguage The target language for the translation.
* 翻译的目标语言。
*
*/
function GoogleRequest(translationData, sourceLanguage, targetLanguage) {
this.translationData = translationData;
this.sourceLanguage = sourceLanguage;
this.targetLanguage = targetLanguage;
this.characterCount = 0;
}
GoogleRequest.prototype = {
/**
* Initiates the request
* 发起请求
*/
fireRequest() {
/**
* 因为需要使用免费 api 。注释掉关于获取 Key 的部分
let key =
Services.cpmm.sharedData.get("translationKey") ||
Services.prefs.getStringPref("browser.translation.google.apiKey", "");
if (!key) {
return Promise.reject("no API key");
}
*/
// 抄一点关于从 config 获取内容的部分
// 从 about:config 内获取 api url,不硬编码 api 地址。
// const URL = 'http://localhost:5000/'
let URL = Services.prefs.getStringPref("browser.translation.google.apiURL", "");
if (!URL) {
return Promise.reject("no API URL");
}
// Prepare request headers.
let headers = [
["Content-type", "application/json;charset=UTF-8"]
];
// Prepare the request body.
// 准备请求正文。
// let postData = [
// // ["key", key],
// ["sourceLang", this.sourceLanguage],
// ["targetLang", [this.targetLanguage]]
// ];
let postData = {
"sourceLang": this.sourceLanguage,
"targetLangs": [this.targetLanguage]
}
for (let [, text] of this.translationData) {
postData["query"] = text;
this.characterCount += text.length;
// console.log(JSON.stringify(postData));
}
let jsonData = JSON.stringify(postData)
// Set up request options.
// 设置请求选项。
return new Promise((resolve, reject) => {
let options = {
onLoad: (responseText, xhr) => {
resolve(this);
},
onError(e, responseText, xhr) {
reject(xhr);
},
postData: jsonData
, headers
};
// console.log(JSON.stringify(postData));
// Fire the request.
// 触发请求。
this.networkRequest = httpRequest(URL, options);
});
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment