Separate translation module

This commit is contained in:
sienori 2018-06-02 21:29:38 +09:00
parent efa227fb1f
commit 2c5b8019e0
7 changed files with 185 additions and 181 deletions

View file

@ -50,7 +50,7 @@
{ {
"matches": ["http://*/*", "https://*/*", "<all_urls>"], "matches": ["http://*/*", "https://*/*", "<all_urls>"],
"css": ["simple-translate.css"], "css": ["simple-translate.css"],
"js": ["Settings.js", "simple-translate.js"] "js": ["Settings.js", "Translate.js", "simple-translate.js"]
} }
] ]
} }

View file

@ -97,6 +97,10 @@ hr {
#target .candidate { #target .candidate {
color: var(--sub-text); color: var(--sub-text);
margin-top:1em;
}
#target .candidate:empty {
margin-top:0;
} }
#footer { #footer {

View file

@ -30,6 +30,7 @@
</div> </div>
<script src="../Settings.js"></script> <script src="../Settings.js"></script>
<script src="../translate.js"></script>
<script src="popup.js"></script> <script src="popup.js"></script>
</body> </body>

View file

@ -2,14 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let S = new settingsObj(); const S = new settingsObj();
const T = new Translate();
//設定を読み出し //設定を読み出し
S.init().then(function (value) { S.init().then(function (value) {
defaultTargetLang = value.targetLang; defaultTargetLang = value.targetLang;
targetLang = value.targetLang;
secondTargetLang = value.secondTargetLang; secondTargetLang = value.secondTargetLang;
ifChangeSecondLang = value.ifChangeSecondLang; ifChangeSecondLang = value.ifChangeSecondLang;
langList.value = targetLang; //リスト初期値をセット langList.value = value.targetLang; //リスト初期値をセット
langList.addEventListener("change", changeLang); langList.addEventListener("change", changeLang);
document.body.style.fontSize = value.fontSize; document.body.style.fontSize = value.fontSize;
@ -19,11 +20,9 @@ let target = document.getElementById("target");
let langList = document.getElementById("langList"); let langList = document.getElementById("langList");
let textarea = document.getElementById("textarea"); let textarea = document.getElementById("textarea");
//langList= browser.i18n.getMessage("langList");
const initialText = browser.i18n.getMessage("initialTextArea"); const initialText = browser.i18n.getMessage("initialTextArea");
textarea.placeholder = initialText; textarea.placeholder = initialText;
let targetLang;
let secondTargetLang; let secondTargetLang;
let defaultTargetLang; let defaultTargetLang;
let ifChangeSecondLang; let ifChangeSecondLang;
@ -57,12 +56,13 @@ function alphabeticallySort(a, b) {
} }
//翻訳先言語変更時に更新 //翻訳先言語変更時に更新
function changeLang() { async function changeLang() {
targetLang = langList.value;
if (sourceWord !== "") {
translate();
}
if (typeof (url) != "undefined") showLink(); if (typeof (url) != "undefined") showLink();
if (sourceWord !== "") {
const resultData = await T.translate(sourceWord, undefined, langList.value);
showResult(resultData.resultText, resultData.candidateText);
}
} }
//アクティブなタブを取得して渡す //アクティブなタブを取得して渡す
@ -91,15 +91,15 @@ function getSelectionWord(tabs) {
//ページ翻訳へのリンクを表示 //ページ翻訳へのリンクを表示
function showLink() { function showLink() {
document.getElementById("link").innerHTML = "<a href=https://translate.google.com/translate?hl=" + targetLang + "&sl=auto&u=" + encodeURIComponent(url) + ">" + browser.i18n.getMessage('showLink') + "</a>"; document.getElementById("link").innerHTML = "<a href=https://translate.google.com/translate?hl=" + langList.value + "&sl=auto&u=" + encodeURIComponent(url) + ">" + browser.i18n.getMessage('showLink') + "</a>";
} }
//翻訳元テキストを表示 //翻訳元テキストを表示
function refleshSource() { function refleshSource() {
if (sourceWord !== "") { if (sourceWord !== "") {
textarea.innerHTML = sourceWord; textarea.innerHTML = sourceWord;
translate();
resize(); resize();
inputText();
} }
} }
@ -130,89 +130,49 @@ function textAreaClick() {
} }
//文字入力時の処理 //文字入力時の処理
function inputText() { async function inputText() {
sourceWord = textarea.value; sourceWord = textarea.value;
translate();
const resultData = await T.translate(sourceWord, 'auto', langList.value);
changeSecondLang(defaultTargetLang, resultData.sourceLanguage, resultData.percentage);
showResult(resultData.resultText, resultData.candidateText);
} }
//改行で分割してgetRequestに渡す function showResult(resultText, candidateText) {
function translate() {
let promises = [];
sourceLine = sourceWord.split("\n");
for (i = 0; i < sourceLine.length; i++) {
promises.push(getRequest(sourceLine[i]));
}
Promise.all(promises)
.then(function (results) {
showResult(results); //翻訳結果が帰ってきたらshowResult
});
}
//翻訳リクエストを送信,取得して返す
function getRequest(word) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.responseType = 'json';
//let url = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=" + targetLang + "&dt=t&q=" + encodeURIComponent(word);
let url = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=" + targetLang + "&dt=t&dt=bd&q=" + encodeURIComponent(word);
xhr.open("GET", url);
xhr.send();
xhr.onload = function () {
resolve(xhr);
};
})
}
//翻訳結果を表示
function showResult(results) {
const resultArea = target.getElementsByClassName("result")[0]; const resultArea = target.getElementsByClassName("result")[0];
const candidateArea = target.getElementsByClassName("candidate")[0]; const candidateArea = target.getElementsByClassName("candidate")[0];
resultArea.innerText = "";
candidateArea.innerText = "";
let resultText = "";
let candidateText = "";
let wordsCount = 0;
let lineCount = 0;
//第二言語に変更
if (ifChangeSecondLang) {
let lang = results[0].response[2];
let percentage = results[0].response[6];
if (targetLang == defaultTargetLang && lang == defaultTargetLang && percentage > 0 && !changeLangFlag) changeSecondLang();
else if ((lang != defaultTargetLang || percentage == 0) && changeLangFlag) unchangeSecondLang();
}
for (let j = 0; j < results.length; j++) {
lineCount++;
for (let i = 0; i < results[j].response[0].length; i++) {
resultText += results[j].response[0][i][0];
}
resultText += "\n";
if (results[j].response[1]) {
wordsCount++;
for (let i = 0; i < results[j].response[1].length; i++) {
const partsOfSpeech = results[j].response[1][i][0];
const candidates = results[j].response[1][i][1];
candidateText += `\n${partsOfSpeech}${partsOfSpeech!="" ? ": " : ""}${candidates.join(", ")}`;
}
}
}
resultArea.innerText = resultText; resultArea.innerText = resultText;
if (S.get().ifShowCandidate && wordsCount == 1 && lineCount == 1) candidateArea.innerText = candidateText; if (S.get().ifShowCandidate) candidateArea.innerText = candidateText;
} }
let changeLangFlag = false; let changeLangFlag = false;
function changeSecondLang() { function changeSecondLang(defaultTargetLang, sourceLang, percentage) {
//検出された翻訳元言語がターゲット言語と一致
const equalsSourceAndTarget = sourceLang == langList.value && percentage > 0;
//検出された翻訳元言語がデフォルト言語と一致
const equalsSourceAndDefault = sourceLang == defaultTargetLang && percentage > 0;
if (!changeLangFlag) {
//通常時
if (equalsSourceAndTarget && equalsSourceAndDefault) {
//ソースとターゲットとデフォルトが一致する場合
//ターゲットを第2言語に変更
changeLangFlag = true; changeLangFlag = true;
langList.value = secondTargetLang; langList.value = secondTargetLang;
changeLang(); changeLang();
} }
} else {
function unchangeSecondLang() { //第2言語に切替した後
if (!equalsSourceAndDefault) {
//ソースとデフォルトが異なる場合
//ターゲットをデフォルトに戻す
changeLangFlag = false; changeLangFlag = false;
langList.value = defaultTargetLang; langList.value = defaultTargetLang;
changeLang(); changeLang();
}
}
} }

View file

@ -65,6 +65,11 @@
#simple-translate-panel .candidate { #simple-translate-panel .candidate {
color: var(--simple-translate-sub-text); color: var(--simple-translate-sub-text);
margin-top:1em;
}
#simple-translate-panel .candidate:empty {
margin-top:0;
} }
@keyframes simple-translate-showButton { @keyframes simple-translate-showButton {

View file

@ -8,57 +8,54 @@ var panel = document.getElementById("simple-translate-panel");
var selectionWord; var selectionWord;
var clickPosition; var clickPosition;
let S = new settingsObj(); const S = new settingsObj();
const T = new Translate();
S.init(); S.init();
window.addEventListener("mouseup", Select, false); window.addEventListener("mouseup", Select, false);
//テキスト選択時の処理 ダブルクリックした時2回処理が走るのを何とかしたい //テキスト選択時の処理 ダブルクリックした時2回処理が走るのを何とかしたい
function Select(e) { async function Select(e) {
hidePanel(e); hidePanel(e);
if (e.target.tagName == "INPUT" && e.target.type == "password") return; if (e.target.tagName == "INPUT" && e.target.type == "password") return;
setTimeout(function () { //誤動作防止の為ディレイを設ける setTimeout(async () => { //誤動作防止の為ディレイを設ける
if (e.target.tagName == "INPUT" || e.target.tagName == "TEXTAREA") { //選択文の取得 テキストフィールド内を選択した場合にも対応
selectionWord = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd); const isTextField = (e.target.tagName == "INPUT") || (e.target.tagName == "TEXTAREA");
} else { if (isTextField) selectionWord = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
selectionWord = String(window.getSelection()); else selectionWord = String(window.getSelection());
}
//選択文が存在し,パネル外を左クリックした場合は翻訳する
const existsSelectionWord = (selectionWord.length !== 0);
const isLeftClick = (e.button == 0);
const isPanelOutside = (e.target.id !== 'simple-translate-panel') && (e.target.parentElement.id !== 'simple-translate-panel');
const shouldTranslate = existsSelectionWord && isLeftClick && isPanelOutside;
if (!shouldTranslate) return;
//選択した言語が翻訳先言語と異なれば翻訳する
const needTranslate = await checkLang(selectionWord, 'auto', S.get().targetLang);
if (!needTranslate) return;
if ((selectionWord.length !== 0) && (e.button == 0) && (e.target.id !== "simple-translate-panel") && (e.target.parentElement.id !== "simple-translate-panel")) { //選択範囲が存在かつ左クリックかつパネル以外のとき
clickPosition = e; clickPosition = e;
checkLang().then(function (results) {
if (results) {
switch (S.get().whenSelectText) { switch (S.get().whenSelectText) {
case 'showButton': case 'showButton':
popupButton(e); popupButton(e);
break; break;
case 'showPanel': case 'showPanel':
translate(); translate(selectionWord, 'auto', S.get().targetLang);
showPanel(e); showPanel(e);
break; break;
case 'dontShowButton': case 'dontShowButton':
break; break;
} }
}
});
}
}, 200); }, 200);
} }
//選択テキストの言語をチェックして返す //選択テキストの言語をチェックして返す
function checkLang() { async function checkLang(sourceWord, sourceLang, targetLang) {
return new Promise(function (resolve, reject) { if (!S.get().ifCheckLang) return true; //設定がオフならtrue
if (S.get().ifCheckLang) { //設定がオンなら
getRequest(selectionWord.substr(0, 100)) //先頭100文字を抽出して言語を取得 const resultData = await T.translate(sourceWord, sourceLang, targetLang);
.then(function (results) { const needTranslate = (S.get().targetLang != resultData.sourceLanguage) && (resultData.percentage > 0);
let lang = results.response[2]; return needTranslate; //ターゲットとソースの言語が不一致ならtrue
let percentage = results.response[6];
resolve(lang != S.get().targetLang && percentage > 0); //真偽値を返す
});
} else { //設定がオフならtrueを返す
resolve(true);
}
})
} }
//ボタンを表示 //ボタンを表示
@ -89,66 +86,23 @@ function popupButton(e) {
button.style.display = 'block'; button.style.display = 'block';
} }
button.addEventListener("click", function (e) { button.addEventListener("click", function (e) {
translate(); translate(selectionWord, 'auto', S.get().targetLang);
showPanel(e); showPanel(e);
}, false); }, false);
//改行で分割してgetRequestに渡す async function translate(sourceWord, sourceLang, targetLang) {
function translate() { const resultData = await T.translate(sourceWord, sourceLang, targetLang);
promises = []; showResult(resultData.resultText, resultData.candidateText);
sourceLine = selectionWord.split("\n");
for (i = 0; i < sourceLine.length; i++) {
promises.push(getRequest(sourceLine[i]));
}
Promise.all(promises)
.then(function (results) {
showResult(results); //翻訳結果が帰ってきたらshowResult
});
}
//翻訳リクエストを送信,取得して返す
function getRequest(word) {
return new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.responseType = 'json';
let url = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=" + S.get().targetLang + "&dt=t&dt=bd&q=" + encodeURIComponent(word);
xhr.open("GET", url);
xhr.send();
xhr.onload = function () {
resolve(xhr);
};
})
}
//翻訳結果を表示
function showResult(results) {
panel.innerText = "";
let resultText = "";
let candidateText = "";
let wordsCount = 0;
let lineCount = 0;
for (let j = 0; j < results.length; j++) {
lineCount++;
for (let i = 0; i < results[j].response[0].length; i++) {
resultText += results[j].response[0][i][0];
}
resultText += "\n";
if (results[j].response[1]) {
wordsCount++;
for (let i = 0; i < results[j].response[1].length; i++) {
const partsOfSpeech = results[j].response[1][i][0];
const candidates = results[j].response[1][i][1];
candidateText += `\n${partsOfSpeech}${partsOfSpeech!="" ? ": " : ""}${candidates.join(", ")}`;
}
}
}
panel.innerHTML = "<p class=result></p><p class=candidate>"
panel.getElementsByClassName("result")[0].innerText = resultText;
if (S.get().ifShowCandidate && wordsCount == 1 && lineCount == 1) panel.getElementsByClassName("candidate")[0].innerText = candidateText;
panelPosition(clickPosition); panelPosition(clickPosition);
}
function showResult(resultText, candidateText) {
panel.innerHTML = '<p class=result></p><p class=candidate>';
const resultArea = panel.getElementsByClassName("result")[0];
const candidateArea = panel.getElementsByClassName("candidate")[0];
resultArea.innerText = resultText;
if (S.get().ifShowCandidate) candidateArea.innerText = candidateText;
} }
//パネル表示 //パネル表示
@ -219,6 +173,6 @@ function sendToPopup() {
//コンテキストメニュークリックでパネルを表示 //コンテキストメニュークリックでパネルを表示
function showPanelFromMenu() { function showPanelFromMenu() {
button.style.display = "none"; button.style.display = "none";
translate(); translate(selectionWord, 'auto', S.get().targetLang);
showPanel(clickPosition); showPanel(clickPosition);
} }

View file

@ -0,0 +1,80 @@
class Translate {
constructor() {
}
set sourceWord(word) {
this.sourceWord = word;
}
translate(sourceWord, sourceLang = 'auto', targetLang) {
//改行で分割
const sourceLines = sourceWord.split("\n");
let promises = [];
for (let sourceLine of sourceLines) {
promises.push(this.sendRequest(sourceLine, sourceLang, targetLang));
}
return new Promise(resolve => {
Promise.all(promises)
.then((results) => {
resolve(this.formatResult(results));
});
});
}
sendRequest(word, sourceLang, targetLang) {
const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&dt=bd&q=${encodeURIComponent(word)}`;
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send();
return new Promise((resolve, reject) => {
xhr.onload = () => {
resolve(xhr);
};
})
}
formatResult(results) {
const resultData = {
resultText: '',
candidateText: '',
sourceLanguage: '',
percentage: 0
}
//翻訳元言語を取得
resultData.sourceLanguage = results[0].response[2];
resultData.percentage = results[0].response[6];
let candidateText = '';
let wordCount = 0;
let lineCount = 0;
for (const result of results) {
lineCount++;
//翻訳文を取得
for (const response of result.response[0]) {
resultData.resultText += response[0];
}
//訳候補を取得
if (result.response[1]) {
wordCount++;
for (let i = 0; i < result.response[1].length; i++) {
const partsOfSpeech = result.response[1][i][0];
const candidates = result.response[1][i][1];
candidateText += `${partsOfSpeech}${partsOfSpeech != '' ? ': ' : ''}${candidates.join(', ')}\n`;
}
}
}
//訳候補が一つの単語のみに対して存在するとき返す
if (wordCount == 1 && lineCount == 1) resultData.candidateText = candidateText;
return resultData;
}
}