Translator for Whatsapp

Translator for Whatsapp web

اعتبارا من 26-02-2018. شاهد أحدث إصدار.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         Translator for Whatsapp
// @namespace    http://tampermonkey.net/
// @homepage     https://greasyfork.org/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);
        };
    })();
})();