Greasy Fork

Greasy Fork is available in English.

NovelAI图像生成汉化

NovelAI图像生成的简体中文汉化脚本

当前为 2025-07-20 提交的版本,查看 最新版本

// ==UserScript==
// @name         NovelAI图像生成汉化
// @namespace    https://github.com/qiqi20020612/NovelAI-zh_CN
// @version      3.1
// @description  NovelAI图像生成的简体中文汉化脚本
// @author       Z某ZMou
// @match        https://novelai.net/image
// @match        https://novelai.net/inspect
// @icon         https://novelai.net/icons/novelai-round.png
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      GPL-3.0-or-later
// ==/UserScript==

(function () {
    'use strict';

    // 获取用户设置,如果未设置则默认为启用标题修改
    var isTitleModificationEnabled = GM_getValue('isTitleModificationEnabled', true);

    // 设置页面语言为中文
    document.documentElement.lang = 'zh-CN';

    // 文本替换映射表
    const translationMap = {
        // 历史记录
        'History': '历史记录',
        'Click on an image to set your settings to the ones used to generate it': '左键点击图像可以快速应用生成该图像时的原始设置',
        'except for any init image': '种子和原始图像不会被复制',
        'Delete this image': '确定要删除这张图片吗',
        'Delete it': '确认删除',
        'No, keep it': '不,取消',
        'Download ZIP': '打包下载全部图片',
        'Download all images? This could take a while, or fail entirely, with large numbers of images.': '确实要下载所有图像吗?如果图像数量较多,可能会需要一些时间,或导致下载失败。',
        'Downloading': '正在下载',
        'images...': '张图片...',
        'Images downloaded': '图片已开始下载',

        // 个人菜单
        'Author': '作者',
        'Not Subscribed': '未订阅',
        'Account': '账号',
        'Account Settings': '账号设置',
        'Logout': '退出',
        'Other': '其它',
        'Quick Start Gallery': '快速上手图库',
        'Director Tools': '导演工具',
        'Text Generation': '文本生成',
        'Tokenizer': '分词器',
        'Help': '帮助',
        'Tutorial': '教程',
        'Documentation': '文档',
        'Version ': '版本 ',
        'EN': 'CN',

        // 快速上手图库
        'Get Started': '让我们开始吧',
        'Get Inspiration from our quick start gallery!': '从我们的快速上手图库中获取灵感!',
        'Click an image to copy the prompt.': '点击图片以复制提示词。',
        'Copied!': '已复制!',

        // 模型选择
        'New': '新增',
        'A version of our newest model trained on a curated subset of images. Recommended for streaming.': '我们最新模型的一个版本,该模型是在经过精心挑选的图像集上进行训练的。建议用于流式传输。',
        'Our newest and best model.': '我们最新、最好的模型。',
        'Legacy': '旧版',
        'Our V4 model trained on a curated subset of images. No longer recommended for use.': '我们V4模型的一个版本,该模型是在经过精心挑选的图像集上进行训练的。不再推荐使用。',
        'Our V4 model. No longer recommended for use.': '我们的V4模型。不再推荐使用。',
        'Our previous model. No longer recommended for use.': '我们以前的模型。不再推荐使用。',
        'One of our older models. No longer recommended for use.': '我们以前的模型之一。不再推荐使用。',

        // 提示词
        'Prompt': '提示词',
        'You are currently using Anime mode.': '您当前使用的是动漫模式。',
        'The mode changes the tag suggestions and adds a dataset tag to the prompt. You can click the icon to switch.': '切换模式会更改Tag建议,并在提示中添加数据集Tag。您可以点击图标进行切换。',
        'You are currently using Furry mode.': '您当前使用的是Furry模式。',
        // 'or': '或者',
        'Randomize': '随机生成',
        'Random Prompt': '随机提示词',
        'Quality Tags Enabled': '已启用质量优化',
        'Added to the end of the prompt:': '会在输入的提示词结尾添加以下提示词:',
        'Undesired Content': '负面提示词',
        'Reattach Undesired Content': '拼接负面提示词',
        'Detach Undesired Content': '拆分负面提示词',
        'UC Preset Enabled': '已启用负面提示词预设',
        'Added to the beginning of the UC:': '会在输入的负面提示词前面添加以下提示词:',
        'This prompt is using ': '输入的部分占',
        ' of the currently used ': '个Token,共使用了',
        ' tokens. Max total tokens: ': '个。该部分最大Token数:',
        'Prompt Settings': '提示词设置',
        'Add Quality Tags': '添加质量优化Tag',
        'The prompt will be used unmodified.': '不会修改提示词。',
        'Tags to increase quality will be prepended to the prompt.': '会在提示词中添加能够提高质量的Tag。',
        'Undesired Content Preset': '负面提示词预设',
        'Heavy': '全面',
        // 全面档负面提示词:nsfw, lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]
        'Light': '精简',
        // 精简档负面提示词:nsfw, lowres, jpeg artifacts, worst quality, watermark, blurry, very displeasing
        'Human Focus': '以人为本',
        // 聚焦角色档负面提示词:nsfw, lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract], bad anatomy, bad hands, @_@, mismatched pupils, heart-shaped pupils, glowing eyes
        'None': '无',
        // 即使选择无,也会添加负面提示词:lowres
        'Disable Tag Suggestions': '禁用Tag建议',
        'Highlight Emphasis': '高亮强调部分',

        // 图生图
        'Add a Base Img': '上传基准图片',
        'Optional': '可选',
        'What do you want to do with this image': '您想要怎样处理这张图片',
        'Image2Image': '图生图',
        'Transform your image.': '改变您的图片。',
        'Strength': '强度',
        'Noise': '噪声',
        'Inpaint Image': '重绘图像',
        'This image has metadata': '这张图片带有元数据',
        'Did you want to import that instead': '您想要怎样导入它',
        'Characters': '角色',
        'Append': '附加',
        'Settings': '设置',
        'Import Metadata': '导入元数据',
        'Clean Imports': '清除增益',
        'Remove': '移除',
        ', add spaces after commas': ',在逗号后添加空格',

        // 角色提示词
        'Character Prompts': '角色提示词',
        'Customize separate characters.': '自定义每个角色。',
        'Click to edit a character.': '点击以编辑一个角色。',
        'Clear Random Prompt Characters': '清除随机提示词角色',
        'Add Character': '添加角色',
        'Female': '女性',
        'Male': '男性',
        'Character': '角色',
        'Position': '位置',
        'AI’s Choice': '由AI决定',
        'Character Positions': '角色位置',
        'Global': '全局',
        'Set Character ': '调整角色',
        '’s Position': '的位置',
        'Adjust': '调整',
        'Done': '完成',

        // 其他工具
        'Vibe Transfer': '氛围转移',
        'Change the image, keep the vision.': '改变图像,保留视觉。',
        'Normalize Reference Strength Values': '标准化参考强度值',
        'Imported': '已导入',
        'Vibe Transfer reference image': '张氛围转移参考图片',
        'Enable/Disable Vibe Transfer Reference Image': '启用/禁用氛围转移参考图片',
        'Reference Strength': '参考强度',
        'Information Extracted': '信息提取度',
        'Encoding required. This will cost': '需要编码。这将使下一次生成的成本增加',
        'on the next generation.': '。',
        'Learn more here.': '点击此处了解更多信息。',

        // 图像设置
        'Image Settings': '图像设置',
        'Normal': '中等尺寸',
        'Large': '大型尺寸',
        'Wallpaper': '壁纸(特大尺寸)',
        'Small': '小型尺寸',
        'Custom': '自定义',
        'Portrait': '竖向',
        'Landscape': '横向',
        'Square': '方形',
        'Number of Images': '生成数量',

        // AI设置
        'AI Settings': 'AI设置',
        'Reset Settings': '重置设置',
        'Reset all settings to default': '确实要恢复所有设置为默认状态吗',
        'Yes': '确定',
        'Cancel': '取消',
        'Steps': '步数',
        'Guidance': '引导值',
        'Prompt Guidance': '提示词引导值',
        'Variety': '多样性',
        'Enable guidance only after body has been formed, to improve diversity and saturation of samples. May reduce relevance.': '只应在身体形成后再启用引导功能,可以提高样本的多样性和饱和度。可能会降低相关性。',
        'Seed': '种子',
        'Enter a seed': '输入一个种子',
        'Sampler': '采样器',
        'Recommended': '推荐',
        'Advanced Settings': '高级设置',
        'Prompt Guidance Rescale': '缩放提示词引导值',
        'Noise Schedule': '噪声计划表',
        'N/A': '随机',

        // 生成按钮
        'Free Trial': '免费体验',
        'image generations remaining.': '张图片可生成。',
        'Seen enough': '没用够吗',
        'Check out our plans': '查看我们的计划',
        'Generate 1 Image': '生成1张图像',
        'Generate 2 Images': '生成2张图像',
        'Generate 3 Images': '生成3张图像',
        'Generate 4 Images': '生成4张图像',
    };

    // 创建一个高效的正则表达式,一次性匹配所有需要翻译的词
    // 使用 Object.keys 获取所有要翻译的英文原文
    // 用 | 连接成一个大的 "或" 逻辑的正则
    const regex = new RegExp(Object.keys(translationMap).sort((a, b) => b.length - a.length).join('|'), 'g');

    // 1. 获取所有键并按长度从长到短排序,避免"Account"优先于"Account Settings"被匹配
    // const sortedKeys = Object.keys(translationMap).sort((a, b) => b.length - a.length);

    // 2. 将每个键视为一个独立的单词,用单词边界 \b 包裹
    //    注意:这里我们不能简单地在外面套一层 \b,因为有些键本身可能包含非单词字符(如 'Version ')
    //    更稳妥的方式是为每个“干净”的键添加边界。但为了简单起见,我們先用一个通用方法。
    //    一个更健壮的通用方法是把所有键用 | 连接后,再用括号包起来,然后在这个整体前后加 \b。
    //
    //    注意:在字符串中表示反斜杠 \ 需要写成 \\

    // 创建一个高效的正则表达式,一次性匹配所有需要翻译的词
    // 使用 Object.keys 获取所有要翻译的英文原文
    // 用 | 连接成一个大的 "或" 逻辑的正则
    // const regex = new RegExp('\\b(' + sortedKeys.join('|') + ')\\b', 'g');

    // 替换函数
    function replaceText(node) {
        // 只检查文本节点,并且内容不能是纯空格
        if (node.nodeType !== Node.TEXT_NODE || !node.nodeValue.trim()) {
            return;
        }

        // 使用一个正则表达式一次性替换所有匹配到的词
        // replace函数的第二个参数可以是函数,它会对每个匹配项调用这个函数
        // 'match' 就是匹配到的英文原文(比如 'Account')
        let replacedValue = node.nodeValue.replace(regex, (match) => translationMap[match]);

        // 只有在内容真的发生改变时才去修改 DOM,避免不必要的重绘
        if (node.nodeValue !== replacedValue) {
            node.nodeValue = replacedValue;
        }
    }

    // 遍历函数
    function traverseAndReplace(node) {
        // 创建一个TreeWalker,它能高效地只遍历文本节点!
        const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
        let currentNode;
        while (currentNode = walker.nextNode()) {
            replaceText(currentNode);
        }
    }

    // 修改网页标题函数 (保持不变)
    function modifyPageTitle() {
        if (!isTitleModificationEnabled) {
            return;
        }
        const currentPageUrl = window.location.href;
        if (currentPageUrl.includes('inspect')) {
            document.title = '检视图像参数 - NovelAI';
        } else {
            var originalTitle = document.title;
            document.title = '图像生成 - NovelAI';
            Object.defineProperty(document, 'title', {
                get: function () {
                    return originalTitle;
                },
                set: function (value) {
                    if (value !== originalTitle) {
                        console.log('已拦截标题修改:', value);
                    }
                }
            });
        }
    }

    // 菜单相关函数 (保持不变)
    function updateMenuText() {
        var menuText = isTitleModificationEnabled ? '禁用标题修改' : '启用标题修改';
        // 为了避免重复注册,先清空旧的(虽然在这个脚本里影响不大,但是好习惯)
        if (window.myMenuCommandId) {
            GM_unregisterMenuCommand(window.myMenuCommandId);
        }
        window.myMenuCommandId = GM_registerMenuCommand(menuText, function () {
            isTitleModificationEnabled = !isTitleModificationEnabled;
            GM_setValue('isTitleModificationEnabled', isTitleModificationEnabled);
            alert('标题修改功能' + (isTitleModificationEnabled ? '已启用' : '已禁用') + ',现在页面标题' + (isTitleModificationEnabled ? '无法' : '能够') + '反映图像生成的进度。\n提示:刷新前请确保生成的图像、参数等已经被保存!');
            updateMenuText();
        });
    }
    updateMenuText();

    // 监听DOM变化 (使用优化后的逻辑)
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            // 只关心新添加的节点
            if (mutation.addedNodes.length) {
                mutation.addedNodes.forEach(node => {
                    // 对每个新添加的节点和它的所有后代进行一次高效的文本替换
                    traverseAndReplace(node);
                });
            }
        });
    });

    // 初始化脚本
    function init() {
        // 首次加载时,对整个页面进行一次翻译
        traverseAndReplace(document.body);
        modifyPageTitle();

        // 启动观察器以检测后续的DOM变化
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // 页面加载完成后运行脚本
    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();