From 2c5b8019e0e5096a7504fcab4ed8afc8078ee3ac Mon Sep 17 00:00:00 2001 From: sienori Date: Sat, 2 Jun 2018 21:29:38 +0900 Subject: [PATCH] Separate translation module --- simple-translate/manifest.json | 2 +- simple-translate/popup/popup.css | 4 + simple-translate/popup/popup.html | 1 + simple-translate/popup/popup.js | 130 ++++++++--------------- simple-translate/simple-translate.css | 5 + simple-translate/simple-translate.js | 144 +++++++++----------------- simple-translate/translate.js | 80 ++++++++++++++ 7 files changed, 185 insertions(+), 181 deletions(-) create mode 100644 simple-translate/translate.js diff --git a/simple-translate/manifest.json b/simple-translate/manifest.json index 3326e66..7c0317c 100644 --- a/simple-translate/manifest.json +++ b/simple-translate/manifest.json @@ -50,7 +50,7 @@ { "matches": ["http://*/*", "https://*/*", ""], "css": ["simple-translate.css"], - "js": ["Settings.js", "simple-translate.js"] + "js": ["Settings.js", "Translate.js", "simple-translate.js"] } ] } diff --git a/simple-translate/popup/popup.css b/simple-translate/popup/popup.css index ef649c3..6f86c73 100644 --- a/simple-translate/popup/popup.css +++ b/simple-translate/popup/popup.css @@ -97,6 +97,10 @@ hr { #target .candidate { color: var(--sub-text); + margin-top:1em; +} +#target .candidate:empty { + margin-top:0; } #footer { diff --git a/simple-translate/popup/popup.html b/simple-translate/popup/popup.html index 144fcfd..8978ad2 100644 --- a/simple-translate/popup/popup.html +++ b/simple-translate/popup/popup.html @@ -30,6 +30,7 @@ + diff --git a/simple-translate/popup/popup.js b/simple-translate/popup/popup.js index 7d41d58..c1be2b6 100644 --- a/simple-translate/popup/popup.js +++ b/simple-translate/popup/popup.js @@ -2,14 +2,15 @@ * 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/. */ -let S = new settingsObj(); +const S = new settingsObj(); +const T = new Translate(); + //設定を読み出し S.init().then(function (value) { defaultTargetLang = value.targetLang; - targetLang = value.targetLang; secondTargetLang = value.secondTargetLang; ifChangeSecondLang = value.ifChangeSecondLang; - langList.value = targetLang; //リスト初期値をセット + langList.value = value.targetLang; //リスト初期値をセット langList.addEventListener("change", changeLang); document.body.style.fontSize = value.fontSize; @@ -19,11 +20,9 @@ let target = document.getElementById("target"); let langList = document.getElementById("langList"); let textarea = document.getElementById("textarea"); -//langList= browser.i18n.getMessage("langList"); const initialText = browser.i18n.getMessage("initialTextArea"); textarea.placeholder = initialText; -let targetLang; let secondTargetLang; let defaultTargetLang; let ifChangeSecondLang; @@ -57,12 +56,13 @@ function alphabeticallySort(a, b) { } //翻訳先言語変更時に更新 -function changeLang() { - targetLang = langList.value; - if (sourceWord !== "") { - translate(); - } +async function changeLang() { 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() { - document.getElementById("link").innerHTML = "" + browser.i18n.getMessage('showLink') + ""; + document.getElementById("link").innerHTML = "" + browser.i18n.getMessage('showLink') + ""; } //翻訳元テキストを表示 function refleshSource() { if (sourceWord !== "") { textarea.innerHTML = sourceWord; - translate(); resize(); + inputText(); } } @@ -130,89 +130,49 @@ function textAreaClick() { } //文字入力時の処理 -function inputText() { +async function inputText() { 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 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) { +function showResult(resultText, candidateText) { const resultArea = target.getElementsByClassName("result")[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; - if (S.get().ifShowCandidate && wordsCount == 1 && lineCount == 1) candidateArea.innerText = candidateText; + if (S.get().ifShowCandidate) candidateArea.innerText = candidateText; } let changeLangFlag = false; -function changeSecondLang() { - changeLangFlag = true; - langList.value = secondTargetLang; - changeLang(); -} +function changeSecondLang(defaultTargetLang, sourceLang, percentage) { + //検出された翻訳元言語がターゲット言語と一致 + const equalsSourceAndTarget = sourceLang == langList.value && percentage > 0; -function unchangeSecondLang() { - changeLangFlag = false; - langList.value = defaultTargetLang; - changeLang(); + //検出された翻訳元言語がデフォルト言語と一致 + const equalsSourceAndDefault = sourceLang == defaultTargetLang && percentage > 0; + + if (!changeLangFlag) { + //通常時 + if (equalsSourceAndTarget && equalsSourceAndDefault) { + //ソースとターゲットとデフォルトが一致する場合 + //ターゲットを第2言語に変更 + changeLangFlag = true; + langList.value = secondTargetLang; + changeLang(); + } + } else { + //第2言語に切替した後 + if (!equalsSourceAndDefault) { + //ソースとデフォルトが異なる場合 + //ターゲットをデフォルトに戻す + changeLangFlag = false; + langList.value = defaultTargetLang; + changeLang(); + + } + } } diff --git a/simple-translate/simple-translate.css b/simple-translate/simple-translate.css index d1f1603..e1ab841 100644 --- a/simple-translate/simple-translate.css +++ b/simple-translate/simple-translate.css @@ -65,6 +65,11 @@ #simple-translate-panel .candidate { color: var(--simple-translate-sub-text); + margin-top:1em; +} + +#simple-translate-panel .candidate:empty { + margin-top:0; } @keyframes simple-translate-showButton { diff --git a/simple-translate/simple-translate.js b/simple-translate/simple-translate.js index 5f12aa5..f9462c3 100644 --- a/simple-translate/simple-translate.js +++ b/simple-translate/simple-translate.js @@ -8,57 +8,54 @@ var panel = document.getElementById("simple-translate-panel"); var selectionWord; var clickPosition; -let S = new settingsObj(); +const S = new settingsObj(); +const T = new Translate(); S.init(); window.addEventListener("mouseup", Select, false); //テキスト選択時の処理 ダブルクリックした時2回処理が走るのを何とかしたい -function Select(e) { +async function Select(e) { hidePanel(e); if (e.target.tagName == "INPUT" && e.target.type == "password") return; - setTimeout(function () { //誤動作防止の為ディレイを設ける - if (e.target.tagName == "INPUT" || e.target.tagName == "TEXTAREA") { - selectionWord = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd); - } else { - selectionWord = String(window.getSelection()); - } + setTimeout(async () => { //誤動作防止の為ディレイを設ける + //選択文の取得 テキストフィールド内を選択した場合にも対応 + const isTextField = (e.target.tagName == "INPUT") || (e.target.tagName == "TEXTAREA"); + if (isTextField) selectionWord = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd); + else selectionWord = String(window.getSelection()); - if ((selectionWord.length !== 0) && (e.button == 0) && (e.target.id !== "simple-translate-panel") && (e.target.parentElement.id !== "simple-translate-panel")) { //選択範囲が存在かつ左クリックかつパネル以外のとき - clickPosition = e; + //選択文が存在し,パネル外を左クリックした場合は翻訳する + 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; - checkLang().then(function (results) { - if (results) { - switch (S.get().whenSelectText) { - case 'showButton': - popupButton(e); - break; - case 'showPanel': - translate(); - showPanel(e); - break; - case 'dontShowButton': - break; - } - } - }); + //選択した言語が翻訳先言語と異なれば翻訳する + const needTranslate = await checkLang(selectionWord, 'auto', S.get().targetLang); + if (!needTranslate) return; + + clickPosition = e; + switch (S.get().whenSelectText) { + case 'showButton': + popupButton(e); + break; + case 'showPanel': + translate(selectionWord, 'auto', S.get().targetLang); + showPanel(e); + break; + case 'dontShowButton': + break; } }, 200); } //選択テキストの言語をチェックして返す -function checkLang() { - return new Promise(function (resolve, reject) { - if (S.get().ifCheckLang) { //設定がオンなら - getRequest(selectionWord.substr(0, 100)) //先頭100文字を抽出して言語を取得 - .then(function (results) { - let lang = results.response[2]; - let percentage = results.response[6]; - resolve(lang != S.get().targetLang && percentage > 0); //真偽値を返す - }); - } else { //設定がオフならtrueを返す - resolve(true); - } - }) +async function checkLang(sourceWord, sourceLang, targetLang) { + if (!S.get().ifCheckLang) return true; //設定がオフならtrue + + const resultData = await T.translate(sourceWord, sourceLang, targetLang); + const needTranslate = (S.get().targetLang != resultData.sourceLanguage) && (resultData.percentage > 0); + return needTranslate; //ターゲットとソースの言語が不一致ならtrue } //ボタンを表示 @@ -89,71 +86,28 @@ function popupButton(e) { button.style.display = 'block'; } button.addEventListener("click", function (e) { - translate(); + translate(selectionWord, 'auto', S.get().targetLang); showPanel(e); }, false); -//改行で分割してgetRequestに渡す -function translate() { - promises = []; - 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 = "

" - panel.getElementsByClassName("result")[0].innerText = resultText; - if (S.get().ifShowCandidate && wordsCount == 1 && lineCount == 1) panel.getElementsByClassName("candidate")[0].innerText = candidateText; +async function translate(sourceWord, sourceLang, targetLang) { + const resultData = await T.translate(sourceWord, sourceLang, targetLang); + showResult(resultData.resultText, resultData.candidateText); panelPosition(clickPosition); +} +function showResult(resultText, candidateText) { + panel.innerHTML = '

'; + const resultArea = panel.getElementsByClassName("result")[0]; + const candidateArea = panel.getElementsByClassName("candidate")[0]; + + resultArea.innerText = resultText; + if (S.get().ifShowCandidate) candidateArea.innerText = candidateText; } //パネル表示 function showPanel(e) { - clickPosition = e; + clickPosition=e; panel.style.display = 'block'; panelPosition(e); } @@ -219,6 +173,6 @@ function sendToPopup() { //コンテキストメニュークリックでパネルを表示 function showPanelFromMenu() { button.style.display = "none"; - translate(); + translate(selectionWord, 'auto', S.get().targetLang); showPanel(clickPosition); } diff --git a/simple-translate/translate.js b/simple-translate/translate.js new file mode 100644 index 0000000..4eb734d --- /dev/null +++ b/simple-translate/translate.js @@ -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; + } +} \ No newline at end of file