// ==UserScript==
// @name Axiom推文翻译
// @namespace http://tampermonkey.net/
// @version 4.0 // 再次更新版本号
// @author @Gufii_666
// @description 对axiom的推文监控,代币内推文,扫链出现的推文进行翻译
// @match https://axiom.trade/pulse*
// @match https://axiom.trade/trackers*
// @match https://axiom.trade/meme/*
// @match https://axiom.trade/discover*
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict';
const TRANSLATION_BASE_URL = 'https://translate.googleapis.com/translate_a/single';
const CLIENT_PARAM = 'gtx';
const SOURCE_LANG = 'en';
const TARGET_LANG = 'zh-CN';
const DATA_TYPE = 't';
const TRANSLATED_TEXT_CLASS = 'localized-content-display';
const ORIGINAL_DATA_ATTR = 'data-translation-processed-status';
async function obtainLocalizedText(inputString) {
if (!inputString || typeof inputString !== 'string') {
return '[Translation Input Error]';
}
const queryParams = new URLSearchParams({
client: CLIENT_PARAM,
sl: SOURCE_LANG,
tl: TARGET_LANG,
dt: DATA_TYPE,
q: inputString
});
const fullUrl = `${TRANSLATION_BASE_URL}?${queryParams.toString()}`;
try {
const response = await fetch(fullUrl);
const data = await response.json();
return data[0]?.map(segment => segment[0]).join('') || '[Translation Fetch Error]';
} catch (error) {
console.error("Content localization failed:", error);
return '[Failed to Localize]';
}
}
function injectLocalizedParagraph(targetElement, translatedContent, prepend = false) {
if (!targetElement || !targetElement.parentElement) return;
const newParagraph = document.createElement("p");
newParagraph.classList.add(TRANSLATED_TEXT_CLASS);
newParagraph.textContent = translatedContent;
Object.assign(newParagraph.style, {
color: "#FFFFFF",
backgroundColor: "#2E8B57",
fontSize: "14px",
fontWeight: "bold",
padding: "8px 12px",
borderRadius: "6px",
margin: "8px 0",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
border: "1px solid #4CAF50",
display: "block",
lineHeight: "1.5",
textShadow: "1px 1px 2px rgba(0,0,0,0.2)"
});
if (prepend) {
targetElement.parentElement.insertBefore(newParagraph, targetElement);
} else {
targetElement.parentElement.appendChild(newParagraph);
}
}
// New: Function to handle primary tweet content translation (direct tweets, posts)
async function translatePrimaryTweetContent(tweetArticle) {
// Look for the main tweet body P tag
const textElement = tweetArticle.querySelector("p.tweet-body_root__ChzUj") ||
tweetArticle.querySelector("p.text-textSecondary.mt-1.whitespace-pre-wrap");
if (!textElement || textElement.getAttribute(ORIGINAL_DATA_ATTR)) {
return;
}
const rawContent = textElement.innerText.trim();
if (!rawContent) {
textElement.setAttribute(ORIGINAL_DATA_ATTR, 'true'); // Mark empty as processed
return;
}
const localizedContent = await obtainLocalizedText(rawContent);
injectLocalizedParagraph(textElement, localizedContent, true);
textElement.setAttribute(ORIGINAL_DATA_ATTR, 'true'); // Mark as processed
}
// New: Function to handle quoted/retweet content translation
async function translateQuotedTweetContent(quotedTweetContainer) {
// Look for the text P tag INSIDE the specific quoted tweet container
const textElement = quotedTweetContainer.querySelector("p.text-textSecondary.mt-1");
if (!textElement || textElement.getAttribute(ORIGINAL_DATA_ATTR)) {
return;
}
const rawContent = textElement.innerText.trim();
if (!rawContent) {
textElement.setAttribute(ORIGINAL_DATA_ATTR, 'true'); // Mark empty as processed
return;
}
const localizedContent = await obtainLocalizedText(rawContent);
injectLocalizedParagraph(textElement, localizedContent, true);
textElement.setAttribute(ORIGINAL_DATA_ATTR, 'true'); // Mark as processed
}
async function handleProfileDescriptionTranslation(containerElement) {
const descriptionElement = containerElement.querySelector("p.break-words");
if (!descriptionElement || descriptionElement.getAttribute(ORIGINAL_DATA_ATTR)) return;
const textParts = Array.from(descriptionElement.querySelectorAll("span"));
const combinedText = textParts.map(s => s.textContent).join('').trim();
if (!combinedText) {
descriptionElement.setAttribute(ORIGINAL_DATA_ATTR, 'true'); // Mark empty bios as processed
return;
}
const localizedContent = await obtainLocalizedText(combinedText);
injectLocalizedParagraph(descriptionElement, localizedContent, false);
descriptionElement.setAttribute(ORIGINAL_DATA_ATTR, 'true');
const parentWrapper = descriptionElement.closest("div[style], div.relative");
if (parentWrapper) {
parentWrapper.style.maxHeight = "none";
parentWrapper.style.overflow = "visible";
const overlayGradient = parentWrapper.querySelector("div[class*='bg-gradient-to-b']");
if (overlayGradient) {
overlayGradient.style.display = "none";
}
}
}
function performFullPageScan() {
// 1. Clear ALL previous translations and reset processed status.
document.querySelectorAll(`.${TRANSLATED_TEXT_CLASS}`).forEach(el => el.remove());
// Select ALL possible original text elements and remove their processed status
// so they can be re-evaluated for the "new" page content.
document.querySelectorAll(
"p.tweet-body_root__ChzUj," +
"p.text-textSecondary.mt-1.whitespace-pre-wrap," +
"div.mt-2.border.border-secondaryStroke.rounded-\\[4px\\].relative.group.overflow-hidden p.text-textSecondary.mt-1," +
"p.break-words"
).forEach(el => el.removeAttribute(ORIGINAL_DATA_ATTR));
// 2. Process current content.
// Process primary tweet content (articles and other main blocks)
document.querySelectorAll("article.tweet-container_article__0ERPK").forEach(translatePrimaryTweetContent);
document.querySelectorAll(".hover\\:bg-primaryStroke\\/20.relative.group.text-\\[14px\\]").forEach(translatePrimaryTweetContent);
// Process quoted/retweet content specifically
document.querySelectorAll("div.mt-2.border.border-secondaryStroke.rounded-\\[4px\\].relative.group.overflow-hidden").forEach(translateQuotedTweetContent);
// Process user bios.
document.querySelectorAll("p.break-words").forEach(pElement => {
const parentDiv = pElement.closest("div");
if (parentDiv) {
handleProfileDescriptionTranslation(parentDiv);
}
});
}
let lastPathname = window.location.pathname;
let scanTimeoutId = null;
const contentWatcher = new MutationObserver((mutations) => {
if (window.location.pathname !== lastPathname) {
lastPathname = window.location.pathname;
clearTimeout(scanTimeoutId);
scanTimeoutId = setTimeout(performFullPageScan, 500);
}
for (const mutationRecord of mutations) {
for (const addedDomNode of mutationRecord.addedNodes) {
if (addedDomNode.nodeType !== 1 || !addedDomNode.querySelector) continue;
// Process primary tweet articles
addedDomNode.querySelectorAll("article.tweet-container_article__0ERPK").forEach(translatePrimaryTweetContent);
// Process other main tweet-like content blocks
const mainTweetBlock = addedDomNode.querySelector(".hover\\:bg-primaryStroke\\/20.relative.group.text-\\[14px\\]");
if (mainTweetBlock) {
translatePrimaryTweetContent(mainTweetBlock);
}
// Process specific quoted tweet containers
const quotedTweetContainer = addedDomNode.querySelector("div.mt-2.border.border-secondaryStroke.rounded-\\[4px\\].relative.group.overflow-hidden");
if (quotedTweetContainer) {
translateQuotedTweetContent(quotedTweetContainer);
}
// Process user bio sections
const potentialBioElement = addedDomNode.querySelector("p.break-words");
if (potentialBioElement) {
const bioContainer = potentialBioElement.closest("div");
if (bioContainer) {
handleProfileDescriptionTranslation(bioContainer);
}
}
}
}
});
contentWatcher.observe(document.body, {
childList: true,
subtree: true
});
// Initial scan when the script first runs
performFullPageScan();
})();