Greasy Fork is available in English.

Twitch Translate Chat Messages

Double-Click to Translate Twitch Chat Messages

// ==UserScript==
// @name          Twitch Translate Chat Messages
// @namespace     http://userstyles.org
// @description   Double-Click to Translate Twitch Chat Messages
// @author        636597
// @include       *://*.twitch.tv/*
// @run-at        document-start
// @version       1.0
// ==/UserScript==

var enableTranslation = false;
var destinationLanguage = "en";
// You have to setup some coors enabled https site with
// var GoogleTranslateBase = https://github.com/matheuss/google-translate-api
// or use 
// var GoogleTranslateBase = "https://translation.googleapis.com/language/translate/v2";
// https://console.cloud.google.com/apis/credentials
var gapi_key = "";

// Search for Occurrence *anywhere* in message
var enableBlacklist = false;
var blacklist_anywhere_words = [
];

// Search for these exact words
var blacklist_exact_words = [
    "BTTV!",
    "Prime" ,
    "Subscribe",
    "subscription",
];

function searchBlacklist( wText ) {
    var lower = wText.toLowerCase();
    for ( var i = 0; i < blacklist_anywhere_words.length; ++i ) {
        if ( lower.indexOf( blacklist_anywhere_words[ i ] ) !== -1 ) {
            //console.log( "found anywhere" );
            //console.log( wText );
            return true;
        }
    }
    var x11 = wText.split( ":" )[ 1 ];
    if ( x11 ) {
        x11 = x11.split( " " );
        for ( var j = 0; j < x11.length; ++j ) {
            for ( var i = 0; i < blacklist_exact_words.length; ++i ) {
                if ( x11[ j ] === blacklist_exact_words[ i ] ) {
                    //console.log( "found exact" );
                    //console.log( wText );
                    return true;
                }
            }
        }
    }
    return false;
}

function fixedEncodeURIComponent(str){
    return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
}

var TranslationActive = false;
var gapi_key = "";
var GoogleTranslateEnd = "&key=" + gapi_key;
var TranB1 = "q=";
var TranB2 = "&target=" + destinationLanguage;
function translateText( text , dom_elem ) {
    if ( !text ) { return; }
    if ( !dom_elem ) { return; }
    text = text.trim();
    var query_string = TranB1 + fixedEncodeURIComponent( text ) + TranB2 + GoogleTranslateEnd;
    var anHttpRequest = new XMLHttpRequest();
    anHttpRequest.onreadystatechange = function() {
        if ( anHttpRequest.readyState == 4 && anHttpRequest.status == 200 ) {
            var response = anHttpRequest.responseText;
            console.log( response );
            var translation = JSON.parse( response );
            translation = translation[ "data" ][ "translations" ][ 0 ][ "translatedText" ];
            translation = translation.trim();
            var has_mention_fragment = false;
            var mention_frag_node = null;
            for ( var i = 0; i < dom_elem.childNodes.length; ++i ) {
                if ( dom_elem.childNodes[ i ].className === "mention-fragment" ) { has_mention_fragment = true; mention_frag_node = dom_elem.childNodes[ i ]; }
                var attr = dom_elem.childNodes[ i ].getAttribute( "data-a-target" );
                if ( attr === "chat-message-text" ) {
                    dom_elem.childNodes[ i ].innerHTML = translation;
                    dom_elem.setAttribute( "data-showTranslation" ,  "true" );
                    dom_elem.setAttribute( "data-translatedText" , translation );
                    dom_elem.setAttribute( "data-originalText" ,  text );
                }
            }
            if ( has_mention_fragment ) { dom_elem.removeChild( mention_frag_node ); }
        }
    };
    anHttpRequest.open( "POST", GoogleTranslateBase , true );
    anHttpRequest.setRequestHeader( "Content-Type" , "application/x-www-form-urlencoded; charset=UTF-8" );
    anHttpRequest.send( query_string );
}

var chat_element = null;
var chat_observer = null;
var observerConfig = {
    attributes: true,
    childList: true,
    characterData: true
};
function loadObserver() {
    chat_observer = new MutationObserver(function(mutations) {
        mutations.forEach(function( mutation , index ) {
            if ( mutation.type === "childList" ) {
                var addedNode = mutation.addedNodes[0];
                if( addedNode ) {

                    var msg = addedNode.innerText;

                    // Revert Back to Original
                    if ( TranslationActive ) {
                        addedNode.addEventListener( "dblclick" , function() {
                          var that = this;
                          var show_translation = addedNode.getAttribute( "data-showTranslation" );
                          console.log( show_translation );
                          if ( show_translation ) {
                            if ( show_translation === "true" ) {
                                //that.style.background = "red";
                                console.log( "reverting" );
                                console.log( "old text === " );
                                var original_text = addedNode.getAttribute( "data-originalText" );
                                //console.log( original_text );
                                for ( var i = 0; i < addedNode.childNodes.length; ++i ) {
                                    if ( !addedNode.childNodes[ i ] ) { continue; }
                                    var attr = addedNode.childNodes[ i ].getAttribute( "data-a-target" );
                                    if ( attr === "chat-message-text" ) {
                                        addedNode.childNodes[ i ].innerText = original_text;
                                        break;
                                    }
                                }
                                addedNode.setAttribute( "data-showTranslation" , "false" );
                            }
                            else {
                                //that.style.background = "green";
                                addedNode.setAttribute( "data-showTranslation" ,  "true" );
                                var already_translated = addedNode.getAttribute( "data-translatedText" );
                                //console.log( "already_translated text === " );
                                //console.log( already_translated );                                
                                for ( var i = 0; i < addedNode.childNodes.length; ++i ) {
                                    if ( !addedNode.childNodes[ i ] ) { continue; }
                                    var attr = addedNode.childNodes[ i ].getAttribute( "data-a-target" );
                                    if ( attr === "chat-message-text" ) {
                                        addedNode.childNodes[ i ].innerHTML = already_translated;
                                        break;
                                    }
                                }
                            }
                          }
                          else {
                            //that.style.background = "green";
                            var start = msg.indexOf( ":" );
                            var z1 = msg.substring( ( start + 1 ) );
                            translateText( z1 , addedNode );
                          }
                        } , false );
                    }

                    if ( enableBlacklist ) {
                        var remove = searchBlacklist( msg );

                        // If Not Already Set to be Removed , Search Emotes
                        if ( !remove ) {
                            for ( var i = 0; i < addedNode.childNodes.length; ++i ) {
                                if ( !addedNode.childNodes[ i ] ) { continue; }
                                if ( !remove ) {
                                    var alt_text_target = addedNode.childNodes[ i ].getAttribute( "data-a-target" );
                                    if ( alt_text_target === "emote-name" ) {
                                        for ( var j = 0; j < addedNode.childNodes[ i ].childNodes.length; ++j ) {
                                            if ( !addedNode.childNodes[ i ].childNodes[ j ] ) { continue; }
                                            if ( !remove ) {
                                                if ( addedNode.childNodes[ i ].childNodes[ j ].alt ) {
                                                    var test = searchBlacklist( addedNode.childNodes[ i ].childNodes[ j ].alt );
                                                    if ( test ) {
                                                        remove = true;
                                                        break;                                                    
                                                    }
                                                }
                                            }
                                            else { break; }
                                        }
                                    }
                                }
                                else { break; }
                            }
                        }

                        if ( remove ) {
                            if ( addedNode.parentNode ) {
                                try {
                                    addedNode.setAttribute( "style", "visibility: hidden !important" );
                                    addedNode.setAttribute( "style", "height: 0 !important" );
                                    addedNode.setAttribute( "style", "padding: 0 !important" );
                                    addedNode.innerHTML = "";
                                }
                                catch( e ) { console.log( e ); }
                            }
                        }
                    }
                }
            }
        });
    });

    if ( enableTranslation && gapi_key ) {
        TranslationActive = true;
        console.log( "Translation Option Loaded" );
    }
    if ( TranslationActive || enableBlacklist ) {
        if ( enableBlacklist ) {
            console.log( "Blacklist Option Loaded" );
        }
        chat_observer.observe( chat_element , observerConfig );
    }
}

(function() {
    var ready = setInterval(function(){
        var x1 = document.querySelectorAll( '[role="log"]' );
        if ( x1 ) { if ( x1[ 0 ] ) { chat_element = x1[0]; clearInterval( ready ); loadObserver(); } }
    } , 2 );
})();