kiss虽然好用,但没我这个轻量。(划词翻译)

首先感谢kiss翻译开源作者的贡献,本人也是非常喜欢这个项目,就是在使用中有些痛点,所以我个人就搞了一个自己觉得好用的脚本,推荐给大家,如果大家有喜欢的可以点个赞。下面是操作步骤:
1.浏览器要能安装“油猴”插件



2.使用下面的脚本中“******”设置成自己使用的模型调用(支持openai公益和第三方api)

// --- ⚠️ 用户配置区 ---
    // 请将 YOUR_API_ENDPOINT 替换为你的第三方中转API地址
    const API_ENDPOINT = 'https://********/v1/chat/completions';

    // 请将 YOUR_API_KEY 替换为你的API Key
    const API_KEY = '*********';

    // 你想使用的模型
    const MODEL = 'gpt-4.1-nano';

3.完整脚本:

// ==UserScript==
// @name         划词翻译 (基于OpenAI中转API) - 居中最终版
// @name:en      划词翻译
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  增加单例运行保护,彻底解决重复执行问题。在网页上选择文本后,一个半透明的按钮会出现在选区中央,点击即可调用OpenAI API进行翻译。
// @description:en Added singleton guard to prevent duplicate script execution. After selecting text, a semi-transparent button appears in the center of the selection for translation.
// @author       AI Assistant
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      *
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 新增:单例运行保护 ---
    // 如果脚本已在该页面运行,则直接退出,防止重复加载和事件绑定
    if (window.isAiTranslatorScriptRunning) {
        console.log('划词翻译脚本已在运行,本次加载被阻止。');
        return;
    }
    window.isAiTranslatorScriptRunning = true;
    // --- 保护结束 ---


    //--- ⚠️ 用户配置区 ---
    // 请将 YOUR_API_ENDPOINT 替换为你的第三方中转API地址
    const API_ENDPOINT = 'https://********/v1/chat/completions';

    // 请将 YOUR_API_KEY 替换为你的API Key
    const API_KEY = '*********';

    // 你想使用的模型
    const MODEL = 'gpt-4.1-nano';

    // 翻译指令
    const PROMPT_TEMPLATE = `Translate the following text into simplified Chinese, keeping the original formatting as much as possible. Provide only the translated content, without any extra explanations or introductory phrases.\n\nText to translate:\n"""\n{text}\n"""`;
    // --- 配置区结束 ---

    if (API_ENDPOINT === 'YOUR_API_ENDPOINT' || API_KEY === 'YOUR_API_KEY') {
        alert('【划词翻译脚本提示】\n\n请先编辑脚本,在顶部的“用户配置区”填入您的API地址和API Key!');
        return;
    }

    GM_addStyle(`
        #ai-translator-btn {
            position: absolute;
            width: 10px;
            height: 10px;
            background-color: #ff4757;
            border-radius: 50%;
            cursor: pointer;
            z-index: 99999;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            border: 2px solid white;
            transform: translate(-50%, -50%);
            opacity: 0.5;
            transition: transform 0.2s ease, opacity 0.2s ease;
        }
        #ai-translator-btn:hover {
            transform: translate(-50%, -50%) scale(1.2);
            opacity: 1;
        }
        #ai-translator-popup {
            position: absolute;
            z-index: 99998;
            background-color: white;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            padding: 15px;
            max-width: 400px;
            min-width: 200px;
            font-size: 14px;
            line-height: 1.6;
            color: #333;
            text-align: left;
            white-space: pre-wrap;
            word-wrap: break-word;
            transform: translate(-50%, -50%);
        }
    `);

    let translateBtn = null;
    let translatePopup = null;

    function cleanup() {
        const existingBtn = document.getElementById('ai-translator-btn');
        if (existingBtn) {
            existingBtn.remove();
        }
        const existingPopup = document.getElementById('ai-translator-popup');
        if (existingPopup) {
            existingPopup.remove();
        }
        translateBtn = null;
        translatePopup = null;
    }

    document.addEventListener('mouseup', function(e) {
        if (e.target.id === 'ai-translator-btn' || (translatePopup && translatePopup.contains(e.target))) {
            return;
        }

        cleanup();

        const selection = window.getSelection();
        if (selection.isCollapsed || selection.toString().trim().length === 0) {
            return; // 如果没有选择内容或选择已折叠,则直接返回
        }

        const selectedText = selection.toString().trim();
        const range = selection.getRangeAt(0);
        const rect = range.getBoundingClientRect();

        const centerX = window.scrollX + rect.left + rect.width / 2;
        const centerY = window.scrollY + rect.top + rect.height / 2;

        translateBtn = document.createElement('div');
        translateBtn.id = 'ai-translator-btn';
        document.body.appendChild(translateBtn);

        translateBtn.style.top = `${centerY}px`;
        translateBtn.style.left = `${centerX}px`;

        translateBtn.addEventListener('click', function(event) {
            event.stopPropagation();
            showTranslationPopup(selectedText, centerY, centerX);
            if (translateBtn) translateBtn.style.display = 'none';
        });
    });

    document.addEventListener('mousedown', function(e) {
        if (translatePopup && !translatePopup.contains(e.target) && e.target.id !== 'ai-translator-btn') {
            cleanup();
        }
    });

    function showTranslationPopup(text, top, left) {
        // 再次清理以防万一
        cleanup();

        translatePopup = document.createElement('div');
        translatePopup.id = 'ai-translator-popup';
        translatePopup.textContent = '翻译中...';
        document.body.appendChild(translatePopup);

        translatePopup.style.top = `${top}px`;
        translatePopup.style.left = `${left}px`;

        callOpenAI(text);
    }

    function callOpenAI(text) {
        GM_xmlhttpRequest({
            method: 'POST',
            url: API_ENDPOINT,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${API_KEY}`
            },
            data: JSON.stringify({
                model: MODEL,
                messages: [{ role: 'user', content: PROMPT_TEMPLATE.replace('{text}', text) }],
                temperature: 0.7,
                stream: false
            }),
            onload: function(response) {
                if (translatePopup) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (data.choices && data.choices.length > 0) {
                            const translatedText = data.choices[0].message.content.trim();
                            translatePopup.textContent = translatedText;
                        } else {
                            const errorMsg = data.error ? data.error.message : 'API返回格式不正确。';
                            translatePopup.textContent = `翻译失败:${errorMsg}`;
                        }
                    } catch (e) {
                        translatePopup.textContent = '翻译失败:无法解析返回结果。';
                    }
                }
            },
            onerror: function(response) {
                if (translatePopup) {
                    translatePopup.textContent = `翻译失败:网络请求错误。状态: ${response.status}`;
                }
            },
            ontimeout: function() {
                if (translatePopup) {
                    translatePopup.textContent = '翻译失败:请求超时。';
                }
            }
        });
    }
})();

4.效果如下 :选中要翻译的文本,在中心出现一个半透明的小红点(这样做的目的是尽可能减少整体页面的影响,我就是觉得kiss这个图标有点太耀眼了,所以想才有了制作这个脚本的想法)



14 个赞

感谢大佬分享,点赞支持。

这个好啊!感谢分享。

这个也是好东西,感谢分享

感谢分享,有点陈金翻译那个意思了

小红点这个思路不错,确实比大图标低调多了,感谢分享

感谢分享,轻量版确实不错!

点赞点赞

分享谢谢

1 个赞

使用挺方便,感谢分享!

感谢分享 好使

谢谢大佬分享

感谢分享

1 个赞

进来看看 支持楼主

支持OP

这里记得改双//

1 个赞

感谢指正。

谢谢分享

厉害

好用 感谢分享