Translator

If you look to the right side of your screen, you will see a small tab labelled "Translator". Clicking this opens a compact tool designed to help you hear exactly how words on this page sound in different languages.


It is a very straightforward setup. Simply select your target language from the dropdown menu, and the panel will slide neatly out of the way. From there, just hover your mouse pointer over any word on the page, or tap a word if you are using a mobile phone. The widget will seamlessly translate that specific word and read it aloud using the native voice of your chosen language.


This was built as a fun, interactive tool to help with learning foreign pronunciations. Whether you are brushing up on your Spanish or just curious about how a specific term sounds in Japanese, it is a handy little feature to play around with as you read through posts. To turn the audio off, just open the tab again and select the top option to disable the reader.

Translate & Speak

Translator
<style>
  #tts-slide-panel {
    position: fixed;
    top: 50%;
    right: -300px; 
    transform: translateY(-50%);
    width: 300px;
    background: #ffffff;
    border: 1px solid #e0e0e0;
    border-right: none;
    border-radius: 0;
    box-shadow: -2px 2px 10px rgba(0,0,0,0.15);
    transition: right 0.3s ease-in-out;
    z-index: 9999; 
    font-family: Arial, sans-serif;
  }
  
  #tts-slide-panel.open {
    right: 0;
  }

  #tts-tab-button {
    position: absolute;
    top: 0;
    right: 100%; 
    height: 100%;
    background: #0073e6;
    color: #ffffff;
    border: none;
    padding: 12px 8px;
    cursor: pointer;
    border-radius: 0;
    box-shadow: -2px 0px 5px rgba(0,0,0,0.1);
    writing-mode: vertical-rl;
    text-orientation: mixed;
    font-size: 14px;
    font-weight: bold;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0;
  }

  .tts-widget-content {
    padding: 15px;
  }

  #tts-lang {
    width: 100%;
    padding: 8px;
    border: 1px solid #cccccc;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
  }
</style>

<div id="tts-slide-panel">
  <button id="tts-tab-button">Translator</button>
  <div class="tts-widget-content">
    <h3 style="margin: 0 0 10px 0; font-size: 16px; color: #333333; text-align: center;">Translate & Speak</h3>
    
    <select id="tts-lang">
      <option value="off">-- Turn Off Reader --</option>
      <option value="en-GB">English (UK)</option>
      <option value="es-ES">Spanish</option>
      <option value="fr-FR">French</option>
      <option value="de-DE">German</option>
      <option value="it-IT">Italian</option>
      <option value="pt-PT">Portuguese</option>
      <option value="ja-JP">Japanese</option>
      <option value="zh-CN">Chinese (Simplified)</option>
      <option value="ru-RU">Russian</option>
      <option value="ar-SA">Arabic</option>
    </select>
  </div>
</div>

<script>
(function() {
  var slidePanel = document.getElementById("tts-slide-panel");
  var tabButton = document.getElementById("tts-tab-button");
  var languageSelect = document.getElementById("tts-lang");

  var isHoverEnabled = false;
  var hoverTimer = null;
  var currentWord = "";
  
  if (!slidePanel || !tabButton || !languageSelect) return;

  function setCookie(name, value, days) {
    var expires = "";
    if (days) {
      var date = new Date();
      date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
      expires = "; expires=" + date.toUTCString();
    }
    document.cookie = name + "=" + (value || "")  + expires + "; path=/";
  }

  function getCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
      var c = ca[i];
      while (c.charAt(0)==' ') c = c.substring(1,c.length);
      if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
  }

  var savedLang = getCookie("tts_language");
  if (savedLang) {
    languageSelect.value = savedLang;
    if (savedLang !== "off") {
      isHoverEnabled = true;
    }
  }

  tabButton.addEventListener("click", function() {
    slidePanel.classList.toggle("open");
  });

  languageSelect.addEventListener("change", function() {
    var selected = languageSelect.value;
    setCookie("tts_language", selected, 30);
    
    if (selected === "off") {
      isHoverEnabled = false;
      window.speechSynthesis.cancel();
      currentWord = "";
    } else {
      isHoverEnabled = true;
      slidePanel.classList.remove("open");
    }
  });

  function processCoordinates(clientX, clientY) {
    if (hoverTimer) {
      clearTimeout(hoverTimer);
    }
    
    hoverTimer = setTimeout(function() {
      var node = null;
      var offset = 0;
      
      if (document.caretPositionFromPoint) {
        var pos = document.caretPositionFromPoint(clientX, clientY);
        if (pos) {
          node = pos.offsetNode;
          offset = pos.offset;
        }
      } else if (document.caretRangeFromPoint) {
        var range = document.caretRangeFromPoint(clientX, clientY);
        if (range) {
          node = range.startContainer;
          offset = range.startOffset;
        }
      }
      
      if (node && node.nodeType === 3) {
        var textContent = node.nodeValue;
        var start = offset;
        var end = offset;
        
        var boundaryRegex = /[^\s.,!?;:"'()[\]{}<>\-]/;
        
        while (start > 0 && boundaryRegex.test(textContent[start - 1])) {
          start--;
        }
        
        while (end < textContent.length && boundaryRegex.test(textContent[end])) {
          end++;
        }
        
        var word = textContent.substring(start, end).trim();
        
        if (word && word.length > 1) {
          if (word !== currentWord) {
            currentWord = word;
            translateAndSpeak(word);
          }
        } else {
          currentWord = ""; 
        }
      } else {
        currentWord = ""; 
      }
    }, 600); 
  }

  document.addEventListener("mousemove", function(event) {
    if (!isHoverEnabled) return;
    processCoordinates(event.clientX, event.clientY);
  });

  document.addEventListener("touchstart", function(event) {
    if (!isHoverEnabled) return;
    if (event.touches && event.touches.length > 0) {
      processCoordinates(event.touches[0].clientX, event.touches[0].clientY);
    }
  }, {passive: true});

  document.addEventListener("touchmove", function() {
    if (!isHoverEnabled) return;
    if (hoverTimer) clearTimeout(hoverTimer);
    currentWord = "";
  }, {passive: true});

  function translateAndSpeak(text) {
    var langValue = languageSelect.value;
    if (langValue === "off") return;

    var targetCode = langValue.split("-")[0]; 
    var apiUrl = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=" + targetCode + "&dt=t&q=" + encodeURIComponent(text);
    
    fetch(apiUrl)
      .then(function(response) {
        return response.json();
      })
      .then(function(data) {
        if (data && data[0] && data[0][0] && data[0][0][0]) {
          var translatedText = data[0][0][0];
          speakLoud(translatedText, langValue);
        }
      })
      .catch(function(error) {
        console.error(error);
      });
  }

  function speakLoud(text, lang) {
    window.speechSynthesis.cancel(); 
    var utterance = new SpeechSynthesisUtterance(text);
    utterance.lang = lang;
    window.speechSynthesis.speak(utterance);
  }
})();
</script>

Post a Comment

0 Comments

Notice:
Comments are moderated and may not appear immediately. Please keep your comments respectful, and relevant to the post. My site. My rules.

Post a Comment (0)