Steam 文本内容翻译

在 Steam 网页上的常见文本内容如简介、评论、新闻、讨论、聊天消息等文本右上角加上了翻译按钮,一键翻译外文。可通过右键点击按钮可重新翻译或切换翻译引擎。支持 WattToolkit 工具箱。

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Steam 文本内容翻译
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  在 Steam 网页上的常见文本内容如简介、评论、新闻、讨论、聊天消息等文本右上角加上了翻译按钮,一键翻译外文。可通过右键点击按钮可重新翻译或切换翻译引擎。支持 WattToolkit 工具箱。
// @author       羽
// @match        *://*.steampowered.com/*
// @match        *://steamcommunity.com/*
// @grant        GM_xmlhttpRequest

// @connect      translate.google.com
// @connect      translate.googleapis.com
// @connect      fanyi.baidu.com
// @connect      ifanyi.iciba.com
// @connect      translate.alibaba.com
// @connect      m.youdao.com
// @connect      dict.youdao.com
// @connect      fanyi.youdao.com
// @connect      transmart.qq.com

// @require      https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/base64.min.js

// @license      MIT License

// ==/UserScript==

(function() {
    'use strict';

    // 添加目标语言和API的全局配置
    const TARGET_LANGUAGES = {
        "中文": "zh-CN",
        "英语": "en",
        "日语": "ja",
        "韩语": "ko",
        "法语": "fr",
        "德语": "de",
        "西班牙语": "es",
        "俄语": "ru"
    };

    const TRANSLATION_APIS = {
        "Google翻译": "google",
        "百度翻译": "baidu",
        "金山词霸": "iciba",
        "有道词典-mobile": "youdao_m",
        "腾讯AI": "tencentai"
    };

    // 需要添加翻译功能的控件类名列表
    const targetClasses = [
        'profile_summary noexpand', // 用户简介
        'game_description_snippet', // 游戏简介
        'curator_review', // 游戏简介(推荐流)
        'recommendation',    // 游戏简介(推荐流卡片)
        'game_area_description',    // 游戏介绍
        'devnotes',     // 开发者说明
        'review_area_content',  // 游戏评论详情
        'blotter_group_announcement_content',  // 公告
        'workshop_item_row', // 创意工坊mod简介
        'workshopItemDescription',  // 创意工坊mod介绍
        'commentthread_comment_text',   // 创意工坊回复/游戏评论回复/讨论回复
        'apphub_CardContentMain',   // 新闻卡片简讯
        'workshopItemCollectionContainer',  // 指南简讯
        'guideTopContent',  // 指南主题
        'guide subSections',    // 指南内容
        'A_A2B6fTn_MPLlGCmsLtd _3NW5vEM9HgfQrgR4W-Xy_s',    // 新闻内容
        'EventDetailsBody',  // 活动页面
        'achieveTxt', // 成就
    ];
    // 需要添加翻译功能的CSS选择器列表
    const targetSelectors = {
        // 选择class为rightcol内的class为content的元素
        'rightcol': ['content'],    // 游戏评论
        'shortcol': ['content'],    // 游戏短评
        'forum_op': ['content', 'topic'],   // 讨论会话
        'ChatMessageBlock': ['msg'],    // 聊天消息
        '_1h5cJPC1IYFGDEMbRAWSNy': ['_1M8-Pa3b3WboayCgd5VBJT','_2g3JjlrRkzgUWXF57w3leW'],   // 更新记录简讯
        'announcement detailBox': ['headline','bodytext'],  // 创意工坊主页
    };
    const ignoredClasses = [
        'AppSummaryWidgetCtn',  // 介绍中的游戏卡片

    ]

    // 存储原始内容的键
    const ORIGINAL_CONTENT_KEY = 'original-content';
    // 存储翻译内容的键
    const TRANSLATED_CONTENT_KEY = 'translated-content';
    // 标记控件是否已添加翻译按钮
    const BUTTON_ADDED_KEY = 'translation-button-added';
    // 标记控件当前状态
    const TRANSLATION_STATE_KEY = 'translation-state';

    // 获取保存的设置或使用默认值
    let targetLanguage = localStorage.getItem('translation-target-language') || 'zh-CN';
    let translationApi = localStorage.getItem('translation-api') || 'iciba';


    // 初始化函数
    function init() {
        // 定期检查页面上的目标控件
        setInterval(checkForTargetElements, 2000);
        // 立即执行一次检查
        checkForTargetElements();
    }

    // 检查页面上的目标控件
    function checkForTargetElements() {
        // 处理类名列表
        targetClasses.forEach(className => {
            const elements = document.getElementsByClassName(className);
            for (let i = 0; i < elements.length; i++) {
                const element = elements[i];
                if (!element.getAttribute(BUTTON_ADDED_KEY)) {
                    addTranslationButton(element);
                    element.setAttribute(BUTTON_ADDED_KEY, 'true');
                    element.setAttribute(TRANSLATION_STATE_KEY, 'original');
                }
            }
        });
        
        // 处理CSS选择器列表
        Object.entries(targetSelectors).forEach(([selector, includes]) => {
            const elements = document.getElementsByClassName(selector);
            for (let i = 0; i < elements.length; i++) {
                const element = elements[i];
                // 检查元素是否包含所有指定的子元素
                const hasAllIncludes = includes.every(className => 
                    Array.from(element.children).some(child => 
                        child.classList.contains(className)
                    )
                );
                
                if (!element.getAttribute(BUTTON_ADDED_KEY) && hasAllIncludes) {
                    addTranslationButton(element, includes);
                    element.setAttribute(BUTTON_ADDED_KEY, 'true');
                    element.setAttribute(TRANSLATION_STATE_KEY, 'original');
                }
            }
        });
    }

    // 为控件添加翻译按钮
    function addTranslationButton(element, includes=null) {
        // 检查控件内容是否为空
        if (!element.textContent.trim()) {
            return; 
        }
        // 保存原始内容的深拷贝
        const originalContent = element.cloneNode(true);
        element.setAttribute(ORIGINAL_CONTENT_KEY, 'saved');
        element._originalContent = originalContent;

        // 创建按钮容器,设置为绝对定位
        const buttonContainer = document.createElement('div');
        buttonContainer.className = 'translation-button-container';

        // 创建翻译按钮
        const translateButton = document.createElement('button');
        translateButton.textContent = '翻译';
        translateButton.className = 'translation-toggle-button';
        translateButton.title = '左键点击翻译,右键点击可重新翻译/设置。';

        // 添加按钮点击事件
        translateButton.addEventListener('click', function(event) {
            event.stopPropagation(); // 阻止事件冒泡
            toggleTranslation(element, translateButton, includes);
        });
        // 添加右键菜单事件
        translateButton.addEventListener('contextmenu', function(event) {
            event.stopPropagation(); // 阻止事件冒泡
            showContextMenu(event, element, translateButton);
        });

        // 将按钮添加到容器中
        buttonContainer.appendChild(translateButton);

        // 确保元素有相对定位,以便正确放置按钮
        const originalPosition = window.getComputedStyle(element).position;
        if (originalPosition === 'static') {
            element.style.position = 'relative';
        }

        // 将按钮容器添加到元素中
        element.appendChild(buttonContainer);

        // 添加样式
        addStyles();
    }

    // 创建右键菜单
    function createContextMenu(element, button) {
        // 检查是否已存在菜单
        let menu = document.querySelector('.translation-context-menu');
        if (!menu) {
            menu = document.createElement('div');
            menu.className = 'translation-context-menu';
            document.body.appendChild(menu);
        }
        
        // 清空菜单内容
        menu.innerHTML = '';
        
        // 添加菜单项
        const retranslateItem = document.createElement('div');
        retranslateItem.className = 'translation-context-menu-item';
        retranslateItem.textContent = '重新翻译';
        retranslateItem.addEventListener('click', function() {
            retranslate(element, button);
            hideContextMenu();
        });
        menu.appendChild(retranslateItem);
        
        // 添加分隔线
        const separator = document.createElement('div');
        separator.className = 'translation-context-menu-separator';
        menu.appendChild(separator);
        
        // 添加设置菜单项
        const settingsItem = document.createElement('div');
        settingsItem.className = 'translation-context-menu-item';
        settingsItem.textContent = '翻译设置';
        settingsItem.addEventListener('click', function() {
            showTranslationSettings();
            hideContextMenu();
        });
        menu.appendChild(settingsItem);
        
        return menu;
    }
    
    // 显示右键菜单
    function showContextMenu(event, element, button) {
        event.preventDefault();
        
        const menu = createContextMenu(element, button);
        
        // 设置菜单位置
        menu.style.left = `${event.pageX}px`;
        menu.style.top = `${event.pageY}px`;
        menu.style.display = 'block';
        
        // 点击其他区域关闭菜单
        const closeMenuHandler = function(e) {
            if (!menu.contains(e.target)) {
                hideContextMenu();
                document.removeEventListener('click', closeMenuHandler);
            }
        };
        
        setTimeout(() => {
            document.addEventListener('click', closeMenuHandler);
        }, 0);
    }
    
    // 隐藏右键菜单
    function hideContextMenu() {
        const menu = document.querySelector('.translation-context-menu');
        if (menu) {
            menu.style.display = 'none';
        }
    }

    // 显示翻译设置
    function showTranslationSettings() {
        // 检查是否已存在设置面板
        let settingsPanel = document.getElementById('translation-settings-panel');
        if (settingsPanel) {
            settingsPanel.style.display = 'block';
            // 重置数据
            document.getElementById('translation-target-language').value = targetLanguage;
            document.getElementById('translation-api').value = translationApi;
            return;
        }
        
        // 创建设置面板
        settingsPanel = document.createElement('div');
        settingsPanel.id = 'translation-settings-panel';
        settingsPanel.className = 'translation-settings-panel';
        
        // 添加标题
        const title = document.createElement('h3');
        title.textContent = '翻译设置';
        title.className = 'translation-settings-title';
        settingsPanel.appendChild(title);
        
        // 添加关闭按钮
        const closeButton = document.createElement('button');
        closeButton.textContent = '×';
        closeButton.className = 'translation-settings-close-btn';
        closeButton.addEventListener('click', function() {
            settingsPanel.style.display = 'none';
        });
        settingsPanel.appendChild(closeButton);
        
        // 添加设置选项
        const settingsContent = document.createElement('div');
        settingsContent.className = 'translation-settings-content';
        settingsContent.innerHTML = `
            <div class="translation-settings-option">
                <label>目标语言:</label>
                <select id="translation-target-language">
                    ${Object.entries(TARGET_LANGUAGES).map(([name, value]) => 
                        `<option value="${value}"${value === targetLanguage ? ' selected' : ''}>${name}</option>`
                    ).join('')}
                </select>
            </div>
            <div class="translation-settings-option">
                <label>翻译API:</label>
                <select id="translation-api">
                    ${Object.entries(TRANSLATION_APIS)
                        .map(([name, value]) => 
                            `<option value="${value}"${value === translationApi ? ' selected' : ''}>${name}</option>`
                        ).join('')}
                </select>
            </div>
        `;
        settingsPanel.appendChild(settingsContent);
        
        // 添加保存按钮
        const saveButton = document.createElement('button');
        saveButton.textContent = '保存设置';
        saveButton.className = 'translation-settings-save-btn';
        saveButton.addEventListener('click', function() {
            // 保存设置逻辑
            targetLanguage = document.getElementById('translation-target-language').value;
            translationApi = document.getElementById('translation-api').value;
            
            // 保存到localStorage
            localStorage.setItem('translation-target-language', targetLanguage);
            localStorage.setItem('translation-api', translationApi);
            
            settingsPanel.style.display = 'none';
        });
        settingsPanel.appendChild(saveButton);
        
        // 添加到页面
        document.body.appendChild(settingsPanel);
        
        if (targetLanguage) {
            document.getElementById('translation-target-language').value = targetLanguage;
        }
        
        if (translationApi) {
            document.getElementById('translation-api').value = translationApi;
        }
        
        // 确保样式已添加
        addStyles();
    }

    // 切换翻译状态
    function toggleTranslation(element, button, includes=null) {
        const currentState = element.getAttribute(TRANSLATION_STATE_KEY);
        if (button.classList.contains('error')) {
            button.classList.remove('error'); 
            button.title = '左键点击翻译,右键点击可重新翻译/设置。';
            element._translatedContent = null;
        }

        if (currentState === 'original') {
            // 如果已经有翻译内容,直接显示
            if (element._translatedContent) {
                showTranslatedContent(element);
                button.textContent = '原文';
                element.setAttribute(TRANSLATION_STATE_KEY, 'translated');
            } else {
                // 否则进行翻译
                button.textContent = '翻译中...';
                button.disabled = true;

                // 先移除翻译按钮,避免其文本被收集
                const buttonContainer = element.querySelector('.translation-button-container');
                if (buttonContainer) {
                    element.removeChild(buttonContainer);
                }
                // 仅翻译目标元素
                const nonTargetElements = [];
                let targetElements = [];
                if (includes) {
                    // 保存目标元素和非目标元素
                    Array.from(element.children).forEach((child, index) => {
                        if (includes.some(className => child.classList.contains(className))) {
                            targetElements.push(child);
                        } else if (!child.classList.contains('translation-button-container')) {
                            nonTargetElements.push({
                                element: child,
                                parent: child.parentNode,
                                nextSibling: child.nextSibling,
                                index: index
                            });
                            child.parentNode.removeChild(child);
                        }
                    });
                } else if (ignoredClasses && ignoredClasses.length > 0){
                    // 当没有指定includes时,排除ignoredClasses
                    const ignoredElements = element.querySelectorAll(
                        ignoredClasses.map(className => '.' + className).join(',')
                    );
                    ignoredElements.forEach(el => {
                        el.setAttribute('data-skip-translation', 'true');
                    });
                }

                // 收集所有文本节点
                // const textNodeInfos = collectTextNodes(element);
                // const textsToTranslate = textNodeInfos.map(info => info.node.nodeValue.trim()).filter(text => text.length > 0);
                // 收集所有文本节点(仅从目标元素中收集)
                const textNodeInfos = includes ? 
                    targetElements.flatMap(el => collectTextNodes(el)) :
                    collectTextNodes(element);
                const textsToTranslate = textNodeInfos.map(info => info.node.nodeValue.trim()).filter(text => text.length > 0);

                // 按原始顺序恢复元素
                nonTargetElements.sort((a, b) => a.index - b.index).forEach(info => {
                    info.parent.insertBefore(info.element, info.nextSibling);
                });
                // 重新添加按钮
                if (buttonContainer) {
                    element.appendChild(buttonContainer);
                }

                if (textsToTranslate.length > 0) {
                    // 将所有文本分批翻译,避免超出API限制
                    translateTextBatches(textsToTranslate, function(translatedTexts, success) {
                        // 创建翻译内容的深拷贝
                        const translatedContent = element._originalContent.cloneNode(true);

                        // 再次移除翻译按钮

                        // 获取翻译后的文本节点信息
                        // const translatedNodeInfos = collectTextNodes(translatedContent);
                        // 获取翻译后的文本节点信息(仅从目标元素获取)
                        const translatedTargetElements = includes ?
                            Array.from(translatedContent.children)
                                .filter(child => includes.some(className => child.classList.contains(className))) :
                            [translatedContent];
                        
                        const translatedNodeInfos = translatedTargetElements.flatMap(el => collectTextNodes(el));

                        // 按照原始顺序替换文本
                        let translatedIndex = 0;
                        textNodeInfos.forEach((originalInfo, i) => {
                            if (i >= translatedNodeInfos.length) return;
                            
                            const originalText = originalInfo.node.nodeValue.trim();
                            if (originalText.length > 0 && translatedIndex < translatedTexts.length) {
                                translatedNodeInfos[i].node.nodeValue = translatedNodeInfos[i].node.nodeValue.replace(
                                    translatedNodeInfos[i].node.nodeValue.trim(),
                                    translatedTexts[translatedIndex]
                                );
                                translatedIndex++;
                            }
                        });

                        // 保存翻译内容
                        element._translatedContent = translatedContent;

                        // 显示翻译内容
                        showTranslatedContent(element);

                        button.textContent = '原文';
                        button.disabled = false;
                        element.setAttribute(TRANSLATION_STATE_KEY, 'translated');
                        if(!success){
                            button.textContent = '翻译错误';
                            button.classList.add('error');
                            button.title = '左键点击翻译,右键点击可重新翻译/设置。\n翻译错误可尝试重新翻译,或者切换翻译引擎。';
                        }
                    });
                } else {
                    button.textContent = '翻译';
                    button.disabled = false;
                }
            }
        } else {
            // 恢复原文
            showOriginalContent(element);
            button.textContent = '翻译';
            element.setAttribute(TRANSLATION_STATE_KEY, 'original');
        }
    }

    // 分批翻译文本,处理长文本
    function translateTextBatches(texts, callback) {
        const MAX_BATCH_SIZE = 1000; // 每批最大字符数
        const batches = [];
        let currentBatch = [];
        let currentSize = 0;

        // 将文本分成多个批次
        for (let i = 0; i < texts.length; i++) {
            const text = texts[i];
            if (currentSize + text.length > MAX_BATCH_SIZE && currentBatch.length > 0) {
                batches.push(currentBatch);
                currentBatch = [text];
                currentSize = text.length;
            } else {
                currentBatch.push(text);
                currentSize += text.length;
            }
        }

        if (currentBatch.length > 0) {
            batches.push(currentBatch);
        }

        const results = new Array(texts.length);
        let totalSuccess = true;
        let completedBatches = 0;

        // 翻译每个批次
        batches.forEach((batch, batchIndex) => {
            const batchText = batch.join('\n');

            translateText(batchText, function(translatedText, success) {
                let textIndex = 0;
                // 空值检查
                if (!translatedText) {
                    console.error('翻译返回空值:', {
                        batchText,
                        batchIndex,
                        translationApi
                    });
                    results[textIndex] = batchText; // 使用原文
                    success = false;
                } else {
                    // 分割翻译结果
                    const translatedParts = translatedText.split('\n');
                    
                    // 将结果放入正确的位置
                    for (let i = 0; i < batchIndex; i++) {
                        textIndex += batches[i].length;
                    }

                    for (let i = 0; i < Math.min(batch.length, translatedParts.length); i++) {
                        results[textIndex + i] = translatedParts[i];
                    }
                }
                
                totalSuccess = totalSuccess && success;
                completedBatches++;

                // 所有批次都完成后,返回结果
                if (completedBatches === batches.length) {
                    callback(results, totalSuccess);
                }
            });
        });
    }

    // 显示翻译内容
    function showTranslatedContent(element) {
        if (!element._translatedContent) return;

        // 保存当前的按钮容器
        const buttonContainer = element.querySelector('.translation-button-container');

        // 清空当前内容
        while (element.firstChild) {
            element.removeChild(element.firstChild);
        }

        // 添加翻译内容的所有子节点
        const fragment = document.createDocumentFragment();
        Array.from(element._translatedContent.childNodes).forEach(node => {
            fragment.appendChild(node.cloneNode(true));
        });

        element.appendChild(fragment);

        // 重新添加按钮容器
        if (buttonContainer) {
            element.appendChild(buttonContainer);
        }
    }

    // 显示原始内容
    function showOriginalContent(element) {
        if (!element._originalContent) return;

        // 保存当前的按钮容器
        const buttonContainer = element.querySelector('.translation-button-container');

        // 清空当前内容
        while (element.firstChild) {
            element.removeChild(element.firstChild);
        }

        // 添加原始内容的所有子节点
        const fragment = document.createDocumentFragment();
        Array.from(element._originalContent.childNodes).forEach(node => {
            fragment.appendChild(node.cloneNode(true));
        });

        element.appendChild(fragment);

        // 重新添加按钮容器
        if (buttonContainer) {
            element.appendChild(buttonContainer);
        }
    }

    // 重新翻译功能
    function retranslate(element, button) {
        // 如果当前是翻译状态,则重新触发翻译
        if (element.getAttribute(TRANSLATION_STATE_KEY) === 'translated') {
            // 先切换回原文
            toggleTranslation(element, button);
        }
        // 清除已有的翻译内容
        element._translatedContent = null;
        // 再触发翻译
        toggleTranslation(element, button);
    }

    // 收集元素中的所有文本节点
    function collectTextNodes(element) {
        const textNodes = [];
        const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);

        let node;
        while (node = walker.nextNode()) {
            const parent = node.parentNode;
            // 检查是否为需要跳过的元素或其子元素
            const shouldSkip = parent && (
                parent.tagName === 'SCRIPT' ||
                parent.tagName === 'STYLE' ||
                parent.closest('.translation-button-container') ||
                ignoredClasses.some(className => 
                    parent.classList.contains(className) || 
                    parent.closest('.' + className)
                )
            );
            
            if (parent && !shouldSkip) {
                const index = Array.from(parent.childNodes).indexOf(node);
                textNodes.push({
                    node: node,
                    parent: parent,
                    index: index
                });
            }
        }

        return textNodes;
    }
    

    // 翻译文本
    async function translateText(text, callback) {
        // console.log('翻译文本:', text);
        if (!text.trim()) {
            callback('',false);
            return;
        }
        
        try {
            let result;
            switch (translationApi) {
                case 'google':
                    result = await translate_gg(text, targetLanguage); break; 
                case 'baidu':
                    result = await translate_baidu(text, targetLanguage); break;
                case 'youdao_m':
                    result = await translate_youdao_mobile(text, targetLanguage); break;
                case 'iciba':
                    result = await translate_iciba(text, targetLanguage); break;
                case 'tencentai':
                    result = await translate_tencentai(text, targetLanguage); break;
                default:
                    result = await translate_gg(text, targetLanguage); break;
            }
            
            callback(result, true);
        } catch (error) {
            console.error('翻译出错:', error);
            callback(text, false); // 出错时返回原文
        }
    }

    //--谷歌翻译--start
    async function translate_gg(raw,targetLanguage='zh-CN') {
        const options = {
            method: "GET",
            url: `https://translate.google.com/translate_a/t?client=gtx&sl=auto&tl=zh-CN&q=` + encodeURIComponent(raw),
            anonymous: true,
            nocache: true,
        }
        return await BaseTranslate('谷歌翻译', raw, options, res => JSON.parse(res)[0][0])
    }
    //--百度翻译--start
    async function translate_baidu(raw, lang='zh') {
        if (!lang) {
            lang = await check_lang(raw);
        }
        if (lang == 'zh-CN') lang = 'zh';
        const options = {
            method: "POST",
            url: 'https://fanyi.baidu.com/ait/text/translate',
            data: JSON.stringify({ query: raw, from: lang, to: "zh" }),
            headers: {
                "referer": 'https://fanyi.baidu.com',
                'Content-Type': 'application/json',
                'Origin': 'https://fanyi.baidu.com',
                'accept': 'text/event-stream',
            },
        }
        return await BaseTranslate('百度翻译', raw, options, res => res.split('\n').filter(item => item.startsWith('data: ')).map(item => JSON.parse(item.slice(6))).find(item => item.data.list).data.list.map(item => item.dst).join('\n'))
    }
    async function check_lang(raw) {
        let t = Date.now();
        const options = {
            method: "POST",
            url: 'https://fanyi.baidu.com/langdetect',
            data: 'query=' + encodeURIComponent(raw.replace(/[\uD800-\uDBFF]$/, "").slice(0, 50)),
            headers: {
                "Content-Type": "application/x-www-form-urlencoded",
            }
        }
        const res = await Request(options);
        //console.log(`语言加载完毕,耗时${Date.now()-t}ms`)
        try {
            return JSON.parse(res.responseText).lan
        } catch (err) {
            console.log(err);
            return
        }
    }
    //--腾讯AI翻译--start
    function guid() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }
    async function translate_tencentai(raw,targetLanguage='zh') {
        if(targetLanguage=='zh-CN') targetLanguage='zh';
        const data = {
            "header": {
                "fn": "auto_translation",
                "client_key": `browser-chrome-121.0.0-Windows_10-${guid()}-${Date.now()}`,
                "session": "",
                "user": ""
            },
            "type": "plain",
            "model_category": "normal",
            "text_domain": "",
            "source": {
                "lang": "auto",
                "text_list": [raw]
            },
            "target": {
                "lang": targetLanguage
            }
        }
        const options = {
            method: 'POST',
            url: 'https://transmart.qq.com/api/imt',
            data: JSON.stringify(data),
            headers: {
                'Content-Type': 'application/json',
                'Host': 'transmart.qq.com',
                'Origin': 'https://transmart.qq.com',
                'Referer': 'https://transmart.qq.com/'
            },
            anonymous: true,
            nocache: true,
        }
        return await BaseTranslate('腾讯AI翻译', raw, options, res => JSON.parse(res).auto_translation[0])
    }
    //--有道翻译m--start
    async function translate_youdao_mobile(raw, targetLanguage='') {
        const options = {
            method: "POST",
            url: 'http://m.youdao.com/translate',
            data: "inputtext=" + encodeURIComponent(raw) + "&type=AUTO",
            anonymous: true,
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            }
        }
        return await BaseTranslate('有道翻译mobile', raw, options, res => /id="translateResult">\s*?<li>([\s\S]*?)<\/li>\s*?<\/ul/.exec(res)[1])
    }
    //--爱词霸翻译--start
    async function translate_iciba(raw, targetLanguage='zh') {
        if(targetLanguage=='zh-CN') targetLanguage='zh';
        const sign = CryptoJS.MD5("6key_web_fanyiifanyiweb8hc9s98e" + raw.replace(/(^\s*)|(\s*$)/g, "")).toString().substring(0, 16)
        const options = {
            method: "POST",
            url: `https://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_web_fanyi&sign=${sign}`,
            data: `from=auto&t=${targetLanguage}&q=` + encodeURIComponent(raw),
            headers: {
                "Content-Type": "application/x-www-form-urlencoded",
            },
        }
        return await BaseTranslate('爱词霸翻译', raw, options, res => JSON.parse(res).content.out)
    }
    

    // 基础请求
    async function BaseTranslate(name, raw, options, processer) {
        const toDo = async () => {
            try {
                const { responseText } = await Request(options);
                // 响应内容检查
                if (!responseText) {
                    throw new Error('翻译API返回空响应');
                }
                // 处理器结果检查
                const result = await processer(responseText);
                if (!result) {
                    throw new Error('翻译处理器返回空结果');
                }
                queueMicrotask(() => sessionStorage.setItem(`${name}-${raw}`, result));
                return result;
            } catch (err) {
                console.error('翻译请求详细错误:', {
                    name,
                    raw,
                    error: err,
                    responseText: err.responseText,
                    api: translationApi
                });
                throw err;
            }
        };
        
        return await PromiseRetryWrap(toDo, { 
            RetryTimes: 3, 
            ErrProcesser: (err) => {
                console.error('翻译重试失败:', err);
                throw err; // 抛出错误而不是返回原文
            }
        });
    }
    // 基础请求函数
    function Request(options) {
        return new Promise((resolve, reject) => {
            const timeout = options.timeout || 10000; // 默认10秒超时
            const controller = new AbortController();
            const timeoutId = setTimeout(() => controller.abort(), timeout);
    
            GM_xmlhttpRequest({
                ...options,
                signal: controller.signal,
                onload: (response) => {
                    clearTimeout(timeoutId);
                    resolve(response);
                },
                onerror: (error) => {
                    clearTimeout(timeoutId);
                    reject(error);
                },
                ontimeout: () => {
                    clearTimeout(timeoutId);
                    reject(new Error('请求超时'));
                }
            });
        });
    }

    // 重试包装函数
    async function PromiseRetryWrap(func, { RetryTimes = 3, ErrProcesser = null, baseDelay = 1000 } = {}) {
        let lastError;
        
        for (let i = 0; i < RetryTimes; i++) {
            try {
                return await func();
            } catch (error) {
                lastError = error;
                if (i < RetryTimes - 1) {
                    // 使用指数退避策略,延迟时间随重试次数增加
                    const delay = baseDelay * Math.pow(2, i);
                    await new Promise(resolve => setTimeout(resolve, delay));
                }
            }
        }
        
        return ErrProcesser ? ErrProcesser(lastError) : Promise.reject(lastError);
    }

    // 添加全局样式
    function addStyles() {
        // 检查是否已添加样式
        if (document.getElementById('translation-button-styles')) {
            return;
        }

        const styleElement = document.createElement('style');
        styleElement.id = 'translation-button-styles';
        styleElement.textContent = `
            .translation-button-container {
                top: 0;
                right: 0;
                position: absolute;
                z-index: 9999;
                pointer-events: none; /* 容器本身不接收点击事件 */
            }

            .translation-toggle-button {
                padding: 1px 5px;
                font-size: 12px;
                cursor: pointer;
                background-color: rgba(40, 80, 107, 0.3); 
                color: rgba(96, 182, 231, 0.3);
                border: none;
                border-radius: 4px;
                transition: all 0.3s ease;
                box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
                pointer-events: auto; /* 按钮可以接收点击事件 */
                transform-origin: center;
            }
            .translation-toggle-button:hover {
                background-color: rgba(40, 80, 107, 1); /* 悬停时不透明 */
                box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
                color: rgba(96, 182, 231, 1);
            }
            .translation-toggle-button:active {
                transform: scale(0.95); /* 点击时缩小效果 */
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
                background-color: rgba(102, 192, 244, 0.7);
                color: white;
            }
            .translation-toggle-button:disabled {
                background-color: rgba(102, 192, 244, 0.5);
                cursor: not-allowed;
                color: rgba(27, 40, 56, 1);
            }
            .translation-toggle-button.error {
                background-color: rgba(200, 51, 51, 0.7);
                color: rgba(243, 236, 236, 0.7);
            }
            .translation-toggle-button.error:hover {
                background-color: rgba(200, 51, 51, 1);
                color: rgb(245, 234, 234);
            }

            /* 右键菜单样式 */
            .translation-context-menu {
                position: absolute;
                background-color: rgba(72, 101, 130, 0.9);
                border: 1px solid #2a4b62;
                border-radius: 4px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
                color: #d6d8db;
                padding: 2px;
                z-index: 10000;
                min-width: 50px;
                display: none;
            }
            
            .translation-context-menu-item {
                padding: 1px 5px;
                cursor: pointer;
                font-size: 13px;
                transition: background-color 0.2s;
            }
            
            .translation-context-menu-item:hover {
                background-color: rgb(30, 47, 68);
            }
            
            .translation-context-menu-separator {
                height: 1px;
                background-color: #8b9fb4;
                margin: 2px;
            }

            /* 设置面板样式 */
            .translation-settings-panel {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background-color: rgba( 33, 49, 68, 0.9);
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
                z-index: 10001;
                min-width: 300px;
            }
            
            .translation-settings-title {
                margin: 0 0 15px 0;
                border-bottom: 1px solid #eee;
                padding-bottom: 10px;
            }
            
            .translation-settings-close-btn {
                position: absolute;
                top: 10px;
                right: 10px;
                background: none;
                border: none;
                font-size: 20px;
                cursor: pointer;
                color: #999;
            }
            
            .translation-settings-content {
                margin-bottom: 15px;
            }
            
            .translation-settings-option {
                margin-bottom: 10px;
            }
            
            .translation-settings-option label {
                display: block;
                margin-bottom: 5px;
            }
            
            .translation-settings-option select {
                width: 100%;
                padding: 5px;
                background-color: #7d90a4;
            }
            
            .translation-settings-save-btn {
                padding: 8px 15px;
                background-color: #486582;
                color: white;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                margin-top: 15px;
            }
            
            .translation-settings-save-btn:hover {
                background-color: #6E8FAF;
            }

            /* 按钮定位调整 */
            .commentthread_comment_text {
                overflow-y: unset;
            }
            .blotter_group_announcement_content,
            .review_area_content {
                overflow: unset;
            }
            .game_area_description .translation-button-container {
                top: 0;
                bottom: unset;
            }
            .workshopItemDescription .translation-button-container,
            .commentthread_comment_content .translation-button-container {
                top: unset;
                right: 30px;
                bottom: 100%; 
            }
            .apphub_CardContentMain .translation-button-container {
                top: 5px;
                right: 5px; 
                bottom: unset;
            }
            .EventDetailsBody .translation-button-container,
            .workshopItemCollectionContainer .translation-button-container {
                top: 5px;
                right: 100px; 
                bottom: unset;
            }
            .announcement .translation-button-container,
            .ChatMessageBlock .translation-button-container{
                top: 5px;
                right: 15px;
                bottom: unset;
            }
            .subSections .translation-button-container {
                top: 15px;
                right: 5px;
                bottom: unset;
            }
            .guideTopContent .translation-button-container {
                top: 8px;
                right: 160px; 
                bottom: unset;
            }
            .forum_op .translation-button-container {
                top: 50px;
                right: 12px; 
                bottom: unset;
            }
            .rightcol .translation-button-container {
                top: 60px;
                right: 10px; 
                bottom: unset;
            }
            .profile_summary .translation-button-container,
            .devnotes .translation-button-container,
            .shortcol .translation-button-container {
                top: 0;
                right: 10px; 
                bottom: unset;
            }
            .game_description_snippet .translation-button-container{
                top: 0;
                right: 3px; 
                bottom: unset;
            }
            .recommendation .translation-button-container {
                top: 12px;
                right: 80px;
                bottom: unset; 
            }
            .curator_review .translation-button-container {
                top: 0;
                right: unset;
                bottom: unset; 
            }
            .blotter_group_announcement_content .translation-button-container{
                top: -30px;
                right: 10px;
                bottom: unset;
            }
            .review_area_content .translation-button-container,
            ._3NW5vEM9HgfQrgR4W-Xy_s .translation-button-container {
                top: -22px;
                right: 0;
                bottom: unset; 
            }

        `;

        document.head.appendChild(styleElement);
    }

    // 启动脚本
    init();
})();