LanguageApp
は Google Apps Script に用意されている小さなクラスです。簡単な翻訳くらいしかできることはありませんが、どんな使い道があるものかな、と思ったのでまとめてみました。Google Cloud Translation API とかなり機能が似ている……というか無料版の制限があるのを除けば LanguageApp
の上位互換なので、なんとか機能面で LanguageApp
の存在意義を見つけたいところ。
基本的な機能
LanguageApp
に用意されている唯一のメソッドが translate(text, sourceLanguage, targetLanguage[, advancedArgs])
です。言語名は Google Translate で指定されている略語 の文字列で指定します。翻訳元言語に空文字列を指定すると、自動判別が効きます。自動判別の際に推測された言語名も取得できるといいんですけど、どうやらそれは無理そう。
const txt = LanguageApp.translate("ねこです。", "ja", "en");
Logger.log(txt); // >>> It is a cat.
const txt = LanguageApp.translate("ねこです。", "", "en");
Logger.log(txt); // >>> It is a cat.
advanceArgs
とは大仰ですが、入力データがプレーンテキスト (txt) か HTML かを選択できるだけです(デフォルトでは txt)。HTML 指定をするとタグを省いたテキスト部分だけ翻訳され、タグは可能な範囲で対応する箇所に残ります。html のままで処理できるのは便利ですね。
const html = "これは<strong>ねこ</strong>です。";
const sl = "";
const tl = "en";
const translatedHtml = LanguageApp.translate(html, sl, tl, {contentType: 'html'});
Logger.log(translatedHtml); // >>> This is a <strong>cat</strong>
なお HTML の translate
属性に対応しているので、translate="no"
と指定された部分は翻訳対象から除外されます。
複数入力
配列を入力した場合は、カンマで結合してひとつの文字列にしたうえで、翻訳にかけられるようです。
const input1 = ["I am a cat", "and a dog."];
const output1 = LanguageApp.translate(input1, "", "ja"); // 私は猫であり、犬です。
const input2 = "I am a cat, and a dog.";
const output2 = LanguageApp.translate(input2, "", "ja"); // 私は猫であり、犬です。
const input3 = "I am a cat and a dog.";
const output3 = LanguageApp.translate(input3, "", "ja"); // 私は猫と犬です。
配列として出力したい場合は map
したりループを回したりしましょう。ただ LanguageApp
を何度も呼びたくない場合は、改行を入れて結合したり、何らかの置換を噛ませたりすることも考えられます(改行をはさむと翻訳上は別単位と扱われるため)。
const inputList = ["cat","dog","squirrel"];
const outputList = inputList.map(x => LanguageApp.translate(x,"","ja"));
// OR:
const outputList = LanguageApp.translate(inputList.join("\n"),"","ja").split("\n");
// outputList = [ネコ, 犬, リス]
参考
応用的な使い道
以上の機能しかないので、これをメインに据えた使い道というのは限られます。
言語処理の API にする
URL クエリで文字列を渡して、ContentService.textOutput
でテキストとして返すシンプルな API を作ってみます1。
const doGet = (e) => {
const q = e.parameter.q; // 翻訳したい文字列は q で渡す
const sl = e.parameter.sl || ""; // 翻訳元言語は sl で指定、デフォルトで自動判別
const tl = e.parameter.tl || "ja"; // 翻訳先言語は tl で指定、デフォルトで日本語
const r = LanguageApp.translate(q, sl, tl);
return ContentService.createTextOutput(r);
}
// .../exec?q='This is a cat.' とすると "これは猫です。" が返ってくる
これだけでは何の役にも立たないので、選択範囲を翻訳してアラート表示するブックマークレットを作ってみましょう。上のコードを Web アプリケーションとして導入し2、以下をブックマークに登録すればOKです。
javascript:void((()=>{
const txt = window.getSelection().toString();
const url = 'https://script.google.com/macros/s/___scriptId___/exec?q='+txt;
fetch(url)
.then((response)=>response.text())
.then((text)=>alert(text))
.catch((error)=>alert(error))
})())
参考
- Bookmarkletを作ろう(準備編)| Qiita
- 今から10分ではじめる Google Apps Script(GAS) で Web API公開 | Qiita
- GASのExecution APIを使ってGASを外部からぶっ叩く | Qiita
テキストエディタの翻訳プラグインとして使う
やはり Google Docs との連携は楽です。Docs のアプリからは文書全体の翻訳しかできませんが、GAS を書くことで部分翻訳を含めた様々なことができるようになります。たとえば、カスタムメニューに選択範囲を翻訳する関数をつけてみましょう。以下のスクリプトを Docs ファイル紐付きの GAS として書き、onOpen
を開始時トリガーに設定しましょう。
Google Docs はドキュメントを様々な独自オブジェクトのカタマリとして扱っているので、それとは無関係に設定される「ユーザーの選択範囲」を扱うのは直観よりはるかに面倒くさいですが、そこだけ別口で関数をつくってしまえば簡単です。
const doc = DocumentApp.getActiveDocument();
const onOpen = () => {
const ui = DocumentApp.getUi();
ui.createMenu('カスタムメニュー')
.addItem('選択部分を日本語に翻訳する(アラート)', 'translateToJaByAlert')
.addItem('選択部分を日本語に翻訳する(挿入)', 'translateToJaByInsertion')
.addToUi();
}
const translateToJaByAlert = () => {
const range = doc.getSelection();
const originalText = getText(range);
if(originalText){
const translatedText = LanguageApp.translate(originalText, "", "Ja");
DocumentApp.getUi().alert(`BEFORE「${originalText}」\n AFTER「${translatedText}」`);
}else{
DocumentApp.getUi().alert("選択範囲がありません。");
}
}
const translateToJaByInsertion = () => {
const range = doc.getSelection();
const originalText = getText(range);
const positionForInsertion = getEndPosOfRange(range);
if(originalText && positionForInsertion){
const translatedText = LanguageApp.translate(originalText, "", "Ja");
positionForInsertion.insertText(translatedText);
}else{
DocumentApp.getUi().alert("選択範囲がありません。");
}
}
const getText = (range) => {
if(!range) return false;
const elements = range.getRangeElements();
let text = [];
for(let i=0; i<elements.length; i++){
if(!elements[i].getElement().editAsText()) continue;
if(elements[i].isPartial()){
const element = elements[i].getElement().asText();
const startIndex = elements[i].getStartOffset();
const endIndex = elements[i].getEndOffsetInclusive() + 1;
text.push(element.getText().substring(startIndex, endIndex));
}else{
text.push(elements[i].getElement().asText().getText());
}
}
return text.length!==0 ? text.join("") : ""; // 区切り文字等は好みによる
}
const getEndPosOfRange = (range) => {
if(!range) return false;
const elements = range.getRangeElements();
let lastElement, endOfLastElement;
for(let i=elements.length-1; i>=0; i--){
if(!elements[i].getElement().editAsText()) continue;
endOfLastElement = elements[i].isPartial()
? elements[i].getEndOffsetInclusive() + 1
: elements[i].getElement().asText().getText().length;
lastElement = elements[i];
break;
}
return doc.newPosition(lastElement.getElement().editAsText(), endOfLastElement);
}
選択範囲を range
オブジェクトとして取得するのは簡単ですが、range
オブジェクトから直接 text を取得することはできないので、いったん各種 element
を経由して取得する必要があります。選択範囲が element
の境界と一致しない場合は getStartOffset
と getEndOffsetInclusive
で選択範囲の境界を取得して、それを利用して text を抽出します。
それ以外のテキストエディタでも同様に、翻訳プラグインとして利用して、読書/執筆作業を効率化することができます。既にいくつも作成されています。
- Vimの翻訳プラグイン作りました | ゴリラの技術ブログ
- HmGoogleLangTranslatorCS(秀丸エディタのマクロ)
VBA でも HTTPS 通信できるようですが、System.Net.Http
は Windows10 非対応っぽい? VBA はよくわかりません。
折り返し翻訳やってみる
かつて「エキサイト翻訳]というものが流行りましたが、機械翻訳技術の発達により、再翻訳はいまや笑い以外の幸せを人間にもたらすようになりました。日本語→英語→日本語と多重翻訳を繰り返しても、ちょっとした言葉遣いの違いが蓄積していくだけで、あまり文意を損なうことがなくなりました。これを用いて文章の推敲をやってみましょう。先ほどの続きで、以下のコードを Google Docs に仕込んで試してみます。
const reviseViaRecursiveTranslation = () => {
const range = doc.getSelection();
const originalText = getText(range);
if (originalText){
const mediumText = LanguageApp.translate(originalText, "Ja", "En");
const revisedText = LanguageApp.translate(mediumText, "En", "Ja");
DocumentApp.getUi().alert(`BEFORE「${originalText}」\n AFTER「${revisedText}」`);
}else{
DocumentApp.getUi().alert("選択範囲がありません。");
}
}
宮沢賢治「セロ弾きのゴーシュ」の冒頭の一文をコレにかけてみると、こんな感じ。英語の単純過去形を習慣相に訳せていないのはしょうがないとして、poor と楽長以外はほぼ語彙の間違いもなく、良さげです。単純なフレーズ翻訳ではないので、文の構造からがらりと変わっているのも可能性を感じます。
安部公房「砂の女」はどうでしょう。翻訳システムの学習に使われているデータの都合でしょうか、語彙選択が多少ナウでヤングな感じになっていますが、まあ内容は外してないかな……。
……推敲に使えるかは微妙ですが、見ていると面白いですね。これだけで単独の記事にできそう。
その他
Google Spreadsheet の組み込み関数 googletranslate
はなぜか本記事で扱った LanguageApp
とは異なる(より品質の低い)翻訳を返すようです。
- Google Spreadsheet の googletranslate 関数の代わりに LanguageApp を使うワークシート関数を作ってイケてる翻訳ができるようにする | Qiita
- GASでspreadsheetの組み込み関数を使う方法 | Qiita
近頃はあちこちで翻訳 API が公開されていますので、翻訳結果を並べて比較するのも良いかもしれません。
単に全文自動翻訳にかけるのではなく、何らかの処理を噛ませることで、ウェブアプリの日本語化をより高精度に行なう手法はありそうです(形態素解析を組み合わせたりとか)。ただそうなるともう Cloud Translation API
に用語集噛ませるのが強いので、GAS で頑張ることではない気がします。
まとめ
- やっぱり機能が貧弱すぎる。
- GAS は API を作りやすいので、翻訳をサポートとして使う道はたくさんあるはず。
-
実装では q が未指定の場合の挙動もきちんとする。 ↩