BGM Ollama 翻译器

使用 Ollama 翻译 Bangumi 条目和角色详情,支持模型切换、缓存、术语表和提示格式选择。

目前為 2024-10-20 提交的版本,檢視 最新版本

// ==UserScript==

// @name         BGM Ollama 翻译器

// @namespace    http://tampermonkey.net/

// @version      1.0

// @description  使用 Ollama 翻译 Bangumi 条目和角色详情,支持模型切换、缓存、术语表和提示格式选择。

// @author       Sedoruee

// @match        https://bgm.tv/subject/*

// @match        https://bgm.tv/character/*

// @grant        GM_xmlhttpRequest

// @grant        GM_setValue

// @grant        GM_getValue

// @license MIT

// ==/UserScript==



(function() {

    'use strict';



    const ollamaEndpoint = 'http://localhost:11434/api/generate'; // **修改为你自己的 Ollama API 地址**



    // 配置选项

    const config = {

        subject: {

            model: 'GalTransl7B_v2.6_Q6_K', // 条目翻译模型

            autoTranslate: 0,    // 0: 自动,1: 手动

            useNewPromptFormat: 1 // 0: 其他模型,1: Sakura模型1.0版及以上/GalTransl模型 (支持术语表)

        },

        character: {

            model: 'GalTransl7B_v2.6_Q6_K', // 角色翻译模型

            autoTranslate: 0,             // 0: 自动,1: 手动

            useNewPromptFormat: 1,          // 0: 其他模型,1: Sakura模型1.0版及以上/GalTransl模型 (支持术语表)

            glossary: [                     // 角色翻译术语表

                {"src": "可选", "dst": "可选", "info": "可选"},

                {"src": "可选", "dst": "可选"},

            ]

        }

    };



    // 获取页面类型和ID

    const isSubjectPage = window.location.href.includes('/subject/');

    const pageType = isSubjectPage ? 'subject' : 'character';

    const id = window.location.href.match(isSubjectPage ? /subject\/(\d+)/ : /character\/(\d+)/)[1];



    // 获取配置

    const { model, autoTranslate, useNewPromptFormat, glossary } = config[pageType];

    const detailElement = isSubjectPage ? document.getElementById('subject_summary') : document.querySelector('div.detail');

    const cacheKey = `translatedText_${id}_${model}`;

    const cachedData = GM_getValue(cacheKey);





    function displayTranslation(translatedText) {

        const translatedDiv = document.createElement('div');

        translatedDiv.style.marginTop = '10px';

        translatedDiv.innerHTML = `<hr><h3>翻译结果:</h3><p>${translatedText.replace(/\n/g, '<br>')}</p>`;

        if (isSubjectPage) {

            detailElement.parentNode.insertBefore(translatedDiv, detailElement.nextSibling);

        } else {

            detailElement.appendChild(translatedDiv);

        }

    }



    function translate() {

        const textToTranslate = detailElement.innerText;



        const translateButton = document.querySelector('#translateButton');

        if (translateButton) {

            translateButton.disabled = true;

            translateButton.textContent = '翻译中...';

        }



        let prompt;

        if (useNewPromptFormat === 1) {

            let glossaryText = "";

            if (glossary && glossary.length > 0) { //  检查 glossary 是否存在

                const glossaryLines = glossary.map(item => {

                    const info = item.info ? ` #${item.info}` : "";

                    return `${item.src}->${item.dst}${info}`;

                });

                glossaryText = "根据以下术语表:\n" + glossaryLines.join('\n') + "\n";

            }



            prompt = `<|im_start|>system\n你是一个轻小说翻译模型,可以流畅通顺地以日本轻小说的风格将日文翻译成简体中文,并联系上下文正确使用人称代词,不擅自添加原文中没有的代词。<|im_end|>\n` +

                      `<|im_start|>user\n${glossaryText}将下面的日文文本翻译成中文:${textToTranslate}<|im_end|>\n` +

                      `<|im_start|>assistant\n`;

        } else {

            prompt = `把这段日语文本直接翻译为中文文本,不保留任何非中文语言和额外内容: "\n\n${textToTranslate}"`;

        }





        const requestBody = { model, prompt, stream: false };



        GM_xmlhttpRequest({

            method: 'POST',

            url: ollamaEndpoint,

            headers: { 'Content-Type': 'application/json' },

            data: JSON.stringify(requestBody),

            onload: function(response) {

                try {

                    const responseJson = JSON.parse(response.responseText);

                    if (responseJson.response) {

                        const translatedText = responseJson.response;

                        displayTranslation(translatedText);

                        GM_setValue(cacheKey, translatedText);

                    } else {

                        alert('翻译失败: ' + (responseJson.error || 'Unexpected response format.'));

                    }

                } catch (error) {

                    alert('解析JSON响应失败: ' + error);

                } finally {

                    if (translateButton) {

                        translateButton.disabled = false;

                        translateButton.textContent = '翻译为中文';

                    }

                }

            },

            onerror: function(error) {

                alert('请求失败: ' + error);

                if (translateButton) {

                    translateButton.disabled = false;

                    translateButton.textContent = '翻译为中文';

                }

            }

        });

    }





    if (cachedData) {

        displayTranslation(cachedData);

    } else if (autoTranslate === 1) {

        const translateButton = document.createElement('button');

        translateButton.textContent = '翻译为中文';

        translateButton.style.marginTop = '10px';

        translateButton.id = 'translateButton';



        if (isSubjectPage) {

            detailElement.parentNode.insertBefore(translateButton, detailElement);

        } else {

            detailElement.appendChild(translateButton);

        }



        translateButton.addEventListener('click', translate);

    } else {

        translate();

    }



})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址