Language Switcher for Google™

a port of Language Switcher for Google™ Chome extension (https://chrome.google.com/webstore/detail/language-switcher-for-goo/jbbappjmafnnhhalfbhdhiemchcgejcp) to userscript.js

// ==UserScript==
// @name        Language Switcher for Google™
// @author      gb.ianni shunf4
// @namespace   gb.ianni
// @version     2.3.4
// @copyright   Copyright 2015-2022 gb.ianni shunf4
// @description a port of Language Switcher for Google™ Chome extension (https://chrome.google.com/webstore/detail/language-switcher-for-goo/jbbappjmafnnhhalfbhdhiemchcgejcp) to userscript.js
// @include *://www.google.com/*
// @include *://www.google.ad/*
// @include *://www.google.ae/*
// @include *://www.google.com.af/*
// @include *://www.google.com.ag/*
// @include *://www.google.com.ai/*
// @include *://www.google.am/*
// @include *://www.google.co.ao/*
// @include *://www.google.com.ar/*
// @include *://www.google.as/*
// @include *://www.google.at/*
// @include *://www.google.com.au/*
// @include *://www.google.az/*
// @include *://www.google.ba/*
// @include *://www.google.com.bd/*
// @include *://www.google.be/*
// @include *://www.google.bf/*
// @include *://www.google.bg/*
// @include *://www.google.com.bh/*
// @include *://www.google.bi/*
// @include *://www.google.bj/*
// @include *://www.google.com.bn/*
// @include *://www.google.com.bo/*
// @include *://www.google.com.br/*
// @include *://www.google.bs/*
// @include *://www.google.co.bw/*
// @include *://www.google.by/*
// @include *://www.google.com.bz/*
// @include *://www.google.ca/*
// @include *://www.google.cd/*
// @include *://www.google.cf/*
// @include *://www.google.cg/*
// @include *://www.google.ch/*
// @include *://www.google.ci/*
// @include *://www.google.co.ck/*
// @include *://www.google.cl/*
// @include *://www.google.cm/*
// @include *://www.google.cn/*
// @include *://www.google.com.co/*
// @include *://www.google.co.cr/*
// @include *://www.google.com.cu/*
// @include *://www.google.cz/*
// @include *://www.google.de/*
// @include *://www.google.dj/*
// @include *://www.google.dk/*
// @include *://www.google.dm/*
// @include *://www.google.com.do/*
// @include *://www.google.dz/*
// @include *://www.google.com.ec/*
// @include *://www.google.ee/*
// @include *://www.google.com.eg/*
// @include *://www.google.es/*
// @include *://www.google.com.et/*
// @include *://www.google.fi/*
// @include *://www.google.com.fj/*
// @include *://www.google.fm/*
// @include *://www.google.fr/*
// @include *://www.google.ga/*
// @include *://www.google.ge/*
// @include *://www.google.gg/*
// @include *://www.google.com.gh/*
// @include *://www.google.com.gi/*
// @include *://www.google.gl/*
// @include *://www.google.gm/*
// @include *://www.google.gp/*
// @include *://www.google.gr/*
// @include *://www.google.com.gt/*
// @include *://www.google.gy/*
// @include *://www.google.com.hk/*
// @include *://www.google.hn/*
// @include *://www.google.hr/*
// @include *://www.google.ht/*
// @include *://www.google.hu/*
// @include *://www.google.co.id/*
// @include *://www.google.ie/*
// @include *://www.google.co.il/*
// @include *://www.google.im/*
// @include *://www.google.co.in/*
// @include *://www.google.iq/*
// @include *://www.google.is/*
// @include *://www.google.it/*
// @include *://www.google.je/*
// @include *://www.google.com.jm/*
// @include *://www.google.jo/*
// @include *://www.google.co.jp/*
// @include *://www.google.co.ke/*
// @include *://www.google.com.kh/*
// @include *://www.google.ki/*
// @include *://www.google.kg/*
// @include *://www.google.co.kr/*
// @include *://www.google.com.kw/*
// @include *://www.google.kz/*
// @include *://www.google.la/*
// @include *://www.google.com.lb/*
// @include *://www.google.li/*
// @include *://www.google.lk/*
// @include *://www.google.co.ls/*
// @include *://www.google.lt/*
// @include *://www.google.lu/*
// @include *://www.google.lv/*
// @include *://www.google.com.ly/*
// @include *://www.google.co.ma/*
// @include *://www.google.md/*
// @include *://www.google.me/*
// @include *://www.google.mg/*
// @include *://www.google.mk/*
// @include *://www.google.ml/*
// @include *://www.google.mn/*
// @include *://www.google.ms/*
// @include *://www.google.com.mt/*
// @include *://www.google.mu/*
// @include *://www.google.mv/*
// @include *://www.google.mw/*
// @include *://www.google.com.mx/*
// @include *://www.google.com.my/*
// @include *://www.google.co.mz/*
// @include *://www.google.com.na/*
// @include *://www.google.com.nf/*
// @include *://www.google.com.ng/*
// @include *://www.google.com.ni/*
// @include *://www.google.ne/*
// @include *://www.google.nl/*
// @include *://www.google.no/*
// @include *://www.google.com.np/*
// @include *://www.google.nr/*
// @include *://www.google.nu/*
// @include *://www.google.co.nz/*
// @include *://www.google.com.om/*
// @include *://www.google.com.pa/*
// @include *://www.google.com.pe/*
// @include *://www.google.com.ph/*
// @include *://www.google.com.pk/*
// @include *://www.google.pl/*
// @include *://www.google.pn/*
// @include *://www.google.com.pr/*
// @include *://www.google.ps/*
// @include *://www.google.pt/*
// @include *://www.google.com.py/*
// @include *://www.google.com.qa/*
// @include *://www.google.ro/*
// @include *://www.google.ru/*
// @include *://www.google.rw/*
// @include *://www.google.com.sa/*
// @include *://www.google.com.sb/*
// @include *://www.google.sc/*
// @include *://www.google.se/*
// @include *://www.google.com.sg/*
// @include *://www.google.sh/*
// @include *://www.google.si/*
// @include *://www.google.sk/*
// @include *://www.google.com.sl/*
// @include *://www.google.sn/*
// @include *://www.google.sm/*
// @include *://www.google.st/*
// @include *://www.google.com.sv/*
// @include *://www.google.td/*
// @include *://www.google.tg/*
// @include *://www.google.co.th/*
// @include *://www.google.com.tj/*
// @include *://www.google.tk/*
// @include *://www.google.tl/*
// @include *://www.google.tm/*
// @include *://www.google.tn/*
// @include *://www.google.to/*
// @include *://www.google.com.tr/*
// @include *://www.google.tt/*
// @include *://www.google.com.tw/*
// @include *://www.google.co.tz/*
// @include *://www.google.com.ua/*
// @include *://www.google.co.ug/*
// @include *://www.google.co.uk/*
// @include *://www.google.com.uy/*
// @include *://www.google.co.uz/*
// @include *://www.google.com.vc/*
// @include *://www.google.co.ve/*
// @include *://www.google.vg/*
// @include *://www.google.co.vi/*
// @include *://www.google.com.vn/*
// @include *://www.google.vu/*
// @include *://www.google.ws/*
// @include *://www.google.rs/*
// @include *://www.google.co.za/*
// @include *://www.google.co.zm/*
// @include *://www.google.co.zw/*
// ==/UserScript==

var debug = false;

cLog("Start");

//
// Parses and puts in a hash map CGI URL parameters.
//
cLog("Window href:" + window.location.href);

//debugLayer = { 'FORM3' : 1, 'PROF' : 1 };
debugLayer = { 'INJ': 1 };

var vars = null;
function getUrlVars() {
    if (!vars) {
        vars = [];
        var hash;
        var positionQ = window.location.href.indexOf('?');
        var positionS = window.location.href.indexOf('#');

        if (positionQ > 0 && positionS > 0)
            position = Math.min(positionQ, positionS);
        else if (positionQ > 0)
            position = positionQ;
        else position = positionS;

        var hashes = window.location.href.slice(position + 1).split(/&|#/);

        for (var i = 0; i < hashes.length; i++) {
            cLog('VALUE:' + hashes[i]);
            hash = hashes[i].split('=');
            //vars.push(hash[0]);
            vars[hash[0]] = hash[1];

        }
        // Possibly missing hl value is filled with languages value corresponding to TLD value
        if (!vars['hl'])
            vars['hl'] = domainsToLang[getTLD()];
        // Possibly missing lr value is filled with languages value corresponding to TLD value
        if (!vars['lr'])
            vars['lr'] = "lang_" + domainsToLang[getTLD()];
    }
    return vars;
}

function getTLD() {
    var tld = window.location.href.match(/\.(\w+?)(\/|$)/)[1];
    cLog('TLD=' + tld);
    return tld;
}
cLog('TLD:' + getTLD());

//
// Main
//


//var addPath = "/html/body/div/*/div/div[2]/div";
//var addPath = "/html/body/div/div/div[2]/div[@id='resultStats']";
//var addPath = "/html/body/div[@id='main']/div[@id='cnt']/div[@id='subform_ctrl']";
//var addSelector = '#resultStats';
var addPaths = ["//*[@id='resultStats']", "//*[@id='gbx1']"];

//
// Add Possible Injection points below
//
// Places where to try injection of Language switcher box
// var addSelectors = { "#resultStats": 1, "#slim_appbar": 1, "#extabar": 1, "#topabar": 1, ".med": 1, ".g": 1, ".appbar": 1 };
var addSelectors = [ ["#rhs > div:first-of-type", "addBeforeElem", ""], ["#appbar", "addAfterElem", "float:right;"], ];
var languageVars = ["hl", "#hl"];
var documentFormName = 'gbqf';

/*
 *   Injected Code for querying as a different language
 */
var labels = ["en-US", "it", "fr", "de", "es", "cn/hk", "jp", "ru", "nl"];
//,"nl","ru","gr","jp","tr","cn","arb","il");

var languages = ["en", "it", "fr", "de", "es", "zh-CN", "ja", "ru", "nl"];
//,"nl","ru","el","ja","tr","zh-CN","ar","iw");

var domains = ["com", "it", "fr", "de", "es", "com.hk", "co.jp", "ru", "nl"];
var tlds = ["com", "it", "fr", "de", "es", "cn", "jp", "ru", "nl"];

var wikipediaDomains = ["en", "it", "fr", "de", "es", "zh", "ja", "ru", "nl"];

var domainsToLang = [];

for (var i = 0; i < domains.length; i++) {
    domainsToLang[tlds[i]] = languages[i];
}

var injectedObjectName = 'AGB_language_switcher';

var status = new Array();

var radioOn = "checked=\"checked\"";
var radioOff = "";

for (var i = 0; i < labels.length; i++) {
    if ("lang_" + languages[i] != getUrlVars()["lr"])
        status[languages[i]] = radioOff;
    else
        status[languages[i]] = radioOn;
}

cLog("GetURlVars=" + getUrlVars());
cLog("n_lang is=" + getUrlVars()["n_lang"]);
cLog("hl is=" + getUrlVars()["hl"]);
cLog("lr is=" + getUrlVars()["lr"]);
cLog("q is=" + getUrlVars()["q"]);
cLog("Languages[n_lang] is=" + status[getUrlVars()["hl"]]);


var inject = "<div id='" + injectedObjectName + "' class='q qs'><br>Ask again on another local version: \
<table  width='100%' border='0' cellpadding='0' cellspacing='0' style='position:static'><td> \
<table border='0' cellpadding='0' cellspacing='0'> \
<tbody><tr><th scope='row' align='left'>Google:</th>";

//
// Prepare code for radio buttons to be injected in Google page results
//

var googleInjectFuncMap = {};

for (let i = 0; i < languages.length; i++) {
    googleInjectFuncMap["label_g_" + languages[i]] = function() {
        var a = window.location.href.slice(0);
        a = a.replace(/google\.(.*?)\//, 'google.' + domains[i] + '/');
        if (!a.match(/(\?|\&)hl=/)) {
            a = a + '&hl=';
        }
        if (!a.match(/(\?|\&)lr=/)) {
            a = a + '&lr=';
        }
        a = a.replace(/hl=(.*?)(&|$)/, 'hl=' + languages[i] + '$2');
        a = a.replace(/lr=(.*?)(&|$)/, 'lr=lang_' + languages[i] + '$2');
        window.location.href = a;
    }

    //var clicInject = "document."+documentFormName+".hl.value ='"+languages[i]+"'; document."+documentFormName+".submit(); ";
    cLog("Injecting " + domains[i] + " code");

    var checked = ("lang_" + languages[i] == getUrlVars()["lr"]) && (tlds[i] == getTLD() || true);

    inject += "<td nowrap valign='middle'><label id='label_g_" + languages[i] + "' for=il \
        style=\"font-size:large;\"><input id='g_" + languages[i] + "' " + (checked ? ' checked ' : '') + "type=radio name=n_lang value='" + languages[i] + "'>" + labels[i] + "</label></td>";
}
inject += "<td>&nbsp;&nbsp;&nbsp;</td>";
cLog("This is what I'm injecting:" + inject);
/*
function getClicFunction(i)
{
    return function() { document[documentFormName].hl.value = languages[i]; document[documentFormName].submit(); };
}
for (var i = 0; i < languages.length; i++)
    {
        inject.append($('<td>').attr({ 'valign' : 'middle' })
                            .append($('<input>').attr( { id : 'g_'+languages[i], type : 'radio', name : 'n_lang', value : languages[i]}
                            )
                      ));
    }
*/
//
// Adds Wikipedia radio buttons. Constructs xx.wikipedia.org for xx taken from wikipediaDomains[]
//
inject += "</tr><tr><th scope='row' align='left'>Wikipedia:</th>";
var query = getUrlVars()["q"];
for (var i = 0; i < languages.length; i++) {
    var clicWikiInject = "window.location = 'http://" + wikipediaDomains[i] + ".wikipedia.org/?search=" + query + "';";

    inject += "<td nowrap valign='middle'><input id=g_w" + languages[i] + " type=radio name=n_lang value='w" + languages[i] + "' \
                             onClick=\""+ clicWikiInject + "\"><label for=il>" + labels[i] + "</label></td>";

}
inject += "<td>&nbsp;&nbsp;&nbsp;</td></tr></tbody></table></td></table>";

tryInjection();
document.addEventListener("DOMNodeInserted", function (event) {
    var element = event.target;
    //cLog("Node INSERTED "+element.name);
    //if (element.className == 'med') {
    tryInjection();
    //}
});

var alreadyInjected = false;
function tryInjection() {
    if (alreadyInjected) return;
    var placeToInject = null;
    if (!document.getElementById(injectedObjectName)) {
        lLog("Starting injection", "INJ");

        var selector;
        var insertMethod;
        var extraStyles;
        for (var pair of addSelectors) {
            selector = pair[0];
            insertMethod = pair[1];
            extraStyles = pair[2];
            lLog("Attempting injection point: " + selector, "INJ");
            if (placeToInject = document.querySelectorAll(selector)[0]) {
                lLog("Matched Selector:" + selector, "INJ"); break;
            }
        }
        lLog("Place to inject:" + placeToInject, "INJ");
        if (placeToInject) {
            //placeToInject.innerHTML = "<td>"+inject+"</td></div>" + placeToInject.innerHTML;
            // document.querySelectorAll("#appbar").forEach(el => { el.style.zIndex = 999 });
            document.querySelectorAll(selector).forEach(el => {
                var mainElem = new DOMParser().parseFromString(inject, "text/html").body.firstChild;
                if (insertMethod === "addBeforeElem") {
                    el.before(mainElem);
                } else {
                    el.after(mainElem);
                }
                mainElem.setAttribute("style", extraStyles);
            });
            for (var labelId in googleInjectFuncMap) {
                document.getElementById(labelId).onclick = googleInjectFuncMap[labelId];
            }
            alreadyInjected = true;
        }
        lLog("Ending injection", "INJ");

    }
    else lLog("Looks like there is already a language_switcher here.", "INJ2");
}

function cLog(str) {
    if (debug) console.log(str);
}

/**
 * main logging function
 * @param str  String to be printed
 * @param level debugLayer triggering the printout. Will be printed only if level is in the currently active debugLayers
 */

function lLog(str, level) {
    //console.log("Logging:"+str);
    if (!isNaN(level))
        str += " USING NUMBERS FOR DEBUG LEVELS IS DEPRECATED - ";
    else if (level.match(/PROF/)) {
        str = this.debug.getPace() + "\n" + str;
    };
    //	console.log("mid Logging:"+this.debugLayer+" "+this.localLog);

    if (debugLayer == level || debugLayer == "ALL" || (debugLayer instanceof Object && debugLayer[level])) { cLog(str); }
    //console.log("end Logging:"+str);

};

//
// XPath evaluation wrapping function.
//
function xpath(query, node) {
    return document.evaluate(query, node, null,
        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
}

//
// Returns the first element of a given XPath query
//
function fXPath(query, node) {
    var elements = xpath(query, node);
    return elements.snapshotItem(0);
}

//
// Returns the last element of a given XPath query
//
function lXPath(query, node) {
    cLog("Asking for " + query);
    var elements = xpath(query, node);
    cLog("Elements are " + elements.snapshotLength);
    return elements.snapshotItem(elements.snapshotLength - 1);
}

//
// 0-ize NaN and non-number strings
//
function normalize(str) {
    var temp = (str != null) ? parseInt(str) : 0;
    return (isNaN(temp)) ? 0 : temp;
}