// ==UserScript==
// @name Translate Japanese Tweets
// @namespace http://tampermonkey.net/
// @version 2024-05-23
// @description Translates Japanese tweets automatically
// @author You
// @match https://x.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=x.com
// @grant GM.xmlHttpRequest
// @connect translate.googleapis.com
// @license MIT
// @downloadURL none
// ==/UserScript==
const translationQueue = [];
const translatedElementSet = new Set();
const GOOGLE_API_REQUEST_INTERVAL = 300;
const DOM_QUERY_INTERVAL = 500;
function containsJapaneseCharacters(text) {
const regex = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/;
return text.match(regex) != null;
}
function translateTextGoogleTranslate(text, callback) {
// Who doesn't love themselves a free API with no keys or nothing
GM.xmlHttpRequest({
method: "GET",
url: `https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=ja&tl=en&q=${encodeURI(text)}`,
headers: {
"User-Agent": "Mozilla/5.0",
"Accept": "text/xml"
},
onload: function(response) {
callback(JSON.parse(response.responseText));
}
});
}
function translateElement(el) {
// Twitter clamps tweets longer than 10 lines, which we can easily exceed
// with translated tweets. So, bypass it
el.style["-webkit-line-clamp"] = 1000;
// Get the text from Tweet
let text = "";
el.querySelectorAll("span:not([aria-hidden='true']), img, a[role='link']").forEach(x => {
if (x.tagName === "IMG") {
text += x.getAttribute("alt");
} else {
text += x.textContent.trim().replace(/(?:\r\n|\r|\n)/g, "\\n");
}
});
// Double check!
if (!containsJapaneseCharacters(text)) {
return;
}
// Send to translation queue
translationQueue.push({
text: text,
callback: res => {
if (res === null || res[0] === null) {
console.error("Whoops, just got ratelimited!");
return;
}
let translation = "";
// res[0] has items containing the translated lines
res[0].forEach(x => translation += x[0]);
// Split by new lines, or the lines that we purposefully added
let translationSplit = translation.split(/(\\n|\n)+/);
translation = "";
translationSplit.forEach(x => {
x = x.trim();
if (x === "\\n" || x === "") {
return;
}
translation += `${x}
`
});
el.innerHTML += `