Shadowlog Translation

Shadowlog english translation userscript

// ==UserScript==
// @name         Shadowlog Translation
// @namespace    https://github.com/zeluqa/
// @version      1.0
// @description  Shadowlog english translation userscript
// @author       zeluqa
// @match        *://shadowlog.com/*
// @grant        GM_addStyle
// ==/UserScript==

GM_addStyle ( `
    *{
        letter-spacing: 0px;
    }
    table.form .format-column input[type=radio] + label{
        font-size: 9px;
        position:relative;
        top: -3px;
        margin-right: 5px;
    }
    table.form textarea{
         font-family: Hiragino Kaku Gothic ProN;
    }
    table.search label{
         font-size: 10px !important;
    }
    table.search input[type=checkbox i]{
         position:relative !important;
         top: 2px !important;
    }
` );


(function() {
    'use strict';

    var words_overlay = {
        '先週の対戦解析ログ':'Last Week\'s Match Data',
        '配信まとめ':'Broadcasters',
        'サイトの使い方':'Site Usage',
        'ログアウト':'Log Out',
        'マイページ':'My Page',
    };
    var words_trends = {
        //Words exclusive in https://shadowlog.com/trend/
        '全ランク帯':'All Ranks',
        '総合 対戦解析ログ':'All Class Match Data',
        '総合 デッキ別の勝率解析':'All Class Deck Archetypes Win Rate',
        '算出に使った対戦数':'Total Recorded Number of Matches',
        '環境での各リーダーの使用割合':'Class Play Rate Comparison',
        '環境での各リーダーの勝率比較':'Class Win Rate Comparison',
        '使用されたリーダー':'Class Used',
        '使用数順':'Order by Matches',
        '勝利数順':'Order by Victories',
        '勝率順':'Order by Win Rate',
        '使用数':'Matches',
        '勝利数':'Victories',
        '先攻勝率':'1st Win Rate',
        '後攻勝率':'2nd Win Rate',
        '使用デッキタイプ':'Used Deck Archetype',
        'ランクマッチ':'Ranked',
        '2Pick対戦':'Take Two',
        '対戦解析ログ':'Match Data',

        '先行で勝利した割合(先攻勝率)':'Winrate going 1st',
        '後攻で勝利した割合(後攻勝率)':'Winrate going 2nd',

        '※同じデッキ同士での勝率は50%固定になりますので、表示を省略しています。':'※Same deck matchup is fixed at 50% winrate, therefore it will not be written in the table.',
    };
    var words_mypage = {
        //Words exclusive in https://shadowlog.com/mypage/add
        '12以上':'12+',
        '新しい戦績を追加':'Add New Record',
        'マイデッキ管理':'Manage Deck',
        'プロフィール編集':'Edit Profile',
        'ターン数表示':'Show turns',
        '省略している項目を開く':'Show omitted details',
        '常に「省略している項目」を開いた状態にしておく':'Show omitted details by default',
        '対戦で記録しておきたい事項をメモできます。(最大200文字)':'Note about the match (Max. 200 char)',
        '戦績の追加・編集に失敗しました。':'Failed to add add/edit your entry.',
        '相手のリーダーを正しく選んで下さい。':'Please select enemy class.',
        '自分のリーダーを正しく選んで下さい。':'Please select your class.',
        '先攻・後攻を正しく選んで下さい。':'Please select play order.',

        //Words exclusive in https://shadowlog.com/mypage/
        'あなたの戦績一覧':'Match Record Summary',
        '使用リーダー解析':'My Class Analysis',
        '対戦相手の解析':'Enemy Analysis',
        'あなたの勝率推移':'My Win Rate Analysis',
        'デッキの勝率解析':'Deck Win Rate Analysis',
        '2Pickの戦績':'Take Two Records',
        '新しく戦績を記録する':'Add a new match record',
        '記録した戦績を修正する':'Edit past match record',
        'この戦績を削除する':'Delete this entry',
        '自分のリーダー':'My Class',
        '相手のリーダー':'Enemy Class',
        '自分のデッキタイプを細かく指定':'My Deck Archetype',
        '相手のデッキタイプを細かく指定':'Enemy Deck Archetype',
        'この日から..':'From',
        'この日まで..':'To',
        '絞り込む':'Search',
        '手番':'Order',
        '対戦形式':'Format',
        'アンリミテッド':'Unlimited',
        'ローテーション':'Rotation',
        'AAランク':'AA Rank',
        'Aランク':'A Rank',
        'Bランク':'B Rank',
        'C以下':'C or Lower',
        'フリー':'Private',
        '2Pick':'Take Two',
        'ターン数':'Number of Turns',
        '対戦日時':'Date of Match',
        'メモ':'Note',
        'デッキタイプ':'Deck Archetype',
        //'「十禍絶傑」に対応しました。随時新デッキタイプ追加していきます。':'Omen of the Ten',
    };
    var words_analyze = {
        '解析の期間指定':'Period Filter',
        '対戦形式の指定':'Rank Filter',
        '新パック実装以降':'Only the Latest Expansion',
        '全ての期間':'All Period',
        '期間を指定':'Date Range',
        '入力':'Enter',
        '全ランクマッチ':'All Rank',
        '使用したリーダー解析':'Used Class Comparison',
        '使用したリーダー':'Class',
        '対戦数':'Matches',
        '使用したデッキ':'Deck Archetype',
        '全勝率':'Win Rate',
        '使用割合順':'Order by Play Rate',
        '対戦数順':'Order by Matches',
        '全勝率順':'Order by Winrate',
        'ランクマッチ戦でのみの解析になります。':'ランクマッチ戦でのみの解析になります。',

        '対戦相手のリーダー解析':'Enemy Class Comparison',
        '対戦相手のリーダー':'Enemy Class',
        '遭遇確率':'Encounter Rate',
        '遭遇数':'Matches',
        '対戦相手のデッキ解析':'Enemy Deck Archetype Summary',
        '対戦相手のデッキ':'Enemy Deck Archetype',
        'あなたの勝率':'My Win Rate',
        '対戦割合':'Encounter Rate',
        '対戦割合順':'Order by Encounter Rate',
        'あなたの勝率順':'Order by My Win Rate',

        'あなたの勝率の推移':'My Win Rate Trends',
        '現在の勝率':'Current Win Rate',
        '対戦回数':'Number of Matches',
        'あなたの勝利数':'Number of Victories',

        '解析したいデッキを選ぶ':'Select Deck to Analyze',
        '手番別の勝敗解析':'Play Order',
        '先攻':'1st',
        '後攻':'2nd',
        '勝利':'Victories',
        '敗北':'Losses',
        '合計':'Total',

        '※過去30戦の勝率の推移をグラフ化しています。':'※Graph is based on your past 30 matches.',
        '※過去30戦分の勝率の推移をグラフ化しています。':'※Graph is based on your past 30 matches.',
    };
    var words_archetypes = {
        //Forest Archetypes
        'エルフ全般':'Forest in General',
        'ミッドレンジエルフ':'Midrange Forest',
        'アグロエルフ':'Aggro Forest',
        'OTKエルフ':'OTK Forest',
        'テンポエルフ':'Tempo Forest',
        'コントロールエルフ':'Control Forest',
        'ニュートラルエルフ':'Neutral Forest',
        '冥府エルフ':'Path to Purgatory Forest',
        '白狼エルフ':'White Wolf Forest',
        '薔薇エルフ':'Thorn Burst Forest',
        '白銀エルフ':'Silver Bolt Forest',

        //Sword Archetypes
        'ロイヤル全般':'Sword in General',
        'ミッドレンジロイヤル':'Midrange Sword',
        'フェイスロイヤル':'Face Sword',
        'アグロロイヤル':'Aggro Sword',
        'コントロールロイヤル':'Control Sword',
        'ニュートラルロイヤル':'Neutral Sword',
        '指揮官ロイヤル':'Commander Sword',
        'カエルロイヤル':'Frog Sword',
        '御旗ロイヤル':'Royal Banner Sword',
        '潜伏ロイヤル':'Ambush Sword',
        '冥府ロイヤル':'Path to Purgatory Sword',
        '援護射撃ロイヤル':'Support Cannon Sword',

        //Dragon Archetypes
        'ドラゴン全般':'Dragon in General',
        '疾走ランプドラゴン':'Storm Ramp Dragon',
        'ランプドラゴン':'Ramp Dragon',
        'リントヴルムドラゴン':'Lindworm Dragon',
        'ジャバウォックドラゴン':'Jabberwock Dragon',
        'ミッドレンジドラゴン':'Midrange Dragon',
        '原初ドラゴン':'Prime Dragon Keeper Dragon',
        'ニュートラルドラゴン':'Neutral Dragon',
        '竜爪ドラゴン':'Dragonclaw Pendant Dragon',
        'OTKドラゴン':'OTK Dragon',
        'ディスカードドラゴン':'Discard Dragon',
        'フェイスドラゴン':'Face Dragon',
        'サタンドラゴン':'Prince of Darkness Dragon',
        '疾走ドラゴン':'Storm Dragon',
        '庭園ドラゴン':'Phoenix Roost Dragon',

        //Shadow Archetypes
        'ネクロマンサー全般':'Shadow in General',
        'リアニメイトネクロ':'Reanimate Shadow',
        'ミッドレンジネクロ':'Midrange Shadow',
        'アグロネクロ':'Aggro Shadow',
        '骸ネクロ':'Atomy Shadow',
        'ネフティスネクロ':'Nephthys Shadow',
        'コントロールネクロ':'Control Shadow',
        'ラストワードネクロ':'Last Words Shadow',
        'ニュートラルネクロ':'Neutral Shadow',
        'タイラントネクロ':'Tyrant Shadow',
        '冥府ネクロ':'Path to Purgatory Shadow',

        //Rune Archetypes
        'ウィッチ全般':'Rune in General',
        'ドロシー超越ウィッチ':'Daria DShift Rune',
        '暗黒ウィッチ':'Abyss Summoner Rune',
        'マナリアウィッチ':'Mysteria Rune',
        'ギガントキマイラウィッチ':'Giant Chimera Rune',
        '超越ウィッチ':'Dimension Shift Rune',
        '秘術ウィッチ':'Dirt Rune',
        'ドロシーウィッチ':'Daria Rune',
        'ニュートラルウィッチ':'Neutral Rune',
        'アグロウィッチ':'Aggro Rune',
        '冥府ウィッチ':'Path to Purgatory Rune',
        '魔導ウィッチ':'Hulking Giant Rune',

        //Blood Archetypes
        'ヴァンパイア全般':'Blood in General',
        'ミッドレンジヴァンプ':'Midrange Blood',
        'コントロールヴァンプ':'Control Blood',
        '蝙蝠ヴァンパイア':'Darkfeast Bat Blood',
        'ヨルムンガンドヴァンパイア':'Jormungand Blood',
        'アグロヴァンパイア':'Aggro Blood',
        '復讐ヴァンパイア':'Vengeance Blood',
        'OTKヴァンパイア':'OTK Blood',
        'ニュートラルヴァンプ':'Neutral Blood',
        '疾走ヴァンパイア':'Storm Blood',
        '冥府ヴァンパイア':'Path to Purgatory Blood',

        //Haven Archetypes
        'ビショップ全般':'Haven in General',
        'エイラセラフビショップ':'Elana Seraph Haven',
        '天狐ビショップ':'Tenko Haven',
        '聖獅子ビショップ':'Holy Lion Haven',
        '疾走ビショップ':'Storm Haven',
        'イージスビショップ':'Aegis Haven',
        '教会ビショップ':'Summit Temple Haven',
        'ニュートラルビショップ':'Neutral Haven',
        'コントロールビショップ':'Control Haven',
        '陽光ビショップ':'Guardian Sun Haven',
        '燭台ビショップ':'Candelabra Haven',
        'セラフビショップ':'Seraph Haven',
        'エイラビショップ':'Elana Haven',
        'カウントビショップ':'Countdown Amulet Haven',
        '冥府ビショップ':'Path to Purgatory Haven',
        'レリアビショップ':'Laelia Haven',
        '聖杯ビショップ':'Tarnished Grail Haven',

        //Portal Archetype
        'ネメシス全般':'Portal in General',
        '操り人形ネメシス':'Puppet Portal',
        'ミッドレンジネメシス':'Midrange Portal',
        'クロノスネメシス':'Chronos Portal',
        'アーティファクトネメシス':'Artifact Portal',
        'アグロネメシス':'Aggro Portal',
        'コントロールネメシス':'Control Portal',
    };
    var words_classes = {
        //Class Names
        'エルフ':'Forest',
        'ロイヤル':'Sword',
        'ドラゴン':'Dragon',
        'ネクロマンサー':'Shadow',
        'ネクロ':'Shadow',
        'ウィッチ':'Rune',
        'ヴァンパイア':'Blood',
        'ヴァンプ':'Blood',
        'ビショップ':'Haven',
        'ネメシス':'Portal',
        '総合':'All Class',
    };
    var words_login = {
        'ログイン画面へ':'Log in',
        'ログイン画面':'Log In',
        'ログインに失敗しました。':'Log in failed.',
        '登録メールアドレスでログイン':'Log In',
        'OpenIDでログイン':'Log In Using OpenID',
        'Twitterアカウント':'Twitter Account',
        'Facebookアカウント':'Facebook Account',
        'Googleアカウント':'Google Account',
        'でログイン':'Log In',
        'パスワードを忘れてしまった..':'Forgot Password',
        '変更する':'Reset Password',
        'パスワードリマインダー':'Password Reset',
        'メールアドレスを入力...':'Email Address',
        'ログインパスワードがわからないという方へ':'Log In Password Reset',
        'ユーザー名が一致しません。もう1度正しく入力して下さい。':'Your inputted username is wrong, please try again.',
        'パスワード変更用URLを送信しました。':'URL to reset your password has been sent to your email address.',
        'パスワードの変更を行います。':'Password will be resetted.',
        '以下の項目を全て入力して下さい。':'Please fill out the form below.',
        'あなたのユーザー名':'Username',
        '新しく設定したいパスワードを入力してください。':'New Password',
        '確認のためパスワードをもう1度入力してください。':'New Password Confirmation',
        'パスワードは正しく変更されました。':'Password is successfully resetted.',
    };
    var words_register = {
        '新規アカウントを登録する':'Sign Up',
        '登録に失敗しました。':'Registration failed.',
        'は既に登録済みです。':' is already registered.',
        '登録したいユーザー名':'Username',
        '登録したいメールアドレス':'Email Address',
        'OpenIDでアカウント登録':'Sign Up Using OpenID',
        'で登録する':'Sign Up',
        'まだ戦績の記録がありません。':'You don\'t have any match recorded yet.',
    };
    var words_main = {
        '新規ユーザー登録':'New User Registration',
    }
    var words_basic = {
        //Collection of words/texts that are used in more than one phrasing
        '先':'1st',
        '後':'2nd',
        'ローテ':'Rotation',
        '勝率':'Win Rate',
        '使用割合':'Play Rate',
        '自分のデッキ':'My Deck',
        '相手のデッキ':'Enemy Deck',
        'ログイン':'Log in',
        'ユーザー登録':'Register',
        'メールアドレス':'Email Address',
        'ユーザー名':'Username',
        'パスワード':'Password',
        'ユーザー新規登録':'User Registration',
        '編集':'Edit',
        '勝敗':'Win',

        /* Alternative Translation but looks bad
        '年':'Year,',
        '月':'Month,',
        '日':'Date,',
        '時':'Hour,',
        '分':'Minute', */
    };
    var words_regexp = {
        /* Use \\* to match actual asterisks instead of using it as a wildcard
        Syntax: 'Search word' : 'Replace word',
        '/\\bD\\b/g' : '[D]',
        More complex words/text should be on top to prevent incomplete text replacement*/

        //Words for regexp text replacement method
        'ジャバウォックドラゴ*':'Jabberwock Dragon',
        'ヨルムンガンドヴァン*':'Jormungand Blood',
        'ギガントキマイラウィ*':'Giant Chimera Rune',
        'ニュートラルビショッ*':'Neutral Haven',
        'エイラセラフビショッ*':'Elana Seraph Haven',
        'コントロールビショッ*':'Control Haven',
        'アーティファクトネメ*':'Artifact Portal',

        '新しく戦績を記録する':'Add a new match record',
        '記録した戦績を修正する':'Edit past match record',
        'フリー':'Private',
        'ローテ':'Rotation',

        '対戦解析ログ':'Match Data',
        '算出に使った対戦数':'Total Recorded Number of Matches',

        'このサイトのサービスを利用するためには、簡単なユーザー登録を行って頂く必要があります。':'Please create an account to use the services provided by this site.',
        '会員登録にかかる費用は全て無料です。':'You can register an account for free.',
        '登録したメールアドレスからパスワードの変更を行うことができます。':'You can reset your registered account\'s password.',
        '以下のフォームにメールアドレスを正しく入力して下さい。':'Please enter your email address in the form below.',
        'メールアドレスへパスワード変更用のURLを記載したメールを送信しました。':'Please check your email address for the URL to reset your password.',
        'メールアドレスもわからない、上手くメールが届かないという方は':'In case of forgotten email address or mail not being sent properly,',
        'お手数ですが':'Please use the',
        'お問い合わせ':'Inqury',
        'へご連絡下さい。':'function to contact us.',
        '変更が失敗してしまう場合は':'When password resetting failed, please use the',
        'からご連絡下さい。':'function to contact us.',
        '画面左上の':'Use the',
        'ボタンを押して戦績を追加して下さい。':'button to add matches.',
        'ランクマッチ戦でのみの解析になります。':'This chart only lists ranked matches.',
        '平均勝率':'Average Win Rate',
        '対戦デッキ別の勝率解析':'Deck Matchup Win Rate',
        '解析':'Analysis',

        '年':'/',
        '月':'/',
        '日':'',
        '時':':',
        '分':'',
        '回':'',
    }

    var regexs = [], replacements = [],
        tagsWhitelist = ['PRE', 'BLOCKQUOTE', 'CODE', 'INPUT', 'BUTTON', 'TEXTAREA', 'SCRIPT'],
        attr = ['value', 'placeholder'],
        rIsRegexp = /^\/(.+)\/([gim]+)?$/,
        word, text, texts, i, j, k, len, userRegexp, key, obj;

    var words_json = {}; //Combined words list
    var words = {}; //Combined words list later to be converted to regexp

    for(key in words_overlay) words_json[key] = words_overlay[key];
    for(key in words_trends) words_json[key] = words_trends[key];
    for(key in words_mypage) words_json[key] = words_mypage[key];
    for(key in words_analyze) words_json[key] = words_analyze[key];
    for(key in words_archetypes) words_json[key] = words_archetypes[key];
    for(key in words_classes) words_json[key] = words_classes[key];
    for(key in words_login) words_json[key] = words_login[key];
    for(key in words_register) words_json[key] = words_register[key];
    for(key in words_main) words_json[key] = words_main[key];
    for(key in words_basic) words_json[key] = words_basic[key];

    for(key in words_regexp) words[key] = words_regexp[key];
    for(key in words_archetypes) words[key] = words_archetypes[key];

    // used to take a string and ready it for use in new RegExp()
    function prepareRegex(string) {
        return string.replace(/([\[\]\^\&\$\.\(\)\?\/\\\+\{\}\|])/g, '\\$1');
    }

    // function to decide whether a parent tag will have its text replaced or not
    function isTagOk(tag) {
        return tagsWhitelist.indexOf(tag) === -1;
    }

    delete words['']; // Delete empty key/value pair

    // convert the 'words' JSON object to an Array
    for (word in words) {
        if ( typeof word === 'string' && words.hasOwnProperty(word) ) {
            userRegexp = word.match(rIsRegexp);

            // add the search/needle/query
            if (userRegexp) {
                regexs.push(
                    new RegExp(userRegexp[1], 'g')
                );
            } else {
                regexs.push(
                    new RegExp(prepareRegex(word).replace(/\\?\*/g, function (fullMatch) {
                        return fullMatch === '\\*' ? '*' : '[^ ]*';
                    }), 'g')
                );
            }

            // add the replacement
            replacements.push( words[word] );
        }
    }

    //Translation method using json key value by default then regexp if words is not found
    function translate() {
        //Text replacement
        texts = document.evaluate('//input|//textarea|//body//text()[normalize-space(.) != ""]', document, null, 6, null);
        for (i = 0; text = texts.snapshotItem(i); i++) {
            if ( isTagOk(text.parentNode.tagName) ) {
                if(text.tagName) { //For html attribute text translation
                    for (k = 0; k < attr.length; k++) {
                        if (text.getAttribute(attr[k])) { //Attribute exist
                            if(words_json[text.getAttribute(attr[k])]) text.setAttribute(attr[k], words_json[text.getAttribute(attr[k])]); //Translation exist in JSON word list
                            else {
                                //Regexp text replacement
                                for (j = 0, len = regexs.length; j < len; j++) {
                                    text.setAttribute(attr[k], text.getAttribute(attr[k]).replace(regexs[j], replacements[j]));
                                }
                            }
                        }
                    }
                } else { //For raw text translation
                    if(words_json[text.data]) text.data = words_json[text.data]; //Translation exist in JSON word list
                    else {
                        for (j = 0, len = regexs.length; j < len; j++) {
                            text.data = text.data.replace( regexs[j], replacements[j] ); //Regexp text replacement
                        }
                    }
                }
            }
        }
    }

    //Overlay text for 1st 2nd play order in /mypage/add
    if ((window.location.href).match("/mypage/add")){
        (document.querySelector("label[for=ord1]")).setAttribute("style", "position:relative;");
        (document.querySelector("label[for=ord2]")).setAttribute("style", "position:relative;");
        //Add 1st text overlay
        if (!((document.querySelector("label[for=ord1]").innerHTML).match("h3"))) {
            document.querySelector("label[for=ord1]").innerHTML = document.querySelector("label[for=ord1]").innerHTML + `<h3 style="position: absolute;color: #d8d8d8;left: 28%;bottom: 82%;">1st</h3>`
        }
        //Add 2nd text overlay
        if (!((document.querySelector("label[for=ord2]").innerHTML).match("h3"))) {
            document.querySelector("label[for=ord2]").innerHTML = document.querySelector("label[for=ord2]").innerHTML + `<h3 style="position: absolute;color: #d8d8d8;left: 25%;bottom: 82%;">2nd</h3>`
        }
    }

    function replaceUnicode (unicode) {
        switch(unicode) {
            case "\u30a8\u30eb\u30d5":
                return "Forest";
            case "\u30ed\u30a4\u30e4\u30eb":
                return "Sword";
            case "\u30c9\u30e9\u30b4\u30f3":
                return "Dragon";
            case "\u30cd\u30af\u30ed\u30de\u30f3\u30b5\u30fc":
                return "Shadow";
            case "\u30a6\u30a3\u30c3\u30c1":
                return "Rune";
            case "\u30f4\u30a1\u30f3\u30d1\u30a4\u30a2":
                return "Blood";
            case "\u30d3\u30b7\u30e7\u30c3\u30d7":
                return "Haven";
            case "\u30cd\u30e1\u30b7\u30b9":
                return "Portal";
        }
    }

    //Edit and recreate chart
    var canvases = document.evaluate('//canvas', document, null, 6, null);
    var canvas, html, parent;
    for (i = 0; canvas = canvases.snapshotItem(i); i++) {
        if ((canvas.getAttribute('id') == 'pie_chart') || (canvas.getAttribute('id') == 'bar_chart')) {
            //Delete current chart canvas
            parent = canvas.parentNode;
            html = canvas.parentNode.innerHTML;
            parent.removeChild(canvas);
            //Remake empty chart canvas
            parent.appendChild(document.createElement('canvas'));
            parent.innerHTML = html;

            if (canvas.getAttribute('id') == 'pie_chart'){
                var pieData = unsafeWindow.pieData;
                for (j = 0; j < pieData.length; j++){
                    obj = pieData[j];
                    for (key in obj){
                        if (key == 'label') obj[key] = replaceUnicode(obj[key]);
                    }
                }
                var ctx = document.getElementById("pie_chart").getContext("2d");
                var myPie = new unsafeWindow.Chart(ctx).Pie(pieData, {
                    animation : false,
                });
            } else if (canvas.getAttribute('id') == 'bar_chart'){
                var barChartData = unsafeWindow.barChartData;
                for (j = 0; j < barChartData.labels.length; j++){
                    barChartData.labels[j] = replaceUnicode(barChartData.labels[j]);
                }
                ctx = document.getElementById("bar_chart").getContext("2d");
                var myBar = new unsafeWindow.Chart(ctx).Bar(barChartData,{
                    scaleOverride : true,
                    scaleSteps : 7,
                    scaleStepWidth : 10,
                    scaleStartValue : 0,
                    animation : false,
                });
            }
        }
    }

    window.addEventListener("load", function(){translate()}, false);
    var form_button = document.querySelector("table.form");
    if (form_button) form_button.addEventListener('change', function(){translate()}, false); //Handles list change in /mypage/add
})();