Hebrew ToolTip Translation

By Eyal Soha Last update Sep 1, 2009 — Installed 3,131 times. Daily Installs: 2, 0, 0, 1, 5, 0, 0, 0, 2, 1, 0, 0, 1, 5, 1, 0, 0, 1, 0, 3, 3, 0, 2, 2, 9, 0, 1, 2, 1, 1, 0

There are 8 previous versions of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name                        Hebrew ToolTip Translation
// @namespace eyal0
// @description         Translate words on a webpage from Hebrew to English and vice-versa in little tooltips
// @include                     *
// ==/UserScript==

window.addEventListener("load",
  (function() {
    var HTTtooltipDelay; //milliseconds
    var HTTtimeoutID;
    var HTTparent;
    var HTToffset;
    var HTTcurX;
    var HTTcurY;
    var HTTtooltip;
    var HTTdefinitions = ""; //translation output
    var align_left;
    var align_top;
    var keep_on_screen;
    var trigger_hover;
    var trigger_click;
    var trigger_highlight;
    var trigger_keyboard;
    var HTTtooltipCharacter;
    
    function appendChild(child,parent){return(parent.insertBefore(child,parent.lastChild.nextSibling));}
    function $(id){return document.getElementById(id);}
    function onClick(id,func){$(id).addEventListener('click',func,false);}
    function onChange(id,func){$(id).addEventListener('change',func,false);}

    function showConfig() {
      showPopup(
        '<div id="HTTConfig" class="HTTPopup"><div style="text-align:center;"><span class="HTTImportant">Configure <a href="http://userscripts.org/scripts/show/6773">Hebrew Tooltip Translation</a></span></div><br />'+
        'All changes are saved immediately, but some changes might not take effect in tabs that are already open<br /><br />'+
        '<span class="HTTHeader">Tooltip Trigger</span><br />'+
        ' &nbsp; &nbsp; <input type="checkbox" id="HTTTriggerHover"/><label for="HTTTriggerHover">Show Tooltip after </label><input type="text" id="HTTTriggerHoverDelay"/><label for="HTTTriggerHover"> milliseconds</label><br />'+
        ' &nbsp; &nbsp; <input type="checkbox" id="HTTTriggerClick"/><label for="HTTTriggerClick">Show Tooltip after clicking on a word</label><br />'+
        ' &nbsp; &nbsp; <input type="checkbox" id="HTTTriggerHighlight"/><label for="HTTTriggerHighlight">Show Tooltip after selecting a word or phrase</label><br />'+
        ' &nbsp; &nbsp; <input type="checkbox" id="HTTTriggerKeyboard"/><label for="HTTTriggerKeyboard">Show Tooltip after pressing </label><input type="text" maxlength=1 id="HTTTriggerKeyboardCharacter"/><label for="HTTTriggerKeyboard"> on the keyboard</label><br />'+
        '<span class="HTTHeader">Tooltip Location</span><br />'+
        ' &nbsp; &nbsp; <input type="radio" name="HTTLocationX" id="HTTLocationX1" value="1" /><label for="HTTLocationX1">To the right</label>'+
                     '  <input type="radio" name="HTTLocationX" id="HTTLocationX0" value="0" /><label for="HTTLocationX0">To the left</label><br />'+
        ' &nbsp; &nbsp; <input type="radio" name="HTTLocationY" id="HTTLocationY1" value="1" /><label for="HTTLocationY1">Below</label>' +
                     '  <input type="radio" name="HTTLocationY" id="HTTLocationY0" value="0" /><label for="HTTLocationY0">Above</label><br />'+
        ' &nbsp; &nbsp; <input type="checkbox" id="HTTKeepOnScreen"/><label for="HTTKeepOnScreen">Keep Tooltips on screen</label><br />'+
        '<hr /><div style="text-align:right;"><input type="button" id="HTTCloseConfig" value="Close" /></div>'+
        '</div>', true
      );
      onClick('HTTCloseConfig', function() { hidePopup(); });
      if($('HTTLocationX' + align_left))
        $('HTTLocationX' + align_left).checked='checked';
      if($('HTTLocationY' + align_top))
        $('HTTLocationY' + align_top).checked='checked';
      $('HTTKeepOnScreen').checked=keep_on_screen;
      $('HTTTriggerHover').checked=trigger_hover;
      $('HTTTriggerHoverDelay').value=HTTtooltipDelay;
      $('HTTTriggerClick').checked=trigger_click;
      $('HTTTriggerHighlight').checked=trigger_highlight;
      $('HTTTriggerKeyboard').checked=trigger_keyboard;
      $('HTTTriggerKeyboardCharacter').value=HTTtooltipCharacter;
      for(var i = 0; i < 2; i++) {
        onClick('HTTLocationX' + i, (function(i) {return(function(e) { align_left = i; GM_setValue("align_left", i); e.target.blur(); })})(i));
        onClick('HTTLocationY' + i, (function(i) {return(function(e) { align_top = i;  GM_setValue("align_top", i);  e.target.blur(); })})(i));
      }
      onClick('HTTKeepOnScreen', function(e) { keep_on_screen = e.target.checked; GM_setValue("keep_on_screen", keep_on_screen); e.target.blur(); });
      onClick('HTTTriggerHover', function(e) {
        trigger_hover = e.target.checked;
        GM_setValue("trigger_hover", trigger_hover);
        if(trigger_hover) {
          window.addEventListener("mousemove", HTTmousemove, false);
          window.addEventListener("DOMMouseScroll", HTTmousescroll, false);
          window.addEventListener("click", HTTclick, false);
        } else {
          if(!trigger_hover && !trigger_keyboard)
            window.removeEventListener("mousemove", HTTmousemove, false);
          window.removeEventListener("DOMMouseScroll", HTTmousescroll, false);
          if(!trigger_hover && !trigger_click && !trigger_highlight && !trigger_keyboard)
            window.removeEventListener("click", HTTclick, false);
          HTThide(true); //true to force it
        }
        e.target.blur(); 
      });
      onChange('HTTTriggerHoverDelay', function(e) {
        HTTtooltipDelay = e.target.value;
        GM_setValue("HTTtooltipDelay", HTTtooltipDelay);
        return(1);
      });
      onClick('HTTTriggerClick', function(e) {
        trigger_click = e.target.checked;
        GM_setValue("trigger_click", trigger_click);
        if(trigger_click) {
          window.addEventListener("click", HTTclick, false);
        } else {
          if(!trigger_hover && !trigger_click && !trigger_highlight && !trigger_keyboard)
            window.removeEventListener("click", HTTclick, false);
          HTThide(true); //true to force it
        }
        e.target.blur(); 
      });
      onClick('HTTTriggerHighlight', function(e) {
        trigger_highlight = e.target.checked;
        GM_setValue("trigger_highlight", trigger_highlight);
        if(trigger_highlight) {
          window.addEventListener("mouseup", HTTmouseup, false);
          window.addEventListener("click", HTTclick, false);
        } else {
          window.removeEventListener("mouseup", HTTmouseup, false);
          if(!trigger_hover && !trigger_click && !trigger_highlight && !trigger_keyboard)
            window.removeEventListener("click", HTTclick, false);
          HTThide(true); //true to force it
        }
        e.target.blur(); 
      });
      onClick('HTTTriggerKeyboard', function(e) {
        trigger_keyboard = e.target.checked;
        GM_setValue("trigger_keyboard", trigger_keyboard);
        if(trigger_keyboard) {
          window.addEventListener("keypress", HTTkeypress, false);
          window.addEventListener("click", HTTclick, false);
          window.addEventListener("mousemove", HTTmousemove, false);
        } else {
          window.removeEventListener("keypress", HTTkeypress, false);
          if(!trigger_hover && !trigger_click && !trigger_highlight && !trigger_keyboard)
            window.removeEventListener("click", HTTclick, false);
          if(!trigger_hover && !trigger_keyboard)
            window.removeEventListener("mousemove", HTTmousemove, false);
          HTThide(true); //true to force it
        }
        e.target.blur(); 
      });
      onChange('HTTTriggerKeyboardCharacter', function(e) {
        HTTtooltipCharacter = e.target.value;
        GM_setValue("HTTtooltipCharacter", HTTtooltipCharacter);
        return(1);
      });
    };

    function showPopup(content,onTop) {
      $('HTTPopupContainer').innerHTML = content;
      if (onTop) {
        $('HTTShadow').style.zIndex = '1000';
        $('HTTPopupContainer').style.zIndex = '1001';
      } else {
        $('HTTShadow').style.zIndex = '1';
        $('HTTPopupContainer').style.zIndex = '2';
      }
      $('HTTShadow').style.display = 'block';
      $('HTTPopupContainer').style.display = 'block';
      window.scroll(0,0);
    }

  // Hide popups created with showPopup()
    function hidePopup() {
      if ($('HTTPopupContainer')) {
        $('HTTPopupContainer').style.display = 'none';
        $('HTTShadow').style.display = 'none';
      }
    }

    function HTTshowToolTip() {
      if(HTTdefinitions.length) {
        var minX, minY, maxX, maxY, ttX, ttY;

        HTTtooltip.style.width = "auto";
        HTTtooltip.style.height = "auto";
        HTTtooltip.innerHTML = HTTdefinitions;
        HTTtooltip.firstChild.style.marginTop = "0";
        HTTtooltip.firstChild.style.marginRight = "0";
        HTTtooltip.firstChild.style.marginBottom = "0";
        HTTtooltip.firstChild.style.marginLeft = "0";
        if(align_left) {
          ttX = HTTcurX + 10;
        } else {
          ttX = HTTcurX - HTTtooltip.scrollWidth - 10;
        }
        if(align_top) {
          ttY = HTTcurY + 10;
        } else {
          ttY = HTTcurY - HTTtooltip.scrollHeight - 10;
        }
        minX = document.documentElement.scrollLeft;
        minY = document.documentElement.scrollTop;
        maxX = minX + document.documentElement.clientWidth - HTTtooltip.scrollWidth;
        maxY = minY + document.documentElement.clientHeight - HTTtooltip.scrollHeight;
        //GM_log(document.documentElement.clientWidth + "," + HTTtooltip.scrollWidth + "," + minX + "," + minY + "," + maxX + "," + maxY);

        if(keep_on_screen) {
          if(ttX < minX)
            ttX = minX;
          else if(ttX > maxX)
            ttX = maxX;
          if(ttY < minY)
            ttY = minY;
          else if(ttY > maxY)
            ttY = maxY;
        }
        HTTtooltip.style.left = ttX + "px";
        HTTtooltip.style.top = ttY + "px";

        HTTtooltip.style.visibility="visible";
      }
    }

    function HTTparseResponse(responseDetails) {
      /*var bodyStart = responseDetails.responseText.indexOf("<body");
      var bodyEnd = responseDetails.responseText.indexOf("body>")+5;
      responseDetails.responseText = responseDetails.responseText.substring(bodyStart,bodyEnd);
      GM_log(responseDetails.responseText);
      var dom = new XML(responseDetails.responseText);
      var definition;
      HTToutput = "";*/
      var i;
      var startText;
      var endPos;
      var rtl;

      /*for(i=0; i<responseDetails.responseText.length; i+=40){
        GM_log(responseDetails.responseText.substring(i,i+40));
      }*/
      //GM_log(responseDetails.responseText);
      i=1;
      startText = responseDetails.responseText.indexOf("imgWord" + i);
      //GM_log("here1:" + startText);
      if(startText != -1) {
        //we've got something
        startText = responseDetails.responseText.indexOf("TableTranslation");
        startText = responseDetails.responseText.indexOf("style=", startText);
        endText = responseDetails.responseText.indexOf(";", startText) + 2;
        if(responseDetails.responseText.substring(startText, endText).indexOf("rtl") != -1)
          rtl = true; //search of a Hebrew word
        else
          rtl = false; //search of an English word
        HTTdefinitions = "";
        HTTdefinitions += "<table class='HTT " + (rtl?"HTTHebrew":"HTTEnglish") + "' dir=\"\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" background-color=white>";
        //GM_log("here2");
        startText = responseDetails.responseText.indexOf("imgWord" + i);
        //GM_log(HTTdefinitions);
        while(startText != -1) {
          //first the original word
          startText = responseDetails.responseText.indexOf("<span", startText);
          startText = responseDetails.responseText.indexOf(">", startText) + 1;
          endText = responseDetails.responseText.indexOf("</span>", startText);
          HTTdefinitions += "<tr><td class='HTT HTTWord " + (rtl?"HTTHebrew":"HTTEnglish") + "'>" + responseDetails.responseText.substring(startText, endText) + "</td>";
          //GM_log("orig: " + startText + "," + endText + " " + responseDetails.responseText.substring(startText, endText));

          //now the part of speech
          startText = responseDetails.responseText.indexOf("<span", endText);
          startText = responseDetails.responseText.indexOf(">", startText) + 1;
          endText = responseDetails.responseText.indexOf("</span>", startText);
          HTTdefinitions += "<td class='HTT HTTPartOfSpeech " + (rtl?"HTTHebrew":"HTTEnglish") + "'>" + responseDetails.responseText.substring(startText, endText) + "</td>";
          //GM_log("POS: " + startText + "," + endText + " " + responseDetails.responseText.substring(startText, endText));

          //finally the definition
          if(rtl)
            startText = responseDetails.responseText.indexOf('<span style=" height', endText);
          else
            startText = responseDetails.responseText.indexOf("<span dir", endText);
          startText = responseDetails.responseText.indexOf(">", startText) + 1;
          HTTdefinitions += "<td class='HTT HTTDefinition " + (!rtl?"HTTHebrew":"HTTEnglish") + "'>";
          for(var spanCount = 1; spanCount > 0;) {
            endText = responseDetails.responseText.indexOf("<", startText);
            if(responseDetails.responseText.indexOf("<", endText) < responseDetails.responseText.indexOf("</span>", endText)) {
              if(responseDetails.responseText.substr(endText, 5) == "<span") {
                spanCount++;
              }
            } else {
              spanCount--;
            }
            //GM_log("Adding " + startText + " " + endText + " " +responseDetails.responseText.substring(startText, endText));
            HTTdefinitions += responseDetails.responseText.substring(startText, endText);
            startText = responseDetails.responseText.indexOf(">", endText) + 1;
          }
          HTTdefinitions += "</td></tr>";
          //GM_log(responseDetails.responseText.substring(startText, endText));

          i++; //next
          startText = responseDetails.responseText.indexOf("imgWord" + i);
          //if(startText != -1)
            //HTTdefinitions += "<br>";
        }
      }
      //GM_log(HTTdefinitions);
      HTTshowToolTip();
    }

    function HTTtranslateWord(input) {
      var HTTreq;

      //GM_log("url: " + 'http://milon.morfix.co.il/default.aspx?q=' + escape(input));
      GM_xmlhttpRequest({
        method: 'GET',
        url: 'http://milon.morfix.co.il/default.aspx?q=' + escape(input),
        headers: {
          'User-agent': 'Mozilla/4.0 (compatible)',
          'Accept': 'application/atom+xml,application/xml,text/xml',
        },
        onload: HTTparseResponse
      });
    }

    function HTTgetWord() {
      //GM_log("i am " + HTTparent);

      //GM_log("i am attributes " + HTTparent.className);
      //GM_log("tag name is " + HTTparent.tagName);
      var range = HTTparent.ownerDocument.createRange();
      range.selectNode(HTTparent);
      var str = range.toString();
      range.detach();

      if(HTToffset < 0 || HTToffset >= str.length)
        return null;
      var start = HTToffset;
      var end = HTToffset + 1;
      var valid_word = /^((\w)+|([\u0590-\u05ff\"\']+))$/;
      //GM_log(escape(str.substring(start, start + 1)));
      if(!valid_word.test(str.substring(start, end)))
        return null;
      while(start > 0 && valid_word.test(str.substring(start - 1, end)))
        start--;
      while(end < str.length && valid_word.test(str.substring(start, end+1)))
        end++;
      var text = str.substring(start, end);
      //GM_log("try to translate " + text);
      HTTtranslateWord(text);
    }

    function HTTmousescroll(event) {
      HTTmousemove(event);
    }

    function HTThide(force) {
      if(HTTtimeoutID || force) {
        HTTdefinitions = "";
        HTTtooltip.style.visibility = "hidden";
        HTTtooltip.innerHTML = HTTdefinitions;
        HTTtooltip.style.left = 0 + "px";
        HTTtooltip.style.top = 0 + "px";
        HTTtooltip.style.width = 0 + "px";
        HTTtooltip.style.height = 0 + "px";
        window.clearTimeout(HTTtimeoutID);
        HTTtimeoutID = 0;
      }
    }

    function HTTkeypress(event) {
      var e = event;
      
      //GM_log("got keypress");
      HTThide(true); //hide the old one if there is one

      var keynum;
      var keychar;
      var numcheck;
      var text;

      if(window.event) // IE
        keynum = e.keyCode;
      else if(e.which) // Netscape/Firefox/Opera
        keynum = e.which;

      keychar = String.fromCharCode(keynum);
      //GM_log("keychar is " + keychar);
      if(keychar == HTTtooltipCharacter) {
        //GM_log("match");
        if(window.getSelection() != '') {
          //GM_log("translating phrase " + window.getSelection());
          HTTtranslateWord(window.getSelection());
        } else {
          HTTgetWord(); //get the word under the cursor right now and translate it
        }
      }
      return;
    }

    function HTTmouseup(event) {
      var e = event;
      
      if(window.getSelection() == '')
        return; //nothing to do
      //GM_log("got mouseup " + window.getSelection());
      HTThide(true); //hide the old one if there is one

      //variables for use in displaying the translation
      HTTcurX=e.pageX;
      HTTcurY=e.pageY;
      //GM_log("try to translate " + window.getSelection());
      HTTtranslateWord(window.getSelection());
      return;
    }

    function HTTclick(event) {
      var e = event;
      
      //click is used by many handlers, mostly to hide the tooltip
      //variables for use in displaying the translation
      HTTcurX=e.pageX;
      HTTcurY=e.pageY;
      //GM_log("got click");
      HTThide(true); //hide the old one if there is one
      if(!trigger_click)
        return; //just hide and finished

      //don't bother trying if the location is no good.
      try {
        if(!e || !e.rangeParent || e.rangeParent.nodeType != e.rangeParent.TEXT_NODE
           || e.rangeParent.parentNode != e.target)
          return;
      }
      catch(e) {
        return; //I don't know how to handle the permission denied thing
      }
      //variables for use in finding the word
      HTTparent = event.rangeParent;
      HTToffset = event.rangeOffset;
      HTTgetWord(); //get the word under the cursor right now
      return;
    }
    
    function HTTmousemove(event) {
      //test out the tooltip
      var e = event;
      //every movement of the mouse restarts the timer and removes the tooltip
      HTThide();
      //don't bother starting a new timer if the location is no good.
      try {
        if(!e || !e.rangeParent || e.rangeParent.nodeType != e.rangeParent.TEXT_NODE
           || e.rangeParent.parentNode != e.target)
          return;
      }
      catch(e) {
        return; //I don't know how to handle the permission denied thing
      }

      //variables for use in finding the word
      HTTparent = event.rangeParent;
      HTToffset = event.rangeOffset;
      //variables for use in displaying the translation
      HTTcurX=e.pageX;
      HTTcurY=e.pageY;
      if(!trigger_hover)
        return; //only used this to get the last_mouse_event (location)
      HTTtimeoutID = window.setTimeout(HTTgetWord, HTTtooltipDelay);
      return;
    }

    // Initialize the script
    function HTTinit () {
      GM_addStyle(
      '.HTTPopup { background:#f6f6f6; border:3px double #666666; }'+
      '.HTTPopupContainer { display:none; position:absolute; top:0; right:0; bottom:0; left:0; }'+
      '#HTTConfig { width:700px; padding:10px; margin:20px auto 0; }'+
      '#HTTConfig label { color:#666666; font-weight:normal; } '+
      '#HTTConfig .HTTHeader { font-weight:bold; }'+
      '#HTTShadow { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:black; opacity:0.6; }'+
      '.HTT { background: none; border-spacing: 0; border:0; padding:0;}' +
      '.HTTImportant { font-weight:bold; }' +
      '.HTTHebrew { direction:rtl; text-align:right; font-family:David; }' +
      '.HTTEnglish { direction:ltr; text-align:left; font-family:Arial; }' +
      '.HTTWord { font-size:11pt; color:#000099; }' +
      '.HTTPartOfSpeech { font-size:7pt; color:black; font-family:Arial; }' + //always Arial, to keep it readable when small
      '.HTTDefinition { font-size:11pt; color:#000099; }' +
      '.HTTtooltip { border: 1px solid black; visibility: hidden; top: 0px; left: 0px; position: absolute; background-color: lightyellow; z-index: 10000; width: 0px; height: 0px; }'
      );

      var popupDiv = document.createElement('div');
      popupDiv.id = 'HTTPopupContainer';
      popupDiv.className = 'HTTPopupContainer';
      appendChild(popupDiv, document.body);
      var shadowDiv = document.createElement('div');
      shadowDiv.id = 'HTTShadow';
      appendChild(shadowDiv, document.body);

      //here is where we set the initial values that we read from the config
      if(GM_getValue) {
        align_top = GM_getValue("align_top", 1);
        align_left = GM_getValue("align_left", 1);
        keep_on_screen = GM_getValue("keep_on_screen", 1);
        trigger_hover = GM_getValue("trigger_hover", 1);
        HTTtooltipDelay = GM_getValue("HTTtooltipDelay", 1000);
        trigger_click = GM_getValue("trigger_click", 0);
        trigger_highlight = GM_getValue("trigger_highlight", 0);
        trigger_keyboard = GM_getValue("trigger_keyboard", 0);
        HTTtooltipCharacter = GM_getValue("HTTtooltipCharacter", 'T');
      } else {
        align_top = 1;
        align_left = 1;
        keep_on_screen = 1;
        trigger_hover = 1;
        HTTtooltipDelay = 1000;
        trigger_click = 0;
        trigger_highlight = 0;
        trigger_keyboard = 0;
        HTTtooltipCharacter = 'T';
      }
      if(GM_registerMenuCommand) {
        GM_registerMenuCommand("Configure Hebrew ToolTip Translation", showConfig);
      }

      var element;

      if(trigger_hover) {
        window.addEventListener("mousemove", HTTmousemove, false);
        window.addEventListener("DOMMouseScroll", HTTmousescroll, false);
        window.addEventListener("click", HTTclick, false);
      }
      if(trigger_click) {
        window.addEventListener("click", HTTclick, false);
      }
      if(trigger_highlight) {
        window.addEventListener("mouseup", HTTmouseup, false);
        window.addEventListener("click", HTTclick, false);
      }
      if(trigger_keyboard) {
        window.addEventListener("keypress", HTTkeypress, false);
        window.addEventListener("click", HTTclick, false);
        window.addEventListener("mousemove", HTTmousemove, false);
      }
      HTTtooltip = document.createElement("div");
      element = document.lastChild;

      HTTtooltip = appendChild(HTTtooltip, element);
      HTTtooltip.className = "HTT HTTtooltip"; //for use by users that might do things with stylish
      //window.addEventListener("mouseup", function(e) { alert(window.getSelection()); /*HTTtranslateWord(window.getSelection());*/ }, false);
    }
    HTTinit();
  }),false);