プログラミング・動画編集 備忘録

プログラミングや動画編集についての備忘録です

The Elder Scrolls: Arena の日本語化 - 8(DeepL翻訳連携)

前回

NPCとの会話のメッセージが表示されるダイアログ部分のサイズがメッセージ量により異なるため、PCOTでの固定翻訳がしづらい事から、ダイアログ部分のサイズを取得するためにテンプレートマッチングを行うことにした。
rrryutaro.hatenablog.com

今回

ダイアログ部分は他にもお店でのやりとりなどで表示が異なるため、いくつかテンプレートを用意した。
PCOTでのOCRを行わないので、翻訳についても自動的に翻訳が行えるようにしたい。
一番最初の開発段階でSelenium(セレニウム)を利用して、ブラウザ上で翻訳操作を行う仕組みは既に用意してあり、その際はGoogle翻訳を使用していたが、以前作成したスクレイピングの方法では動作しなくなっていたため、折角なので今回はDeepL翻訳を利用することにする。

なお、この回の内容はあまり良い方法ではなかったため、次の回で改善したものがあるのでそちらを参照。
内容の違いは、今回はブラウザの動作内にてテキストエリアの動作を利用したもので、次の回は純粋にURLに翻訳文字列を入れる方法となっている。
rrryutaro.hatenablog.com

Seleniumでのブラウザ操作の自動化

以前にこちらで記事にしているので、今回はDeepL翻訳のスクレイピングを行う。
qiita.com

次の形でDeepL翻訳のページを開いておく。もし他の言語を扱う場合はURLやパラメーターを調整すればいいはず。
https://www.deepl.com/ja/translator

そして、翻訳するテキストエリアに文字列を送り、翻訳結果が1文字以上得られるまで、0.1秒間隔でSleepしながらループして、結果が得られたらループから抜けるといった方法を取っている。
これだけの事なので、他の翻訳サイトなども利用可能だが、Google翻訳などはサイト構成などが度々変わるようなのでその度に対処が必要になる。
操作方法を動的に変更できるような仕組みを用意すれば何でも出来るようになるかとは思うが、今回のツールではそこまでの仕組みを用意するほどではないのでこの程度の形にしておく。

注:次のコードは不完全です。詳細は以降の内容をご確認ください。

        public string DeepLTranslation(string src)
        {
            TranslationString = "";
            var sourceTextarea = driver.FindElement(By.ClassName("lmt__source_textarea"));
            sourceTextarea.SendKeys(src);
            if (wait.Until(condition =>
            {
                var count = 0;
                while (true)
                {
                    TranslationString = condition.FindElement(By.ClassName("lmt__target_textarea")).GetProperty("value");
                    if (1 < TranslationString.Length)
                    {
                        break;
                    }
                    Thread.Sleep(100);
                    if (100 < count++)
                    {
                        return false;
                    }
                }
                return true;
            }))
            {
                sourceTextarea.Clear();
                return TranslationString;
            }
            return String.Empty;
        }

実際の動作


ゲーム画面

OCR結果

翻訳結果







ゲーム画面

OCR結果

翻訳結果






いくつか試したが翻訳結果を取得するのに次のように1文字以上としているので、長文だと翻訳途中の結果を返してしまう時がある。

                    if (1 < TranslationString.Length)

例えば次のようなケースについて。


ゲーム画面

ダイアログ読取後

翻訳文字列





Good day. I‘m having a little problem I hoped you be able to solve. For reasons of business, Elyzausa Hawking, that‘s my mother, needs to get to Ounynali‘s General Supply Store by Sundas, bth of Hearthfire. I had a couple swordsmen hired to escort hi there, but they wound up dead a half hour ago, chopped into goblin snacks by the Afterdark Society. I was going to have may contact in Ounynali‘s General Supply Store pay them 119 gold. That money‘s yours if you take their Ee Yes \ No

翻訳途中の結果が次のように出力される。

ごきげんよう。[...]  [...]  [...]  [...]  [...]  

これはおそらく、ピリオドで次のよう区切られて順次翻訳されているのだと思われる。

Good day.
I‘m having a little problem I hoped you be able to solve.
For reasons of business, Elyzausa Hawking, that‘s my mother, needs to get to Ounynali‘s General Supply Store by Sundas, bth of Hearthfire.
I had a couple swordsmen hired to escort hi there, but they wound up dead a half hour ago, chopped into goblin snacks by the Afterdark Society.
I was going to have may contact in Ounynali‘s General Supply Store pay them 119 gold.
That money‘s yours if you take their Ee  Yes \  No 

このため、[...]が含まれていたらまだ翻訳途中だと判断することが出来るはずである。
だが、出来れば翻訳途中であるとか、翻訳が完了しているといったようなステータスがあればそうしたもので判断したい所だが、ハッキリとそれとわかる情報は見つけられなかった。
しかしながら、翻訳結果が出るまでは次のように処理中を示す進捗インジケータが表示されている。

この際のタグは次のようになっている。

<div class="lmt__loadingIndicator_container">
  <div class="loadingIndicator_module_loadingIndicator__e5ced9d0 loadingIndicator_module_size_m__e5ced9d0  loadingIndicator_module_color_default__e5ced9d0">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</div>

ともかくも、とりあえずは次のようにこの部分の表示の有無を判定することで翻訳が完了しているかを判断できそうではある。

if (!condition.FindElement(By.ClassName("lmt__loadingIndicator_container")).Displayed)
{
    break;
}

しばらくこれで様子を見て、問題があるようであればまたその際により良い方法を考えたいと思う。
しかしながらこの方法でも時折次のような結果となる問題が発覚した。
翻訳対象の文字列:

"You are in Chaseguard. It is 142 in the morning. The date is Tirdas, 15th of Frostfall in the year 36 389 You are currently carrying 51 kg out of 89 Kg. You have Swarip Rot. "

翻訳結果:

"Y "

Thread.Sleep(100)毎に翻訳文字列とcondition.FindElement(By.ClassName("lmt__loadingIndicator_container")).Displayedの状態を出力してみた所。
通常であれば翻訳結果が返るのに最低でも500ミリ秒以上はかかるが、300ミリ秒以内に結果が返されていた。
何度か試した結果傾向としては次のようなケースがみられた。

  • 翻訳元の頭文字 1 文字 + スペース 1文字
  • [...]が含まれている場合と含まれていない場合がある
  • 最初の100ミリ秒時点で進捗インジケータが非表示となっているのに、翻訳が完了するのは1200ミリ秒など

この部分を見ておけば大丈夫といったものが無いため、次の複合での判定を行って様子を見る事にする。

  • 進捗インジゲータが非表示であること
  • [...]が含まれていないこと
  • 翻訳結果の文字数が翻訳元の文字数の 1 / 5 以上であること
if (!condition.FindElement(By.ClassName("lmt__loadingIndicator_container")).Displayed && !TranslationString.Contains("[...]") && src.Length / 5 < TranslationString.Length)
{
    break;
}

1 / 5というのは特に明確な基準があるわけでは無いので、もう少しまっとうな計算方法を考えたい所ではある。

なお、上記までの記事変更をTwitterにて通知した後、Twitterにて適切な方法のアドバイスを頂いたため、次の回にてやり方を変更したので、上記方法はいったん廃止とした。
rrryutaro.hatenablog.com