Created
October 6, 2020 13:01
-
-
Save yitong-ovo/5e08452a5ce0418845704a98e779093e to your computer and use it in GitHub Desktop.
存档用。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 使用 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