Translator for Whatsapp

Translator for Whatsapp web

当前为 2017-03-20 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Translator for Whatsapp
// @namespace    http://tampermonkey.net/
// @homepage     https://greasyfork.org/zh-CN/scripts/28218-translator-for-whatsapp
// @version      0.2
// @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;} .language_menu a:hover{background-color: #f4f5f5;}',
        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 style="padding-bottom:6px;padding-right:10px;color: green;"><img alt="Translator" draggable="false" src="' + image_uri + '" style="width:30px;height:30px;"></div><div tabindex="-1" class="input-container"><div class="input" dir="auto"></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;" /><span style="padding-left:10px; display:table-cell; vertical-align:middle;" id="destination_language_label"></span><span><div tabindex="-1" class="dropdown dropdown-right" style="transform-origin: right top 0px; transform: scale(1); opacity: 1; display:none" id="translate_menu"><ul> '+
        '<li tabindex="-1" class="menu-item menu-shortcut language_menu language_selected" style="opacity: 1; transform: translateY(0px);" data-language="off"><a class="ellipsify" title="Turn off">Off</a></li> ',
        username = '',
        is_debug = true;


    //For menu html
    for(var i=0;i<all_languages.length;i++){
        html_language1 = html_language1 +'<li tabindex="-1" class="menu-item menu-shortcut language_menu" style="opacity: 1; transform: translateY(0px);" data-language="' + all_languages[i].id + '"><a class="ellipsify" title="French">' + all_languages[i].name + '</a></li>';
    }
    html_language1= html_language1 + '</ul></div></span></div>';

    //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);
    };

    //Send translated content
    var dispatchTranslateChange = function(){
        var isEmojiVisible = false;
        //If emoji is visible
        if($('.compose-box-items-overlay-container div:first').children().length === 0 ){
            $('.compose-box-items-overlay-container').hide();
            $('.btn-emoji').click();
        }	else {
            isEmojiVisible = true;
        }
        //Add empty emoji
        $('.emoji-panel-body div div').append('<span data-unicode=" " tabindex="-1" data-emoji-index="0" class="emojik" id="emptyEmojik"></span>');
        //Click to send
        $("#emptyEmojik").trigger("click");
        if(!isEmojiVisible){
            $('.btn-emoji').click();
            setTimeout(function(){
                $('.compose-box-items-overlay-container').removeAttr("style");
                $('.send-container').trigger('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){
                    _this.html(translate_string);
                    dispatchTranslateChange();
                    translate_string = '';
                    translate_ready = false;
                    debugMessage('Message sent');
                    clearInterval(translateInterval);
                }
            }, 100);
        }
    };

    //Add translation bindings
    var addTranslateFunc = function(l){
        if(!username){
            alert('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);
        $('.language_menu.language_selected').removeClass('language_selected');
        $('.language_menu[data-language="'+ _language +'"]').addClass('language_selected');
        $('#destination_language_label').html($('.language_selected').text());
        dest_language = _language;
        GM_setValue(username, _language);

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

            //binding
            $('div.input[contenteditable]')
                .on('DOMSubtreeModified', onDomSubtreeModified)
                .on('keydown', onEnterKeyPressed);

            //translate sent or received messages
            $('.message-list').on('click', '.selectable-text', function(){
                var $_t_this = $(this);
                translate('auto', source_language, $(this).text(), function(){
                    $_t_this.html(this.text);
                });
            });
        }else if(_language === 'off' && $('.tranlate-bottom').length !== 0){
            //remove bindings
            $('.tranlate-bottom').remove();
            $('div.input[contenteditable]')
                .off('DOMSubtreeModified', onDomSubtreeModified)
                .off('keydown', onEnterKeyPressed);
        }
    };

    //Add linster when user activates a new chat
    addListenerInterval = setInterval(function(){
        var $_div_chat = $('div.chat');
        if($_div_chat.length){
            $_div_chat.on('click', function(){

                //Get the username
                username = escape($(this).find('.chat-title').text());

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

                //Add translation input
                var _menu = $(html_language1).on('click', function(){
                    $(this).find('div').slideToggle();
                });
                _menu.find('.language_menu').on('click', function(){
                    addTranslateFunc($(this).data('language'));
                });
                $('.pane-chat-controls div:first').prepend(_menu);

                //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);
        };
    })();
})();