Gooleスプレッドシートで翻訳:DeepL API Free x GAS(GoogleAppsScript)、翻訳比較
DeepL API においてうまく翻訳できなかった原因が分かったので、翻訳関数のコードや原因について。翻訳比較でもあります。
DeepL APIの googleスプレ x GASの"書き方の違い" についても少し考察。
成果物:スプレッドシートへのリンク
以下は各翻訳結果を比較するように作ったgoogleスプレッドシートです。
「英歌詞を日本語に翻訳 比較 =DeepL-web / DeepL-API / AppsScript / GoogleTranslate=」
"リンクを知ってる人は閲覧可能"、という共有設定。列や行に「グループ化」を使っています。これにより下記の効果あり。
- 閲覧に余計な行や列をあらかじめ非表示に(表示したい時はワンクリックで表示)
- 原文と各翻訳エンジンの違いを隣り合うよう比較しやすくなる
{cap:翻訳比較、DeepL APIで翻訳できなかった原因と回避済み}
DeepL API版では「&」記号がNG、「&」出現時点で翻訳STOP。(これは「&」->「and」に置き換える一工夫を加えて翻訳STOP現象を回避してる)
DeepL web/app版は「&」記号のままで問題ない。
DeepL系とGoogle産系(AppsScript,translate関数)のは文体からして違う
ただ共有化による制限「閲覧のみ」の範囲を、当方があまり把握してないので 自分が思ってるように他の人も操作できるのかどうかイマイチわかりません。
見てる人が「グループ化」の表示/非表示 操作を出来るのかどうか、
同じくプルダウンの操作も見てる人が出来るのかどうか、など
→ Google未ログインで閲覧のみの場合:「グループ化」の表示/非表示もプルダウンも出来ない
→ Googleログイン済みで閲覧のみの場合: =未把握=
前書き、前提など。
書き方の違い、コードの書き方の違いの考察。
翻訳比較 所感、翻訳の特徴の差などについて知りたい場合。
GAS:Apps Scriptコード、コードを確認したい場合。
遭遇したエラーとそこからの試行錯誤、エラー原因と回避について知りたい場合。
最後に:参考記事、リンク集
前書き
■用語確認、確認事項
- DeepL API Free
- Google Apps Script (以降「GAS」表記)
- JavaScript (以降「JS」表記)
- Googleスプレッドシート (以降「googleスプレ」表記)
- DeepLアカウント と DeepL APIアカウント
- DeepLのonline翻訳/デスクトップ翻訳 (以降「web/app」版としDeepL APIと区別)
DeepL自体のアカウント(web上のonline翻訳サービス)と DeepL APIのアカウントは別。DeepL APIのなかでもFree版=無料のDeepL API Free とPro版=有償のDeepL API Proがある
■スキル
- JavaScript分かる
- Googleスプレは使ってる ←もっぱら本来の使い道ではないような使い道
- GAS触るのこの機会が初 ←でもJSわかるので問題なし
■範囲
- 多言語対応でなくともいい
- 主に 日本語→英語、英語→日本語(Ja→En、En→Ja)の用途
- エラーハンドリングとかはそこまで詳細にやらない方向性
- DeepL API のアカウント取り方の説明は略、本旨ではないので
- Apps Script画面の出し方なども略、趣旨外なので
■未到達
-
原文の言語を自動判別するタイプの翻訳関数deepL(txt,"",lan2)
- オプション色々つけるタイプの翻訳関数
- ドキュメント翻訳。ドキュメントのアップロード&翻訳済みドキュメントのダウンロード(*.doc, *pdf, *.xlsx, *.xml, *.html など、テキスト形式以外)
- 用語集の管理(glossary 関数)、作成、検査、削除:
└→本来、1アカウントにつき合計1000個の用語集を登録可能 - 使用量の詳細(文字数とリミットなど)の取得:
└→スプレッドシート上に表示させられれば便利と思ったが手付かず
■想定読者
- ここまで書いてることに何かしら納得がある方。
- DeepL API翻訳でこの記事で書かれてる同じ原因で翻訳がうまくいかない現象に遭ってる方。
- APIのfetchの書き方っていくつか型(バリエーション)があるな、どれがいいんだろ?と思ってる方 etc etc...
※この記事とDeepL APIの仕様 は2024年6月現在のものです
書き方の違い
参考にする記事ごとに、コードの書き方や量が大きく異なる、コード記述方法に違いがあることにいついて。変数名のチガイとかそーゆーことではなくて。
記述に違いがあるのは当たり前ですが、そうではなく、そもそもの成り立ち(≒書式?)からして
『ナンカ違うな…?』
というのを見るにつけ気になったので、その辺について少し。
fetch構文の複雑さ
fetch(url, option)
タイプとfetch(url)
タイプがある。
fetch(url, option)
タイプはheader
情報とか連想配列?オブジェクト?の書き方ぽいのが出てきてコード量が多い…。
それに対してfetch(url)
タイプはスッキリしてる。
これは取得方法(方式?)の違い。具体的にはPOST
かGET
の違い。 googleスプレ x GASならカンタンな方でいいんじゃないかと思う。
この記事の書き方はカンタンシンプルな方のfetch(url)
タイプ。
認証キーを書く場所
あらかじめ変数に入れてるか、
処理を定義する部分に直書きしてるか、
googleスプレのsheetのセルに書き込むタイプか、…など。
// 説明用に 認証キーは仮に「XXXXXX:fx」とします。変数名はテキトーです。
// あらかじめ変数に入れてる例
const APPKEY = "XXXXXX:fx";
// 処理を定義する部分に直書きの例
const res = UrlFetchApp.fetch(url+'?auth_key=XXXXXX:fx&text=English texttexttext&source_lang=ja&target_lang=en');
// googleスプレのsheetのセルに書き込むタイプ
// →GAS上にあるのはセル内容取得用の記述で "XXXXXX:fx" など書く場所無し
en,jaなどの自動判別かどうか
これはどう運用したいかによるのでそこまで気にはならないが…。
コード量は増えるが、その機能が必用な場合はそういった分岐処理構造を持たせたらいいというハナシ。
これは必要とする引数の数にも関係。あと自動判別かどうかに限らずfunctionの書き方で引数の数が1~3つとか。例えば
deepL(arg)
で済むのか deepL(arg,arg2,arg3)
のように2つ以上必要になるのかといった感じ。
翻訳比較 所感
それぞれの違い、特長など。
総合的に見て、例にした英歌詞との親和性でいうとDeepLの方がより自然。こまごまとした不備があるとしても、この"自然な感じ"というのは強力なアドバンテージ。
❶Deep L (web/app)
- 文章量によって訳文の細部が変わる
- 例にした英歌詞と文体が合ってる、自然
- カッコ系に弱い。閉じカッコ欠落しがち
- 唐突に句点あったりしてる(原文が英歌詞なので原文に句点ナシにも関わらず)
- 「~~~~~」を反映していたり、いなかったり (as省略目印,キリトリ線代わり)
- 「======」は反映してる (asタイトル区切り)
❷DeepL API
- 基本的にweb/app版と同じ
- 例にした英歌詞と文体が合ってる、自然
- よく見るとDeepL web/app版と違うヶ所もある
- 空白改行の扱いがweb/app版と少し違うっぽい
- カッコ系に弱い。閉じカッコ欠落しがち
- 唐突に句点あったりしてる(原文が英歌詞なので原文に句点ナシにも関わらず)
- 「~~~~~」を反映していない、あれ?反映してる時もあったような (as省略目印,キリトリ線代わり)
- 「======」は反映してる (asタイトル区切り)
- 「&」記号があるとその場所(の手前)で翻訳STOP
❸Apps ScriptのLanguageApp
❹translateとGoogle Translate関数
- 両者はまったく同じ結果:
DeepLが web/app版とAPI版で微妙に細部が異なるのに対して - 文意文脈に関係なく常にデスマス調
- カッコ系強い。閉じカッコ欠落することない、ぜんぶ全角系のカッコになる模様
- 文意文脈に関係なく常に同じ訳語だから相応しくない場合あり:例にした英歌詞でいうと"power"は「力」じゃなくて「権力」の方なんだろうな、など
英歌詞に関して、歌詞onlyでは(現在位置が)わかりにくので、vers1 vers2、sabi、Cmeloなど、セクションを区分するラベル的なものを追加してる。実際は<vers1>
、vers2
、<1Sabi>
、<Cmelo>
と。
❶❷のDeepL系は、以前から感じてたがカッコ系に弱い。閉じカッコがあったりなかったりする。
❸❹のGoogle産系は、カッコ系に強い。でもvers1を「バージョン1」と訳してる、ぜったいちがう…。
DeepL系Google産系のどちらでもダメだったのがCmeloで「クロメ」となってた◝(⁰▿⁰)◜。これについては ま 納得
DeepL系は、vesr1やvers2は原文そのまんま「<vers1>」「<vers2>」な一方、1Sabiが「<1Sabi>」なのに対し2Sabiが「<2サビ」と閉じカッコがなく、でも3Sabiはまた「<3Sabi>」と原文そのまんまになってるというランダム不安定さというか。とにかくカッコ系に弱い。
❷のDeepL API版、もしかしてオプション設定次第で語尾の調を変えられたりするのかもしれないが、そういったことは「詳細にやらない」このかんたんな例においては無視。詳しくはDeepL API技術資料DeepL API Docs 日本語を。日本語とありながら表紙にあたる1p目以外、中身結局全部英語やったゾ…。
ここで考察
この「翻訳比較 所感」は"この翻訳元サンプルでは"というある意味限定的なものです
翻訳元英文のジャンルやカテゴリや文体によるので、いわゆるどっちが優秀かというのは一概には言えません。
なぜ「〇〇の方が自然」「〇〇の方が優秀」など、巷にあるggったモノを読んでる方としては記事によって全く別意見が書れてあるコトについて、かみ砕いて言うと『あっちとこっちで言ってることチガウな?』ということについて何でかな?と考えると、以下のことが挙げられます:
- 単に開発がススんで精度が上がった:
➡︎2年や3年で変わったとすれば(2024年6月現在)、2年前の2021年の記事の(精度の)情報は現在ではupdateされ古くなってる可能性はあり寄りのあり - 記事書いてる人のサンプルに使う翻訳元英文の文体が偏っている:
➡︎ジャンルやカテゴリや文体の偏り、翻訳したいと思う英文ソースに偏りがあるということ
➡︎かつそれを自覚しないまま/明示的な断りを入れないまま(精度についての優劣や自然さの判断を)一般論のように語った
大体こんな事情ではないかと推察できます。
辛口を承知で言うと、1.については検索して読む側の心がまえ(?というか何と言うか)が足りないと言え、2.についてはライター≒記事書く側としての配慮が足りない、わけです。本職や本分がライターの方ではない人が書いた記事であることが多いんだろうな、というのを考えると仕方がない一面もあるのかと思います。 しかし、翻訳にかけたいのが新聞記事か論文か、口語の多いドラマのセリフかツイッターの短文かなど、英文ソースのジャンルやカテゴリや文体といった属性はおおいに翻訳精度に関係するものです。それを忘れてはいけないと思います 見てる人が自分と同じ属性だと思うなよ 。
GAS:Apps Scriptコード
説明部でもあるJSDoc部など削除した極力コードのみのシンプル版 と JSDocもある結果コードブロック量多い版で段階的に紹介。特徴は、「deepL版-その1」と「deepL版-その2」があるところ。実際の運用は「deepL版-その1」を使ってる。
「deepL版-その1」はdeepL(txt)
でもdeepL(txt,lan1,lan2)
でも使える。引数txt
のみの時はen->ja固定(コードの該当部を変えればja->en固定にもいかようにも出来る)、引数3つとも使う場合は 原文・訳文の言語指定はlan1
、lan2
の記述順次第。
「deepL版-その2」は別シートに認証キーと原文、訳文の言語指定するタイプ。言語指定はプルダウンで選ぶ仕様にしてる。が、Google未ログインで閲覧のみのユーザーだとプルダウン出来ない。※冒頭のgoogleスプレのsetting
シートが設定用シート。
極力コードのみのシンプル版
/** 翻訳funcs
* 【1】deepL版-その1("DeepL API Free", 制限:500,000文字/月)
* 【2】deepL版-その2(別途設定sheet①認証KEY,②原文言語,③訳文言語 記入タイプ)
* 【3】AppScript版
*/
const APIURL ="https://api-free.deepl.com/v2/translate";
const APIKEY = "XXXXXXYYYYYYYZZZZZZZZZZ:fx"; //認証キー
const ss = SpreadsheetApp.getActiveSpreadsheet(); //スプレ特定用
const fetch_wait =200; //待ち時間考慮用(単位:ms)
/** ✅DeepL API版、翻訳STOP対策
* 【要点】原文が英語で「&」などの記号があると翻訳がSTOPする
* ようなので、その対策用
*/
function apllyRep(txt){
if (/&/.test(txt)){txt = txt.replace(/&/g,'and')}
return txt;
}
/**【1】deepL版-その1
* lan1とlan2無しOK,その場合en->ja
* ja->en か en->ja は lan1 と lan2 の記述順次第
*/
function deepL(txt,lan1='',lan2=''){
/** 対象文字がない時は働かない */
if ( txt.length === 0 ) null;
Utilities.sleep(fetch_wait);
txt= apllyRep(txt);
const param1 ='?auth_key='+APIKEY+'&text='+txt;
let param2 ='';
//txtのみ、この例では en->ja固定
if (!lan1 && !lan2){ param2 = '&source_lang=en&target_lang=ja' }
//原文->訳文 指定版
if (lan1 && lan2){ param2 = '&source_lang='+lan1+'&target_lang='+lan2 }
const param = encodeURI(param1+ param2);
//fetchで呼び出し、JSONパース
const res = UrlFetchApp.fetch(APIURL+param).getContentText();
const data = JSON.parse(res);
const result = data.translations[0].text;
return result;
}
/**【1】deepL版-その2
* 別途設定用sheetがあり ①認証KEY,②原文言語,③訳文言語 記入タイプ
*/
function deepL2(txt){
/** 対象文字がない時は働かない */
if ( txt.length === 0 ) null;
//★APPKEY、原文、訳文の取得
const settingSht = ss.getSheetByName('setting');
const key = settingSht.getRange(1,2).getValues();
const lan1 = settingSht.getRange(2,2).getValues();
const lan2 = settingSht.getRange(3,2).getValues();
txt= apllyRep(txt);
const param = encodeURI('?auth_key='+key+'&text='+txt+'&source_lang='+lan1+'&target_lang='+lan2);
const URL = APIURL+param; //as fetch's url
//fetchで呼び出し、JSONパース
const res = UrlFetchApp.fetch(URL).getContentText();
const data = JSON.parse(res);
const result = data.translations[0].text;
return result;
}
/**【3】AppScript版:LanguageApp:### 比較用 ###
* lan1とlan2無しOK,その場合en->ja固定
*/
function trsl(txt,lan1='',lan2='') {
if ( txt.length === 0 ) null;
if (!lan1 && !lan2){//この例ではen->ja固定
return LanguageApp.translate(txt,'en','ja');
}
if (lan1 && lan2){//原文->訳文 指定版
return LanguageApp.translate(txt,lan1,lan2);
}
}
以上、極力コードのみのシンプル版。
実際にGASに書いてるのが次の。よく考えるとあんまり必要ないっぽいので畳んでいます。
JSDocもある結果コードブロック量多い版
JSDocもある結果コードブロック量多い版
/** 翻訳func 更新@2024/06/04、@2024/06/03、@2024/05/19
* 【1】deepL版-その1 ("DeepL API Free", 無料, 制限:500,000文字/月)
* 【2】deepL版-その2 (別途設定sheet①認証KEY,②原文言語,③訳文言語 記入タイプ)
* 【3】AppScript版
共通変数
ss @type {string} アクティブなsheetの特定
lan1 @type {string} 原文(翻訳元)
lan2 @type {string} 訳文(翻訳先)
共通引数
@param {string} txt 原文言語
@param {string} sht sheet名特定用
返り値
@return {strings} result 翻訳結果
*/
const APIURL ="https://api-free.deepl.com/v2/translate";
const APIKEY = "XXXXXXYYYYYYYZZZZZZZZZZ:fx"; //認証キー
const ss = SpreadsheetApp.getActiveSpreadsheet(); //スプレ特定用
const fetch_wait =200; //待ち時間考慮用(単位:ms)
/** ✅DeepL API版、翻訳STOP対策
* 【要点】原文が英語で「&」などの記号があると翻訳がSTOPする
* ようなので、その対策用。
* ちなみにapp版やweb版では記号でもちゃんと翻訳する
*/
function apllyRep(txt){
//【条件】「&」記号がある場合のみ処理、【処理】正規表現で全部置き換え
//if (txt.includes('&')){//includesでもtestどっちでもいい
if (/&/.test(txt)){
txt = txt.replace(/&/g,'and')
}
return txt;
}
/**【1】deepL版-その1
* @see:https://qiita.com/chinotippy/items/d041627493cefb2f361d
* https://qiita.com/yaju/items/bf4613393cd4ee402d17
*
* lan1とlan2無しOK,その場合en->ja
* ja->en か en->ja は lan1 と lan2 の記述順次第
*
* `deepL(対象文字列,原文,訳文)` の順で txt,lan1,lan2
* @pararm {string} txt 対象文字列
* @pararm {string} lan1 原文言語 ex) ja
* @pararm {string} lan2 訳文言語 ex) en
* @parram [string} sheet
*/
function deepL(txt,lan1='',lan2=''){
/** 対象文字がない時は働かない */
if ( txt.length === 0 ) null;
//大量呼び出しを考慮し待ち時間(時差)
Utilities.sleep(fetch_wait);
//✅「&」などの記号でも翻訳STOPしない工夫
txt = apllyRep(txt);
const param1 ='?auth_key='+APIKEY+'&text='+txt;
let param2 ='';
//【引数指定=1つ】txtのみ:この例では en->ja固定
if (!lan1 && !lan2){
param2 = '&source_lang=en&target_lang=ja';
}
//【引数指定=3】すべての引数あり:原文->訳文 指定版
if (lan1 && lan2){
param2 = '&source_lang='+lan1+'&target_lang='+lan2;
}
//encodeURI:URIエンコード化版。日本語文字列の場合でエラー避け
const param = encodeURI(param1+ param2);
//fetchで呼び出し、JSONパース
const res = UrlFetchApp.fetch(APIURL+param).getContentText();
const data = JSON.parse(res);
const result = data.translations[0].text;
//結果
return result;
}
/**【1】deepL版-その2
* @see:https://liginc.co.jp/602562
* 別途設定用sheetがあり ①認証KEY,②原文言語,③訳文言語 記入タイプ
* 参照先のやり方なのは「★」部
* 改造:1.共通して使用する変数は冒頭のを使う
* 2.変数名など
* `deepl(対象文字列)` の順で txt
* @pararm {string} txt 対象文字列
*/
function deepL2(txt){
/** 対象文字がない時は働かない */
if ( txt.length === 0 ) null;
//★APPKEY、原文、訳文の取得
const settingSht = ss.getSheetByName('setting');
const key = settingSht.getRange(1,2).getValues();
const lan1 = settingSht.getRange(2,2).getValues();
const lan2 = settingSht.getRange(3,2).getValues();
//✅「&」などの記号でも翻訳STOPしない工夫
const txt = apllyRep(txt);
//encodeURI URIエンコード化版。理由:日本語文字列の場合でエラー避け
const param = encodeURI('?auth_key='+key+'&text='+txt+'&source_lang='+lan1+'&target_lang='+lan2);
const URL = APIURL+param; //as fetch's url
//fetchで呼び出し、JSONパース
const res = UrlFetchApp.fetch(URL).getContentText();
const data = JSON.parse(res);
const result = data.translations[0].text;
//結果
return result;
}
/**【3】AppScript版:LanguageApp:### 比較用 ###
* @see https://qiita.com/a_eau_/items/92430bc9d749fb9c330a
* > 配列を入力した場合は、
* カンマで結合してひとつの文字列にしたうえで、翻訳にかけられるよう
*/
/**【2-1】AppScript lan1とlan2無しOK,その場en->ja固定
* @pararm {string} txt 対象文字列
* @pararm {string} lan1 原文
* @pararm {string} lan2 訳文
*/
function trsl(txt,lan1='',lan2='') {
/** 対象文字がない時は働かない */
if ( txt.length === 0 ) null;
if (!lan1 && !lan2){//この例では en->ja固定
return LanguageApp.translate(txt,'ja','en');
}
if (lan1 && lan2){//原文->訳文 指定版
return LanguageApp.translate(txt,lan1,lan2);
}
}
遭遇したエラーとそこからの試行錯誤
- 文字化け可能性@日本語を訳すとき
- 「&」という記号があったら訳せない@DeepL API版
特に2番目の「&」記号問題が…(Φ言Φ) …、…。
🌀文字化け可能性@日本語を訳すとき
理由:日本語を訳すときurl欄の文字としてふさわしくない場合エラーになる
対応策:URIエンコード化をかます。encodeURI
:URIエンコード化。日本語文字列の場合でエラー避けになる。
🌀「&」という記号があったら訳せない@DeepL API版
現象:あるヶ所で翻訳STOPしてしまう (エラーにはならない)
原因:「&」がでたとこで翻訳STOP
理由:web版/app版のDeepLと API DeepLの仕様・性能の違い(多分)
より正しい理由:POST
かGET
のどちらを使っているか
対応策:「&」を「and」としっかり英単語として書き直したものを原文(翻訳元言語)として使用するようにする
DeepL API版では「&」記号がNG、「&」出現時点で翻訳STOP。(これは「&」->「and」に置き換える一工夫を加えて翻訳STOP現象を回避してる)
DeepL web/app版は「&」記号のままで問題ない。
【追記】より正しい理由:POST
かGET
のどちらを使っているか
web版もapp版(Mac)も「&」のままで何も問題なかったから、しばらく翻訳がうまくいかない原因が分からなかった。
コードどっかでミスってるのか、とか
複数行分の文章=セル内で改行があるとマズイのか、とか
イロイロ考えた…。
※↑参考にした記事のサンプルがことごとくセル内に1単語とか1文章とかだった
※↑しかも、自分で作った他のgoogleスプレで使うdeepL関数は問題なく訳せてから
いやはや原因が分かってヨカッタ、回避策となるコードも組み込むことができ手書きでわざわざ書き直すような事態を回避できるようになってヨカッタヨカッタ。
以上です。
最後に:参考記事
参考にした記事、改めて見てみると2021〜2022年の分が多い。紹介記事という側面も強いみたいで(多分当時にサービス出たばかりだからとか?)、APIの取得方法から図説で解説されてる場合が多い。
Google検索結果ページ
だいたいこれらの結果上位を4〜5ページ読めば傾向が分かる。Qiita分もQiita外の分もだいたいこれらから。
Qiita記事の分 〜日付つき〜
-
【GoogleAppsScript】GASでDeepL APIを叩く ↺2021年09月12日 ✎️️2021年09月12日
-
DeepL API x PySimpleGUIで翻訳支援アプリを作成 ↺2021年12月02日 ✎️️2021年12月01日
Qiita外の分 〜日付つき〜
DeepL本家関連
-
DeepL翻訳 web上のonline翻訳サービス (https://www.deepl.com/translator)
-
DeepLcom/deepl-php @GitHub (https://github.com/DeepLcom/deepl-php)
-
使用した文字数などの確認
ご利用中のDeepLアカウント - ご利用状況タブ (https://www.deepl.com/ja/your-account/usage))
⚠︎APIアカウントにlogin状態で表示されるページ (loginしてなかったら違うページが表示される)