// ==UserScript==
// @name:en [MWI]Recruitment Message Translator:zh-CN
// @name [银河奶牛]组队招募副本信息翻译
// @namespace https://cnb.cool/shenhuanjie/skyner-cn/tamper-monkey-script/mwi-recruitment-translator
// @version 1.0.6
// @description:en Translate in-game recruitment messages to Chinese
// @description 将银河奶牛游戏中的招募信息自动翻译为中文
// @author shenhuanjie
// @license MIT
// @match https://www.milkywayidle.com/*
// @icon https://www.milkywayidle.com/favicon.svg
// @grant none
// @run-at document-end
// @homepage http://greasyfork.icu/zh-CN/scripts/535683
// @supportURL http://greasyfork.icu/zh-CN/scripts/535683
// ==/UserScript==
(function() {
'use strict';
// ========== 全局配置 ==========
const CONFIG = {
enableConsoleLog: false, // 控制台日志开关,默认关闭
caseSensitive: false, // 是否大小写敏感
wholeWordOnly: true, // 是否全词匹配
chatSelector: '.chat-message', // 聊天消息选择器
logLevel: 'INFO', // 日志级别: DEBUG, INFO, WARNING, ERROR
initialScanDelay: 500, // 初始扫描延迟(ms)
rescanInterval: 10000, // 重新扫描间隔(ms)
maxTranslationDepth: 3 // 最大翻译深度
};
// =============================
// ========== 翻译配置 ==========
const TRANSLATIONS = new Map([
// 普通星球副本 - P前缀
["Party: Smelly Planet", "P1:臭臭星球"],
["Party: Swamp Planet", "P2:沼泽星球"],
["Party: Aqua Planet", "P3:海洋星球"],
["Party: Jungle Planet", "P4:丛林星球"],
["Party: Gobo Planet", "P5:哥布林星球"],
["Party: Planet Of The Eyes", "P6:眼球星球"],
["Party: Sorcerer's Tower", "P7:巫师之塔"],
["Party: Bear With It", "P8:熊熊星球"],
["Party: Golem Cave", "P9:魔像洞穴"],
["Party: Twilight Zone", "P10:暮光之地"],
["Party: Infernal Abyss", "P11:地狱深渊"],
// 地下城副本 - D前缀
["Party: Chimerical Den", "D1:奇幻洞穴"],
["Party: Sinister Circus", "D2:阴森马戏团"],
["Party: Enchanted Fortress", "D3:秘法要塞"],
["Party: Pirate Cove", "D4:海盗基地"],
]);
// =============================
// 工具函数:日志记录
function log(message, level = 'INFO') {
if (!CONFIG.enableConsoleLog) return;
if (!['DEBUG', 'INFO', 'WARNING', 'ERROR'].includes(level)) {
level = 'INFO';
}
if (['DEBUG', 'INFO'].includes(level) && level !== CONFIG.logLevel) {
return;
}
const logColor = {
DEBUG: '#888',
INFO: '#2196F3',
WARNING: '#FFC107',
ERROR: '#F44336'
};
console.log(`%c[Translator][${level}] ${message}`, `color: ${logColor[level]}`);
}
// 工具函数:转义正则特殊字符
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// 生成翻译正则表达式模式
function createTranslationPatterns() {
return Array.from(TRANSLATIONS).map(([en, zh]) => {
// 转义原始字符串中的正则特殊字符
const escaped = escapeRegExp(en);
// 使用字符串模板构建正则表达式,支持中英文括号的精英标识
const patternStr = escaped
.replace(/\((Elite)\)/, '(?:\\(|()$1(?:\\)|))')
.replace(/((Elite))/, '(?:\\(|()$1(?:\\)|))');
// 添加全词匹配边界(如果配置为全词匹配)
const fullPattern = CONFIG.wholeWordOnly
? `\\b${patternStr}\\b`
: patternStr;
// 编译正则表达式
const flags = CONFIG.caseSensitive ? 'g' : 'gi';
return {
pattern: new RegExp(fullPattern, flags),
replacement: zh,
original: en
};
}).sort((a, b) => b.pattern.source.length - a.pattern.source.length);
}
// 翻译处理器
function translateTextNode(textNode) {
if (!textNode || !textNode.nodeValue) return false;
let content = textNode.nodeValue;
let translatedContent = content;
let modified = false;
try {
for (const {pattern, replacement, original} of translationPatterns) {
if (pattern.test(translatedContent)) {
const newContent = translatedContent.replace(pattern, replacement);
if (newContent !== translatedContent) {
log(`替换: ${original} → ${replacement}`, 'DEBUG');
translatedContent = newContent;
modified = true;
}
}
}
if (modified) {
textNode.nodeValue = translatedContent;
log(`已翻译节点: ${content.substring(0, 50)}...`, 'INFO');
return true;
}
} catch (error) {
log(`翻译过程中出错: ${error.message}`, 'ERROR');
}
return false;
}
// 检查节点是否为聊天消息
function isChatNode(node) {
return node.nodeType === Node.ELEMENT_NODE &&
node.matches(CONFIG.chatSelector);
}
// 处理单个DOM节点及其子节点
function processNode(node, depth = 0) {
if (depth > CONFIG.maxTranslationDepth) return;
if (isChatNode(node)) {
const walker = document.createTreeWalker(
node,
NodeFilter.SHOW_TEXT,
null,
false
);
let translatedCount = 0;
while (walker.nextNode()) {
if (translateTextNode(walker.currentNode)) {
translatedCount++;
}
}
if (translatedCount > 0) {
log(`在聊天节点中翻译了 ${translatedCount} 处文本`, 'INFO');
}
} else if (node.nodeType === Node.TEXT_NODE) {
// 直接文本节点
translateTextNode(node);
} else if (node.nodeType === Node.ELEMENT_NODE) {
// 递归处理子节点
const childNodes = Array.from(node.childNodes);
childNodes.forEach(child => {
processNode(child, depth + 1);
});
}
}
// 扫描并翻译整个DOM
function scanAndTranslate() {
log('开始扫描DOM...', 'INFO');
const startTime = performance.now();
try {
const chatNodes = document.querySelectorAll(CONFIG.chatSelector);
log(`找到 ${chatNodes.length} 个聊天节点`, 'INFO');
let totalTranslations = 0;
chatNodes.forEach(node => {
const walker = document.createTreeWalker(
node,
NodeFilter.SHOW_TEXT,
null,
false
);
while (walker.nextNode()) {
if (translateTextNode(walker.currentNode)) {
totalTranslations++;
}
}
});
const elapsedTime = performance.now() - startTime;
log(`扫描完成: ${totalTranslations} 处翻译,耗时 ${elapsedTime.toFixed(2)}ms`, 'INFO');
return totalTranslations;
} catch (error) {
log(`扫描过程中出错: ${error.message}`, 'ERROR');
return 0;
}
}
// 初始化函数
function init() {
log('翻译器初始化中...', 'INFO');
try {
// 初始延迟扫描,等待页面完全加载
setTimeout(() => {
log('执行初始DOM扫描...', 'INFO');
const initialTranslations = scanAndTranslate();
log(`初始扫描完成,翻译了 ${initialTranslations} 处文本`, 'INFO');
// 动态监听DOM变化
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
// 处理新增节点
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
processNode(node);
// 处理子节点
const childWalker = document.createTreeWalker(
node,
NodeFilter.SHOW_ELEMENT,
null,
false
);
while (childWalker.nextNode()) {
processNode(childWalker.currentNode);
}
} else if (node.nodeType === Node.TEXT_NODE) {
processNode(node);
}
});
} else if (mutation.type === 'characterData') {
// 处理文本内容变更
const textNode = mutation.target;
if (textNode && textNode.parentNode) {
if (isChatNode(textNode.parentNode)) {
translateTextNode(textNode);
}
}
}
});
});
// 开始监听DOM变化
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
log('翻译器已启动并监听DOM变化', 'INFO');
}, CONFIG.initialScanDelay);
// 定期重新扫描整个DOM
setInterval(() => {
scanAndTranslate();
}, CONFIG.rescanInterval);
log(`翻译器配置: 大小写敏感=${CONFIG.caseSensitive}, 全词匹配=${CONFIG.wholeWordOnly}, 扫描间隔=${CONFIG.rescanInterval/1000}s`, 'INFO');
} catch (error) {
log(`初始化失败: ${error.message}`, 'ERROR');
}
}
// 预编译翻译模式
const translationPatterns = createTranslationPatterns();
// 启动脚本
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// 打印初始状态信息
if (CONFIG.enableConsoleLog) {
console.log('%c[Translator] 翻译器已加载,控制台日志已开启', 'color: #2196F3');
} else {
console.log('%c[Translator] 翻译器已加载,控制台日志已关闭', 'color: #888');
}
})();