Translator for Whatsapp

Translator for Whatsapp web

目前为 2018-02-26 提交的版本。查看 最新版本

// ==UserScript==
// @name         Translator for Whatsapp
// @namespace    http://tampermonkey.net/
// @homepage     https://gf.qytechs.cn/zh-CN/scripts/28218-translator-for-whatsapp
// @version      1.3
// @description  Translator for Whatsapp web
// @author       JedLiu
// @match        https://web.whatsapp.com/*
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      translate.googleapis.com
// @require      https://code.jquery.com/jquery-latest.js
// ==/UserScript==

(function() {
    'use strict';

    //All supported languages
    //Remove // if you want to add new one
    //所有支持的预言
    //将前面的//符号移除即可添加
    var all_languages = [
        //{id:'af', name:'Afrikaans'},
        //{id:'sq', name:'Albanian'},
        //{id:'ar', name:'Arabic'},
        //{id:'hy', name:'Armenian'},
        //{id:'az', name:'Azerbaijani'},
        //{id:'eu', name:'Basque'},
        //{id:'be', name:'Belarusian'},
        //{id:'bn', name:'Bengali'},
        //{id:'bs', name:'Bosnian'},
        //{id:'bg', name:'Bulgarian'},
        //{id:'ca', name:'Catalan'},
        //{id:'ceb', name:'Cebuano'},
        //{id:'ny', name:'Chichewa'},
        //{id:'zh-CN', name:'Chinese Simplified'},
        //{id:'zh-TW', name:'Chinese Traditional'},
        //{id:'co', name:'Corsican'},
        //{id:'hr', name:'Croatian'},
        //{id:'cs', name:'Czech'},
        //{id:'da', name:'Danish'},
        //{id:'nl', name:'Dutch'},
        //{id:'en', name:'English'},
        //{id:'eo', name:'Esperanto'},
        //{id:'et', name:'Estonian'},
        //{id:'tl', name:'Filipino'},
        //{id:'fi', name:'Finnish'},
        {id:'fr', name:'French'},
        //{id:'fy', name:'Frisian'},
        //{id:'gl', name:'Galician'},
        //{id:'ka', name:'Georgian'},
        {id:'de', name:'German'},
        //{id:'el', name:'Greek'},
        //{id:'gu', name:'Gujarati'},
        //{id:'ht', name:'Haitian Creole'},
        //{id:'ha', name:'Hausa'},
        //{id:'haw', name:'Hawaiian'},
        //{id:'iw', name:'Hebrew'},
        {id:'hi', name:'Hindi'},
        //{id:'hmn', name:'Hmong'},
        //{id:'hu', name:'Hungarian'},
        //{id:'is', name:'Icelandic'},
        //{id:'ig', name:'Igbo'},
        //{id:'id', name:'Indonesian'},
        //{id:'ga', name:'Irish'},
        {id:'it', name:'Italian'},
        {id:'ja', name:'Japanese'},
        //{id:'jw', name:'Javanese'},
        //{id:'kn', name:'Kannada'},
        //{id:'kk', name:'Kazakh'},
        //{id:'km', name:'Khmer'},
        {id:'ko', name:'Korean'},
        //{id:'ku', name:'Kurdish (Kurmanji)'},
        //{id:'ky', name:'Kyrgyz'},
        //{id:'lo', name:'Lao'},
        //{id:'la', name:'Latin'},
        //{id:'lv', name:'Latvian'},
        //{id:'lt', name:'Lithuanian'},
        //{id:'lb', name:'Luxembourgish'},
        //{id:'mk', name:'Macedonian'},
        //{id:'mg', name:'Malagasy'},
        //{id:'ms', name:'Malay'},
        //{id:'ml', name:'Malayalam'},
        //{id:'mt', name:'Maltese'},
        //{id:'mi', name:'Maori'},
        //{id:'mr', name:'Marathi'},
        //{id:'mn', name:'Mongolian'},
        //{id:'my', name:'Myanmar (Burmese)'},
        //{id:'ne', name:'Nepali'},
        //{id:'no', name:'Norwegian'},
        //{id:'ps', name:'Pashto'},
        //{id:'fa', name:'Persian'},
        //{id:'pl', name:'Polish'},
        {id:'pt', name:'Portuguese'},
        //{id:'ma', name:'Punjabi'},
        //{id:'ro', name:'Romanian'},
        {id:'ru', name:'Russian'},
        //{id:'sm', name:'Samoan'},
        //{id:'gd', name:'Scots Gaelic'},
        //{id:'sr', name:'Serbian'},
        //{id:'st', name:'Sesotho'},
        //{id:'sn', name:'Shona'},
        //{id:'sd', name:'Sindhi'},
        //{id:'si', name:'Sinhala'},
        //{id:'sk', name:'Slovak'},
        //{id:'sl', name:'Slovenian'},
        //{id:'so', name:'Somali'},
        {id:'es', name:'Spanish'},
        //{id:'su', name:'Sudanese'},
        //{id:'sw', name:'Swahili'},
        //{id:'sv', name:'Swedish'},
        //{id:'tg', name:'Tajik'},
        //{id:'ta', name:'Tamil'},
        //{id:'te', name:'Telugu'},
        //{id:'th', name:'Thai'},
        //{id:'tr', name:'Turkish'},
        //{id:'uk', name:'Ukrainian'},
        //{id:'ur', name:'Urdu'},
        //{id:'uz', name:'Uzbek'},
        {id:'vi', name:'Vietnamese'},
        //{id:'cy', name:'Welsh'},
        //{id:'xh', name:'Xhosa'},
        //{id:'yi', name:'Yiddish'},
        //{id:'yo', name:'Yoruba'},
        //{id:'zu', name:'Zulu'}
    ];

    var $ = $ || window.$,
        addListenerInterval = null,
        translateInterval = null,
        translateTimeout = null,
        translate_enabled = true,
        translate_ready = false,
        translate_string = '',
        custom_style = '.language_selected{background-color: #00bfa5;}',
        image_uri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFZklEQVR42sWXe0xTVxzHv5drWXkIKBQfE61Y2DIyR3Sy+FrqRgRF1IFD54IawU03dUE2I3ukxTgqPuYjK6KOZRndnFPYJoEpcUunokNxYnFT4wOQp9A/BshD4N6zc1sKvaWV0C7xhEt/59Hf93N+5/c7uWXwlBtjMV6JeBnz5r0KhmGG/BIhxKYPtLW1Qaf7Aa30c9gA4eFTof+9KJ5l2WzaDRhakIhs+sdT6/T5C5cSFi9JaB82wHsb1iFz5/ZmJ8UFy2Jv9JdN1DoLQGwnrcXWvbsJsYsWmB574n1jan/ZpHSXAU4VFA0CKCj4FaGhCjwXGiJI9s8LY6Ehij4gog4IlLsOkPzORptQiDvEajwmJhqLYqL6AWRjJrsGIDjiOF6kah2Jzs4upKRuwxtLYxE1/3WLcD9A4Nhg5wFsE+7q1Wuoq2tAZKQSUqnUdOaGihvQao8gdcsmKBTBgwDGjJviHMBOTbo40NRhebkBWYe+wpLFMViwYD4d47F7zwF0d3cjbVtq/zprgLHjFcMH2LAhGZmagRywympos47i5s1b2PpRCm78/Q9OnSrCh6mbIZdPtBUHzxP1+AkhrgHY1nl7ewcyd+9Da2sbOjs6sGLFMsydM8ueuCkCzwaFOg9g75Lp6elBru4YSkuvwF3ijqTk1XAPDEdF3TOgmn3AZoDG1hH6kkov/QCY7RGZPBtbKs9/c/v4inbucWsfwPpkUQ5YFldVV+Pb3GMwNhuR8GYcymhSGh7wePTCDirJ2LsJRZ+Dx/vtouritJjGUu1gAGFRVfUDFBf/RqugHMHBcqxKfAsyWQDdJY/ss904V69wRVx4+No/NGz9+Z1iAMuiO3fuQvfdcURHRSIiYroo20+W+yLvuo8r4ia77pyGsQJIgiYjnVg7fdwL3Gtm8fyYHtE5mgF8HYrHvsRhpBQouQPcb4ZDEAGg4UKmGWC9APB5Oken3Czi2wtHoqJ+BD6JbsX0oMf9DgRx4bEnPmEUQXZiL4RXCv0tQFPIOIwC3f0AwPLl8cjO2l9IJxZaxP+qkZhCL2EJPp7fgmlBXaZMF8TzDb52w540h8PicIILdPezFMDKwwxaOuxXBBUfAJBIJMjSfuH12jzlmtzLHgG/XJcq3T39lJaqkDId6u3R9Qj07oXOMFlZUumttBVn3QhykzhanoDuIoNDqwgO64GTVwaLC02IQGPJLth9/5oYd1w1clyY2lsmN/Vr9RlMnT7DZM/57KGKlfqqbRNubghBWgyPT/MZXL4PHHybwENCo5IDel+IxS0ReCIA6+6t9vSfBAHCGmBhRqPqEe+nts32jDgOE0YDiUcYcPSoFk4lSIli8IGOh6FGLG4G2MU8vOgAYErCCRXv5qkWbAHiRb8qhmm6aJpbtHKLqvi2p9o6w8f6EOSs5XGvCbh011xJnu7AshkMzlQQaAo4kbjw0N1TgN32AdIOnJHPnDmbniAm2Zvvpf5+vsbQMjU7XTObR0IEUFHTV8Z96wJ9gNFeQPxBDo+6xFeyANB0yQGAj18AdmQVyuWKsCdCHCsFGlpo8q2jZfUvkPK9OOGm0W/uXcli32kOP5Vxomqg4acAe+DwR8BQEMIR5JURzJhMoF4KaM8S5F8Vh9mNes/bzMLYRrD2aI8Ijoafaf7zCQAWCNX+fPm4oODVljEvb19lpZFRCuI9nKOrVpxw9ubp7ocGsNdSc2pVl2t91a6IC30q7hzAjK0177NSvy9dEafNSBNQZizdO3wARdzXXv5hy36kzqKFY3ZGnPDc+qoTS/I6G8qGD8C4SeAfFg/WY5TpzZ2Y/1l+qgzYZKAcbd+2O+pK0dVkMPsbLsD/3f4DRTYAbJ65vloAAAAASUVORK5CYII=',
        custom_html = '<div class="block-compose tranlate-bottom"><div tabindex="-1" class="input-container" style="padding-top:0px;padding-bottom:0px;padding-left:0px;"><img alt="Translator" draggable="false" src="' + image_uri + '" style="width:30px;height:30px;padding-left:15px;padding-right:30px;float:left"><div class="input" dir="auto">Hola</div></div></div>',
        source_language = 'en',
        dest_language = 'es',
        html_language1 = '<div class="menu-item" style="display:table"><img alt="Translator" draggable="false" src="data:'+ image_uri +'" style="width:25px;height:25px;"/></div>',
        username = '',
        is_debug = true;


    //For menu html
    html_language1 = html_language1 + '<select class="languageSelect" style="padding-right:5px;width: 120px; text-align-last:center;"><option value="off">OFF</option>';
    for(var i=0;i<all_languages.length;i++){
        html_language1 = html_language1 + '<option value="'+ all_languages[i].id +'">' + all_languages[i].name +'</option>';
    }
    html_language1 = html_language1 + "</select>";

    //Add style
    var customStyleNode = document.createElement('style');
    customStyleNode.textContent = custom_style;
    document.querySelector('head').appendChild(customStyleNode);

    //Replace all function
    function replaceAll(str, find, replace) {
        return str.replace(new RegExp(find, 'g'), replace);
    }

    //Show debug
    var debugMessage = function(mes){
        if(is_debug)
            console.info(mes);
    };

    //Show error message
    var showError = function(err){
        alert(err);
        console.error(err);
    };

    //Send translated content
    //updated 2018.02.11
    var dispatchTranslateChange = function(){
        var isEmojiVisible = false;
        //If emoji is visible
        if($('.emojik').length === 0 ){
            $('._1fkhx').hide();
            $('footer button:first').click();
        }	else {
            isEmojiVisible = true;
        }
        //Add empty emoji
        $('.emojik:first').append('<span data-unicode=" " tabindex="-1" data-emoji-index="0" class="emojik" id="emptyEmojik"></span>');
        //Click to send
        $("#emptyEmojik").trigger("click");
        if(!isEmojiVisible){
            $('footer button:first').click();
            setTimeout(function(){
                $('._1fkhx').removeAttr("style");
                $('footer').find('[data-icon=send]').click();
            },500);
        }
    };

    //Do translation
    //sl - source language
    //dl - target language
    //txt - content to be translated
    //cb - callback after translation
    var translate = function(sl,dl,txt,cb){
        debugMessage('Source:'+ txt);
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://translate.googleapis.com/translate_a/single?client=gtx&sl="+ sl + "&tl=" + dl +"&dt=t&q=" + encodeURI(txt),
            onload: function(response) {
                //replace the \n
                var _r_text = replaceAll(response.responseText, '\n"', '"');
                var _r = eval(_r_text);
                translate_string = '';
                for(var i=0; i<_r[0].length;i++){
                    translate_string += _r[0][i][0];
                }
                debugMessage('Translation:'+translate_string);
                cb.apply({text: translate_string});
                //translate_ready = true;
            }
        });
    };

    //Bind to get user input
    var onDomSubtreeModified = function(){
        var $_translate_input_1 = $('.tranlate-bottom').find('.input');
        $_translate_input_1.html('Typing...');
        translate_ready = false;
        var _this = $(this);
        delay(function(){
            var _input = $.trim(_this.text());
            if(_input){
                translate(source_language, dest_language, _input, function(){
                    $_translate_input_1.html(this.text);
                    translate_ready = true;
                });
            }else{
                $_translate_input_1.html('');
            }
        }, 1000);
    };

    //Bind to send the translated content
    var onEnterKeyPressed = function( event ) {
        if (event.which == 13 && translate_enabled) {
            debugMessage('Waiting translation');
            event.preventDefault();
            var _this = $(this);
            translateInterval = setInterval(function(){
                if(translate_ready){
                    debugMessage('Now sending message:'+translate_string);
                    _this.html(translate_string);
                    dispatchTranslateChange();
                    translate_string = '';
                    translate_ready = false;
                    debugMessage('Message sent');
                    clearInterval(translateInterval);
                }
            }, 100);
        }
    };

    //refocus on the input box
    var refocus = function(){
        var $_i = $('div.input[contenteditable]');
        var _tt = $_i.html();
        if(_tt !== ''){
            $_i.html(_tt).focus();
        }
    };

    //Add translation bindings
    var addTranslateFunc = function(l){
        if(!username){
            showError('Can not get the username');
            return;
        }
        var _language = GM_getValue(username);
        if(l){
            _language = l;
        }else if(!_language){
            _language = 'off';
        }

        //Menu
        debugMessage('Set language to:' + _language);
        $('.languageSelect').val(_language);
        dest_language = _language;
        GM_setValue(username, _language);

        //Add translation input
        if(_language !== 'off' && $('.tranlate-bottom').length === 0){
            //$('.pane-chat-footer').append($(custom_html));
            //fixed:2018.01.23
            $('footer').append($(custom_html));

            //binding
            //updated:2017.09.29
            //$('div.input[contenteditable]')
            //var $_input_body = $('div.pluggable-input-body');

            //updated:2018.02.26
            var $_input_body = $('footer div.copyable-text.selectable-text');
            if($_input_body === null || $_input_body.length !== 1){
                showError('Error binding for Whatsapp translator plugin!');
            }
            $_input_body.on('DOMSubtreeModified', onDomSubtreeModified)
                .on('keydown', onEnterKeyPressed);

            //translate sent or received messages
            //fixed:2017.08.31 Trigger translation for search result
            //fixed:2018.01.23 Fixed for whatsapp update
            $('#main').on('click', '.selectable-text', function(){
                var $_t_this = $(this);
                translate('auto', source_language, $(this).text(), function(){
                    $_t_this.html(this.text);
                    refocus();
                });
            });
            $('#main').on('click', function(){
                refocus();
            });
        }else if(_language === 'off' && $('.tranlate-bottom').length !== 0){
            //remove bindings
            $('.tranlate-bottom').remove();
            $('div.input[contenteditable]')
                .off('DOMSubtreeModified', onDomSubtreeModified)
                .off('keydown', onEnterKeyPressed);
        }
    };

    //Add listener when user activates a new chat
    addListenerInterval = setInterval(function(){
        var $_div_chat = $('#pane-side');
        if($_div_chat.length){
            //updated 2018.02.11
            $('#pane-side').on('click','div._2wP_Y', function(){

                //Get the username
                //username = escape($(this).find('.chat-title').text());
                console.info($(this));
                var _tusername = '';
                $(this).find('span').each(function(i,x){
                    if(x.hasAttribute('title')) {
                        console.info(x.title);
                        _tusername = x.title;
                        return false;
                    }
                });
                if(_tusername !== '')
                    username = escape(_tusername);
                else
                    showError('Not able to get the user name');

                debugMessage('chat menu clicked');

                //Return if the translation input is added
                if($('.languageSelect').length>0)
                    return;

                var $header = $('#main header div:first').next();
                if($header.length != 1)
                    showError('Not able to insert translate menu');
                $header.after($(html_language1));

                $('.languageSelect').on('change', function(){
                    addTranslateFunc($('.languageSelect').val());
                });

                //Apply the translate function
                addTranslateFunc();
            });

            clearInterval(addListenerInterval);
        }
    }, 1000);

    //Delay function
    var delay = (function(){
        var timer = 0;
        return function(callback, ms){
            clearTimeout (timer);
            timer = setTimeout(callback, ms);
        };
    })();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址