// ==UserScript== // @name Wplace 汉化脚本 // @namespace http://tampermonkey.net/ // @version 1.1.0 // @description 将网站 wplace.live 的界面翻译成中文。 // @author Avava_Ava & AI Optimized // @match https://wplace.live/* // @license MIT // @run-at document-body // @icon https://wplace.live/img/favicon-96x96.png // @namespace https://greasyfork.org/zh-CN/scripts/546403-wplace-%E6%B1%89%E5%8C%96%E6%8F%92%E4%BB%B6 // @homepageURL https://greasyfork.org/zh-CN/scripts/546403-wplace-%E6%B1%89%E5%8C%96%E6%8F%92%E4%BB%B6 // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 创建一个翻译字典,将英文原文映射到中文译文 const translations = { // ========== 页面标题与信息 ========== "Wplace - Paint the world": "Wplace-描绘这世界", "Paint the world": "描绘这世界", "Wplace": "Wplace", "Feedback and bugs": "提供反馈或汇报问题", "Map powered by:": "地图技术由以下项目提供:", "©\n\t\t\t\t\t\tOpenMapTiles Data from": "OpenMapTiles 数据来自", "Overview": "概览", "How to paint faster": "如何画得快一些", "Hold": "按住", "and move your cursor over the map.": ",随后在地图上移动鼠标。", "When painting, click on the button": "在绘制时,点击位于屏幕右上角上的", "on the top right corner of the screen. This will lock the screen but it'll also enable painting by moving your finger over the map.": "按钮。点击后屏幕将锁定;此时在屏幕上滑动即可进行绘制。", "My map is lagging": "我的地图画面卡顿严重", "Verify if": "(如果使用的是 Chrome 浏览器)请查看", "is enabled on": "选项是否开启。该选项位于", "Email:": "邮箱:", "Terms": "服务条款", "Privacy": "隐私政策", "Show profile": "显示个人资料", // ========== 文本 ========== "Paint pixel": "已放置的像素点数", "Pixels painted": "放置像素点总数", "Pixels": "放置像素点总数", "painted": " ", "Region": "区域", "Pixels painted inside the region": "放置于该区域内像素点的总数目", "Visit": "去看看", "Menu": "菜单", // ========== 按钮 & 提示 ========== "Info": "信息", "Zoom in": "放大", "Zoom out": "缩小", "Livestreams": "Twitch 相关直播", "Refresh": "刷新页面", "Previous Location": "前一个地点", "Toggle art opacity": "切换作品透明度", "My location": "我的当前位置", "Paint": "绘制", "Close": "关闭", "Understood": "了解", "Store": "商店", //"Move ↑": "移至上方", //"Move ↓": "移至下方", "Eraser": "擦除", "Color Picker": "拾取像素点颜色", "Offline": "你已离线", "Explore": "看看别处", "Favorite": "收藏", "Share": "分享", "Mute ": "禁用音效", "+2 max. charge/level": "每升一级,像素点储备上限+2", "Purchases": "支付信息", "Log Out": "退出登录", "Alliance": "联盟", "Log in": "登录", "Edit profile": "编辑个人资料", "Name": "昵称", "Show last painted pixel on alliance": "在「联盟」页面显示自己最后放置的像素点", "Delete Account": "删除账户", "Save": "保存", "Add profile picture": "添加头像", "Draw profile picture": "绘制头像", "Upload": "上传", "Preferably a 16x16 image": "长宽以 16x16 为佳", "Preview:": "预览:", "Add": "添加", // ========== 弹窗 & 标题 ========== "Max. Charges": "最大像素点储备", "Welcome to": "欢迎来到", "Rules": "规则", "Important": "重要", "📑 Updated rules": "规则(已更新)", "Leaderboard": "排行榜", "PIX": "像素点", "Location favorited": "已收藏该位置", "Location unfavorited": "已取消收藏该位置", "Share place": "分享该位置", "Image": "图像", "Copy": "复制", "Download": "下载", "No internet access or the servers are offline. Try again later.": "当前无网络连接,或本站服务器已离线。请稍后再试。", "Can't reach the server. Maybe you are without internet connection or the server is down. Try again later": "无法同服务器进行通讯。可能当前无网络连接,或本站服务器已离线。请稍后再试。", "You need zoom in to select a pixel": "进一步放大地图方可点选像素点", "Painted by:": "由该用户绘制:", "Not painted": "从未被绘制过", "Username copied to clipboard": "已将用户名复制至剪贴板", "Zoom in to see the pixels": "放大地图即可看到像素点", "Logged out": "已退出登录", "Login with Google": "通过 Google 账号进行登录", "Login with Twitch": "通过 Twitch 账号进行登录", "By continuing, you agree to our": "继续操作,即表明您同意我们的", "Terms of Service": "服务条款", "and": "和", "Privacy Policy": "隐私政策", "Leaderboard is temporarily disabled": "排行榜功能已暂时停用", "No more charges": "像素点储备不足", "You don't have charges to paint. Wait to recharge.": "你没有像素点了。请等待像素点回复。", "Pick a color from the map": "在地图上拾取一像素点的颜色", "Select a pixel to erase": "选择需要擦除的像素", "Click": "点击", "or hold": "或按住", "to paint,": "按键,即可进行绘制。", // ========== 规则列表 ========== "😈 Do not paint over other artworks using random colors or patterns just to mess things up": "😈 禁止使用随机颜色或图样恶意涂抹他人艺术作品", "🚫 No inappropriate content (+18, hate speech, innapropriate links, highly suggestive material, ...)": "🚫 禁止绘制不当内容(如成人内容、仇恨言论、不当链接、强暗示性内容等)", "🧑‍🤝‍🧑 Do not paint with more than one account": "🧑‍🤝‍🧑 禁止单人使用多个账户进行绘制", "🤖 Use of bots is not allowed": "🤖 禁止使用机器人", "🙅 Disclosing other's personal information is not allowed": "🙅 禁止泄露他人个人信息", "✅ Painting over other artworks to complement them or create a new drawing is allowed": "✅ 允许在他人作品上进行补充创作或绘制新内容", "✅ Griefing political party flags or portraits of politicians is allowed": "✅ 允许涂抹政党旗帜或政治人物肖像", "Violations of these rules may result in suspension of your account.": "违反上述规则可能会导致你的账户被封禁。", // ========== 排行榜 & 筛选 ========== "Regions": "区域", "Countries": "国家和地区", "Players": "用户", "Alliances": "联盟", "Today": "今日", "Week": "本周", "Month": "本月", "All time": "总计", // ========== 国家 & 地区 ========== "Country": "国家和地区", "Afghanistan": "阿富汗", "Åland Islands": "奥兰群岛", "Albania": "阿尔巴尼亚", "Algeria": "阿尔及利亚", "American Samoa": "美属萨摩亚", "Andorra": "安道尔", "Angola": "安哥拉", "Anguilla": "安圭拉", "Antarctica": "南极洲", "Antigua and Barbuda": "安提瓜和巴布达", "Argentina": "阿根廷", "Armenia": "亚美尼亚", "Aruba": "阿鲁巴", "Australia": "澳大利亚", "Austria": "奥地利", "Azerbaijan": "阿塞拜疆", "Bahamas": "巴哈马", "Bahrain": "巴林", "Bangladesh": "孟加拉国", "Barbados": "巴巴多斯", "Belarus": "白俄罗斯", "Belgium": "比利时", "Belize": "伯利兹", "Benin": "贝宁", "Bermuda": "百慕大", "Bhutan": "不丹", "Bolivia": "玻利维亚", "Bonaire": "博奈尔", "Bosnia and Herzegovina": "波斯尼亚和黑塞哥维那", "Botswana": "博茨瓦纳", "Bouvet Island": "布韦岛", "Brazil": "巴西", "British Indian Ocean Territory": "英属印度洋领地", "British Virgin Islands": "英属维尔京群岛", "Brunei Darussalam": "文莱", "Bulgaria": "保加利亚", "Burkina Faso": "布基纳法索", "Burundi": "布隆迪", "Cambodia": "柬埔寨", "Cameroon": "喀麦隆", "Canada": "加拿大", "Canary Islands": "加那利群岛", "Cabo Verde": "佛得角", "Cayman Islands": "开曼群岛", "Central African Republic": "中非共和国", "Chad": "乍得", "Chile": "智利", "China": "中国", "Christmas Island": "圣诞岛", "Cocos (Keeling) Islands": "科科斯(基林)群岛", "Colombia": "哥伦比亚", "Comoros": "科摩罗", "Republic of the Congo": "刚果(金)", "Congo": "刚果(布)", "Cook Islands": "库克群岛", "Costa Rica": "哥斯达黎加", "Côte d'Ivoire": "科特迪瓦", "Croatia": "克罗地亚", "Cuba": "古巴", "Curaçao": "库拉索", "Cyprus": "塞浦路斯", "Czechia": "捷克共和国", "Denmark": "丹麦", "Djibouti": "吉布提", "Dominica": "多米尼克", "Dominican Republic": "多米尼加共和国", "Ecuador": "厄瓜多尔", "Egypt": "埃及", "El Salvador": "萨尔瓦多", "Equatorial Guinea": "赤道几内亚", "Eritrea": "厄立特里亚", "Estonia": "爱沙尼亚", "Eswatini": "斯威士兰", "Ethiopia": "埃塞俄比亚", "Falkland Islands (Malvinas)": "福克兰群岛(马尔维纳斯)", "Faroe Islands": "法罗群岛", "Fiji": "斐济", "Finland": "芬兰", "France": "法国", "French Guiana": "法属圭亚那", "French Polynesia": "法属波利尼西亚", "French Southern Territories": "法属南部和南极领地", "Gabon": "加蓬", "Gambia": "冈比亚", "Georgia": "格鲁吉亚", "Germany": "德国", "Ghana": "加纳", "Gibraltar": "直布罗陀", "Greece": "希腊", "Greenland": "格陵兰", "Grenada": "格林纳达", "Guadeloupe": "瓜德罗普", "Guam": "关岛", "Guatemala": "危地马拉", "Guernsey": "根西", "Guinea": "几内亚", "Guinea-Bissau": "几内亚比绍", "Guyana": "圭亚那", "Haiti": "海地", "Heard Island and McDonald Islands": "赫德岛和麦克唐纳群岛", "Honduras": "洪都拉斯", "Hong Kong": "中国香港", "Hungary": "匈牙利", "Iceland": "冰岛", "India": "印度", "Indonesia": "印度尼西亚", "Iran": "伊朗", "Iraq": "伊拉克", "Ireland": "爱尔兰", "Isle of Man": "马恩岛", "Israel": "以色列", "Italy": "意大利", "Jamaica": "牙买加", "Japan": "日本", "Jersey": "泽西", "Jordan": "约旦", "Kazakhstan": "哈萨克斯坦", "Kenya": "肯尼亚", "Kiribati": "基里巴斯", "Kosovo": "科索沃", "Kuwait": "科威特", "Kyrgyzstan": "吉尔吉斯斯坦", "Laos": "老挝", "Latvia": "拉脱维亚", "Lebanon": "黎巴嫩", "Lesotho": "莱索托", "Liberia": "利比里亚", "Libya": "利比亚", "Liechtenstein": "列支敦士登", "Lithuania": "立陶宛", "Luxembourg": "卢森堡", "Macao": "中国澳门", "Madagascar": "马达加斯加", "Malawi": "马拉维", "Malaysia": "马来西亚", "Maldives": "马尔代夫", "Mali": "马里", "Malta": "马耳他", "Marshall Islands": "马绍尔群岛", "Martinique": "马提尼克", "Mauritania": "毛里塔尼亚", "Mauritius": "毛里求斯", "Mayotte": "马约特", "Mexico": "墨西哥", "Micronesia": "密克罗尼西亚", "Moldova": "摩尔多瓦", "Monaco": "摩纳哥", "Mongolia": "蒙古", "Montenegro": "黑山", "Montserrat": "蒙特塞拉特", "Morocco": "摩洛哥", "Mozambique": "莫桑比克", "Myanmar": "缅甸", "Namibia": "纳米比亚", "Nauru": "瑙鲁", "Nepal": "尼泊尔", "Netherlands": "荷兰", "New Caledonia": "新喀里多尼亚", "New Zealand": "新西兰", "Nicaragua": "尼加拉瓜", "Niger": "尼日尔", "Nigeria": "尼日利亚", "Niue": "纽埃", "Norfolk Island": "诺福克岛", "North Korea": "朝鲜", "North Macedonia": "北马其顿", "Northern Mariana Islands": "北马里亚纳群岛", "Norway": "挪威", "Oman": "阿曼", "Pakistan": "巴基斯坦", "Palau": "帕劳", "Palestine": "巴勒斯坦", "Panama": "巴拿马", "Papua New Guinea": "巴布亚新几内亚", "Paraguay": "巴拉圭", "Peru": "秘鲁", "Philippines": "菲律宾", "Pitcairn": "皮特凯恩群岛", "Poland": "波兰", "Portugal": "葡萄牙", "Puerto Rico": "波多黎各", "Qatar": "卡塔尔", "Réunion": "留尼汪", "Romania": "罗马尼亚", "Russia": "俄罗斯", "Rwanda": "卢旺达", "Saint Barthélemy": "圣巴泰勒米", "Saint Helena": "圣赫勒拿", "Saint Kitts and Nevis": "圣基茨和尼维斯", "Saint Lucia": "圣卢西亚", "Saint Martin (French part)": "法属圣马丁", "Saint Pierre and Miquelon": "圣皮埃尔和密克隆", "Saint Vincent and the Grenadines": "圣文森特和格林纳丁斯", "Samoa": "萨摩亚", "San Marino": "圣马力诺", "Sao Tome and Principe": "圣多美和普林西比", "Saudi Arabia": "沙特阿拉伯", "Senegal": "塞内加尔", "Serbia": "塞尔维亚", "Seychelles": "塞舌尔", "Sierra Leone": "塞拉利昂", "Singapore": "新加坡", "Sint Maarten (Dutch part)": "荷属圣马丁", "Slovakia": "斯洛伐克", "Slovenia": "斯洛文尼亚", "Solomon Islands": "所罗门群岛", "Somalia": "索马里", "South Africa": "南非", "South Georgia and the South Sandwich Islands": "南乔治亚和南桑威奇群岛", "South Korea": "韩国", "South Sudan": "南苏丹", "Spain": "西班牙", "Sri Lanka": "斯里兰卡", "Sudan": "苏丹", "Suriname": "苏里南", "Svalbard and Jan Mayen": "斯瓦尔巴和扬马延", "Sweden": "瑞典", "Switzerland": "瑞士", "Syrian Arab Republic": "叙利亚", "Taiwan": "中国台湾", "Tajikistan": "塔吉克斯坦", "Tanzania": "坦桑尼亚", "Thailand": "泰国", "Timor-Leste": "东帝汶", "Togo": "多哥", "Tokelau": "托克劳", "Tonga": "汤加", "Trinidad and Tobago": "特立尼达和多巴哥", "Tunisia": "突尼斯", "Türkiye": "土耳其", "Turkmenistan": "土库曼斯坦", "Turks and Caicos Islands": "特克斯和凯科斯群岛", "Tuvalu": "图瓦卢", "U.S. Virgin Islands": "美属维尔京群岛", "Uganda": "乌干达", "Ukraine": "乌克兰", "United Arab Emirates": "阿拉伯联合酋长国", "United Kingdom": "英国", "United States": "美国", "United States Minor Outlying Islands": "美国本土外小岛屿", "Uruguay": "乌拉圭", "Uzbekistan": "乌兹别克斯坦", "Vanuatu": "瓦努阿图", "Vatican City": "梵蒂冈", "Venezuela": "委内瑞拉", "Viet Nam": "越南", "Virgin Islands": "维尔京群岛", "Wallis and Futuna": "瓦利斯和富图纳", "Western Sahara": "西撒哈拉", "Yemen": "也门", "Zambia": "赞比亚", "Zimbabwe": "津巴布韦", // ========== 颜色 ========== "Black": "黑色", "Dark Gray": "深灰色", "Gray": "灰色", "Medium Gray": "中灰色", "Light Gray": "浅灰色", "White": "白色", "Deep Red": "深红色", "Dark Red": "暗红色", "Red": "红色", "Light Red": "浅红色", "Dark Orange": "暗橙色", "Orange": "橙色", "Gold": "金色", "Yellow": "黄色", "Light Yellow": "浅黄色", "Dark Goldenrod": "暗金菊色", "Goldenrod": "金菊色", "Light Goldenrod": "浅金菊色", "Dark Olive": "深橄榄色", "Olive": "橄榄色", "Light Olive": "浅橄榄色", "Dark Green": "深绿色", "Green": "绿色", "Light Green": "浅绿色", "Dark Teal": "深鸭绿色", "Teal": "鸭绿色", "Light Teal": "浅鸭绿色", "Dark Cyan": "深青色", "Cyan": "青色", "Light Cyan": "浅青色", "Dark Blue": "深蓝色", "Blue": "蓝色", "Light Blue": "浅蓝色", "Dark Indigo": "深靛色", "Indigo": "靛色", "Light Indigo": "浅靛色", "Dark Slate Blue": "暗岩蓝色", "Slate Blue": "岩蓝色", "Light Slate Blue": "浅岩蓝色", "Dark Purple": "深紫色", "Purple": "紫色", "Light Purple": "浅紫色", "Dark Pink": "深粉红色", "Pink": "粉红色", "Light Pink": "浅粉红色", "Dark Peach": "深桃色", "Peach": "桃色", "Light Peach": "桃色", "Dark Brown": "深棕色", "Brown": "棕色", "Light Brown": "浅棕色", "Dark Tan": "深日晒色", "Tan": "日晒色", "Light Tan": "浅日晒色", "Dark Beige": "深米色", "Beige": "米色", "Dark Stone": "深岩棕色", "Stone": "岩棕色", "Light Stone": "浅岩棕色", "Dark Slate": "深岩灰色", "Slate": "岩灰色", "Light Slate": "浅岩灰色", "Transparent": "透明", // ========== 商店页面 & 联盟页面 ========== "Droplets": "小液滴", //"+5 Max. Charges": "像素点储备上限+5", "Increase your maximum paint charges capacity": "让你能够储备更多的像素点", //"+30 Paint Charges": "现有可用像素点立即+30", "Recharge paint charges": "恢复你的像素点储备", "MAX": "最大", "Profile": "个人资料", "Profile picture": "头像", "Add a new 16x16 profile picture": "新建一张大小为 16x16 的头像", "Flags": "旗帜", "Display your country’s flag next to your username. Plus, when painting in regions where you own the corresponding flag, you recover 10% of the charges spent.": "在自己的用户名一旁展示自己所属国家的旗帜。此外,在该旗帜对应区域境内进行绘制,将返还所消耗像素点的10%。", "Show more": "更多", "Show less": "收起", "Items": "购买项", "Get more charges": "让自己储备多一些像素点", "You gain 1 droplet per pixel painted and 500 droplets per level": "每放置一个像素点,你将获得一颗小液滴;每升一级,你将获得 500 颗小液滴", "Not enough droplets": "液滴数不足", "+0 bonus": "无赠送", "75,000 Droplets": "75000 颗小液滴", "+3,750 bonus": ",购买即多赠 3750 颗", "150,000 Droplets": "150000 颗小液滴", "+15,000 bonus": ",购买即多赠 15000 颗", "250,000 Droplets": "250000 颗小液滴", "+37,500 bonus": ",购买即多赠 37500 颗", "375,000 Droplets": "375000 颗小液滴", "+75,000 bonus": ",购买即多赠 75000 颗", "500,000 Droplets": "500000 颗小液滴", "+125,000 bonus": ",购买即多赠 125000 颗", "Members:": "成员总数:", "Headquarters:": "大本营坐标:", "Player": "成员", "Leave alliance": "离开当前联盟", // 其他常见文本 "Version": "当前版本", "Unlock": "需解锁", "Pixel:": "坐标:", "Permanently unlock the color": "一次花费,永久使用", "Wplace is a collaborative, real-time pixel canvas layered over the world map, where anyone can paint and create art together.": "Wplace 是一片覆盖在世界地图图层之上的协作式实时像素画布,任何人都可以在其上进行绘画,创作艺术。" }; // 记录元素的原始文本,用于检测变化 const originalTexts = new WeakMap(); const clickingButtons = new WeakSet(); // 专门监控移动按钮的函数 - 新增 function monitorMoveButton() { const moveButton = document.getElementById('bm-t'); if (moveButton) { const currentText = moveButton.textContent.trim(); if (translations[currentText]) { moveButton.textContent = translations[currentText]; } // 特别处理Move按钮的点击事件 if (!moveButton.hasAttribute('data-move-listener')) { moveButton.setAttribute('data-move-listener', 'true'); moveButton.addEventListener('click', function() { // 立即设置为正确的中文文本 const immediateTranslate = () => { const text = moveButton.textContent.trim(); if (translations[text]) { moveButton.textContent = translations[text]; } }; // 多次快速检查,防止原文闪现 setTimeout(immediateTranslate, 0); setTimeout(immediateTranslate, 1); setTimeout(immediateTranslate, 5); setTimeout(immediateTranslate, 10); setTimeout(immediateTranslate, 20); setTimeout(immediateTranslate, 50); setTimeout(immediateTranslate, 100); setTimeout(immediateTranslate, 200); }); } } } // 专门监控主要绘制按钮的函数 function monitorPaintButton() { const paintButtonSelectors = [ 'button.btn-primary.btn-lg', 'button.btn-primary.btn-xl', 'button[class*="btn-primary"][class*="btn-lg"]', 'button[class*="btn-primary"][class*="btn-xl"]', 'button.relative.z-30', ]; paintButtonSelectors.forEach(selector => { const buttons = document.querySelectorAll(selector); buttons.forEach(button => { const hasPaintIcon = button.querySelector('svg path[d*="240-120q-45"]') || button.querySelector('svg path[d*="M240-120"]'); if (hasPaintIcon) { translateComplexButton(button); } const buttonText = button.textContent.trim(); if (buttonText.includes('Paint') || buttonText === 'Paint') { translateComplexButton(button); } }); }); } // 翻译复杂结构的按钮 function translateComplexButton(button) { if (!button) return; const walker = document.createTreeWalker( button, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { const parent = node.parentElement; if (parent && ['SCRIPT', 'STYLE'].includes(parent.tagName)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } } ); const textNodes = []; let node; while (node = walker.nextNode()) { textNodes.push(node); } textNodes.forEach(textNode => { const text = textNode.textContent.trim(); if (text && translations[text]) { const leadingWhitespace = textNode.textContent.match(/^\s*/)[0]; const trailingWhitespace = textNode.textContent.match(/\s*$/)[0]; textNode.textContent = leadingWhitespace + translations[text] + trailingWhitespace; } }); translateAttributes(button); } // 专门监控像素信息面板的函数 function monitorPixelInfoPanel() { const pixelInfoSelectors = [ '.text-base-content\\/80.mt-1.px-3.text-sm', '.text-base-content\\/80', '[class*="text-base-content"]', '.rounded-t-box .text-sm', ]; pixelInfoSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(element => { const text = element.textContent.trim(); if (text && translations[text]) { element.textContent = translations[text]; } for (let child of element.childNodes) { if (child.nodeType === Node.TEXT_NODE) { translateTextNode(child); } } }); }); } // 其他翻译函数保持不变 function translateText(text) { return translations[text] || text; } function translateTextNode(node) { if (!node || node.nodeType !== Node.TEXT_NODE) return; let text = node.textContent; const trimmedText = text.trim(); // 处理动态坐标文本 "Pixel: X, Y" -> "坐标:X,Y" const pixelCoordRegex = /^Pixel:\s*(\d+),\s*(\d+)$/; const pixelCoordMatch = trimmedText.match(pixelCoordRegex); if (pixelCoordMatch) { const leadingWhitespace = text.match(/^\s*/)[0]; const trailingWhitespace = text.match(/\s*$/)[0]; const x = pixelCoordMatch[1]; const y = pixelCoordMatch[2]; node.textContent = leadingWhitespace + `坐标:${x},${y}` + trailingWhitespace; return; } // 处理动态商店购买项文本 // 匹配 "+数字 Max. Charges" -> "像素点储备上限 +数字" const maxChargesRegex = /^\+([0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)\s+Max\.\s+Charges$/; const maxChargesMatch = trimmedText.match(maxChargesRegex); if (maxChargesMatch) { const leadingWhitespace = text.match(/^\s*/)[0]; const trailingWhitespace = text.match(/\s*$/)[0]; const number = maxChargesMatch[1]; node.textContent = leadingWhitespace + `像素点储备上限 +${number}` + trailingWhitespace; return; } // 匹配 "+数字 Paint Charges" -> "现有可用像素点立即 +数字" const paintChargesRegex = /^\+([0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)\s+Paint\s+Charges$/; const paintChargesMatch = trimmedText.match(paintChargesRegex); if (paintChargesMatch) { const leadingWhitespace = text.match(/^\s*/)[0]; const trailingWhitespace = text.match(/\s*$/)[0]; const number = paintChargesMatch[1]; node.textContent = leadingWhitespace + `现有可用像素点立即 +${number}` + trailingWhitespace; return; } // 匹配中文动态文本(防止重复翻译) const chineseMaxChargesRegex = /^像素点储备上限\s*\+([0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)$/; const chinesePaintChargesRegex = /^现有可用像素点立即\s*\+([0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)$/; if (chineseMaxChargesRegex.test(trimmedText) || chinesePaintChargesRegex.test(trimmedText)) { // 已经是中文,不需要翻译 return; } // 原有的静态翻译逻辑 if (trimmedText && translations[trimmedText]) { const leadingWhitespace = text.match(/^\s*/)[0]; const trailingWhitespace = text.match(/\s*$/)[0]; node.textContent = leadingWhitespace + translations[trimmedText] + trailingWhitespace; } } function translateAttributes(element) { if (!element || element.nodeType !== Node.ELEMENT_NODE) return; const attributesToTranslate = ['title', 'aria-label', 'data-tip', 'placeholder']; attributesToTranslate.forEach(attr => { const value = element.getAttribute(attr); if (value && translations[value]) { element.setAttribute(attr, translations[value]); } }); } function translateElement(element) { if (!element) return; if (element.tagName && ['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(element.tagName)) { return; } translateAttributes(element); for (let child of element.childNodes) { if (child.nodeType === Node.TEXT_NODE) { translateTextNode(child); } } } function translateAllElements(element) { if (!element) return; const walker = document.createTreeWalker( element, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, { acceptNode: function(node) { if (node.nodeType === Node.ELEMENT_NODE && ['SCRIPT', 'STYLE', 'NOSCRIPT'].includes(node.tagName)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } } ); const nodes = []; let node; while (node = walker.nextNode()) { nodes.push(node); } nodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { translateElement(node); } }); } // 检测按钮文本变化(增强版) function monitorButtonTextChanges() { const buttons = document.querySelectorAll('button, input[type="button"], [role="button"], .btn'); buttons.forEach(button => { const currentText = button.textContent.trim(); if (translations[currentText] && !clickingButtons.has(button)) { translateElement(button); originalTexts.set(button, { original: currentText, translated: translations[currentText] }); } if (currentText.includes('Paint')) { translateComplexButton(button); } }); // 专门检查移动按钮 - 新增 monitorMoveButton(); } // 监听DOM变化(增强版) function observeChanges() { const observer = new MutationObserver(mutations => { const immediateElements = []; let hasPixelInfoChange = false; let hasPaintButtonChange = false; let hasMoveButtonChange = false; // 新增 mutations.forEach(mutation => { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { immediateElements.push(node); if (node.classList && ( node.classList.contains('rounded-t-box') || node.querySelector('.rounded-t-box') || node.textContent.includes('Pixel:') || node.textContent.includes('Not painted') )) { hasPixelInfoChange = true; } if (node.classList && ( node.classList.contains('btn-primary') || node.querySelector('.btn-primary') || node.textContent.includes('Paint') )) { hasPaintButtonChange = true; } // 检查移动按钮变化 - 新增 if (node.id === 'bm-t' || node.querySelector('#bm-t') || node.textContent.includes('Move') || node.textContent.includes('移至')) { hasMoveButtonChange = true; } } }); } if (mutation.type === 'characterData') { const parent = mutation.target.parentElement; if (parent) { immediateElements.push(parent); const text = mutation.target.textContent; if (text && (text.includes('Not painted') || text.includes('Painted by'))) { hasPixelInfoChange = true; } if (text && text.includes('Paint')) { hasPaintButtonChange = true; } // 检查移动按钮文本变化 - 新增 if (text && (text.includes('Move') || text.includes('移至'))) { hasMoveButtonChange = true; } } } if (mutation.type === 'attributes') { const target = mutation.target; if (['title', 'aria-label', 'data-tip', 'placeholder'].includes(mutation.attributeName)) { immediateElements.push(target); } } }); // 立即处理所有变化 immediateElements.forEach(element => { if (element.parentNode) { translateAllElements(element); } }); // 如果检测到相关变化,立即进行专门监控 if (hasPixelInfoChange) { setTimeout(monitorPixelInfoPanel, 10); setTimeout(monitorPixelInfoPanel, 50); setTimeout(monitorPixelInfoPanel, 150); } if (hasPaintButtonChange) { setTimeout(monitorPaintButton, 10); setTimeout(monitorPaintButton, 50); setTimeout(monitorPaintButton, 150); } // 新增:移动按钮专门监控 if (hasMoveButtonChange) { setTimeout(monitorMoveButton, 0); setTimeout(monitorMoveButton, 1); setTimeout(monitorMoveButton, 5); setTimeout(monitorMoveButton, 10); setTimeout(monitorMoveButton, 20); setTimeout(monitorMoveButton, 50); setTimeout(monitorMoveButton, 100); } setTimeout(monitorButtonTextChanges, 10); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['title', 'aria-label', 'data-tip', 'placeholder'], characterData: true }); return observer; } // 监听页面交互(增强版) function setupInteractionListeners() { document.addEventListener('click', function(event) { // 特殊处理移动按钮点击 - 新增 if (event.target.id === 'bm-t' || event.target.closest('#bm-t')) { setTimeout(monitorMoveButton, 0); setTimeout(monitorMoveButton, 1); setTimeout(monitorMoveButton, 5); setTimeout(monitorMoveButton, 10); setTimeout(monitorMoveButton, 20); setTimeout(monitorMoveButton, 50); setTimeout(monitorMoveButton, 100); setTimeout(monitorMoveButton, 200); setTimeout(monitorMoveButton, 300); } setTimeout(() => { const contentAreas = document.querySelectorAll( '.modal, .dialog, .popup, .menu, .dropdown-content, .tooltip, ' + '[role="dialog"], [role="menu"], [role="tabpanel"], .rounded-t-box' ); contentAreas.forEach(area => { translateAllElements(area); }); monitorButtonTextChanges(); monitorPixelInfoPanel(); monitorPaintButton(); monitorMoveButton(); // 新增 }, 10); setTimeout(() => { monitorPixelInfoPanel(); monitorPaintButton(); monitorMoveButton(); // 新增 translateAllElements(document.body); }, 100); setTimeout(() => { monitorPixelInfoPanel(); monitorPaintButton(); monitorMoveButton(); // 新增 }, 300); setTimeout(() => { monitorPixelInfoPanel(); monitorPaintButton(); monitorMoveButton(); // 新增 }, 500); }, true); // 专门监听可能的像素点击 document.addEventListener('mousedown', function(event) { setTimeout(monitorPixelInfoPanel, 20); setTimeout(monitorPixelInfoPanel, 100); setTimeout(monitorPixelInfoPanel, 300); // 也检查绘制按钮和移动按钮 setTimeout(monitorPaintButton, 20); setTimeout(monitorPaintButton, 100); setTimeout(monitorMoveButton, 20); // 新增 setTimeout(monitorMoveButton, 100); // 新增 }, true); } // 高频监控(增强版) function startHighFrequencyMonitoring() { let highFreqCount = 0; const highFreqInterval = setInterval(() => { highFreqCount++; monitorButtonTextChanges(); monitorPixelInfoPanel(); monitorPaintButton(); monitorMoveButton(); // 新增 const dynamicSelectors = [ '.modal', '.dialog', '.popup', '.tooltip', '.dropdown', '[role="tabpanel"]', '.tab-content', '.menu', '.overlay', '.rounded-t-box' ]; dynamicSelectors.forEach(selector => { document.querySelectorAll(selector).forEach(element => { translateAllElements(element); }); }); if (highFreqCount >= 60) { clearInterval(highFreqInterval); // 转为中频监控 setInterval(() => { monitorButtonTextChanges(); monitorPixelInfoPanel(); monitorPaintButton(); monitorMoveButton(); // 新增 }, 1000); } }, 500); } // 页面加载阶段的多次检查 function setupLoadTimeChecks() { // 立即检查 monitorPaintButton(); monitorMoveButton(); // 新增 // DOM内容加载后 setTimeout(() => { monitorPaintButton(); monitorMoveButton(); // 新增 translateAllElements(document.body); }, 50); // 稍后检查(处理异步加载) setTimeout(() => { monitorPaintButton(); monitorMoveButton(); // 新增 translateAllElements(document.body); }, 200); setTimeout(() => { monitorPaintButton(); monitorMoveButton(); // 新增 }, 500); setTimeout(() => { monitorPaintButton(); monitorMoveButton(); // 新增 }, 1000); setTimeout(() => { monitorPaintButton(); monitorMoveButton(); // 新增 }, 2000); } // 按钮点击监控 function setupButtonClickMonitoring() { document.addEventListener('mousedown', function(event) { const target = event.target; if (target.tagName === 'BUTTON' || target.tagName === 'INPUT' || target.role === 'button' || target.classList.contains('btn')) { clickingButtons.add(target); const beforeClickText = target.textContent.trim(); // 特殊处理移动按钮 - 新增 if (target.id === 'bm-t') { const rapidTranslate = () => { const text = target.textContent.trim(); if (translations[text]) { target.textContent = translations[text]; } }; // 极快速的多次翻译,防止闪烁 setTimeout(rapidTranslate, 0); setTimeout(rapidTranslate, 1); setTimeout(rapidTranslate, 2); setTimeout(rapidTranslate, 5); setTimeout(rapidTranslate, 10); setTimeout(rapidTranslate, 15); setTimeout(rapidTranslate, 20); setTimeout(rapidTranslate, 30); setTimeout(rapidTranslate, 50); setTimeout(rapidTranslate, 75); setTimeout(rapidTranslate, 100); setTimeout(rapidTranslate, 150); setTimeout(rapidTranslate, 200); setTimeout(rapidTranslate, 300); } setTimeout(() => { const afterClickText = target.textContent.trim(); if (translations[afterClickText] && afterClickText !== beforeClickText) { if (target.classList.contains('btn-primary') && ( target.classList.contains('btn-lg') || target.classList.contains('btn-xl') )) { translateComplexButton(target); } else { translateElement(target); } } clickingButtons.delete(target); }, 50); setTimeout(() => { const finalText = target.textContent.trim(); if (translations[finalText] || finalText.includes('Paint')) { if (target.classList.contains('btn-primary') && ( target.classList.contains('btn-lg') || target.classList.contains('btn-xl') )) { translateComplexButton(target); } else { translateElement(target); } } }, 150); } }, true); } function translatePageTitle() { if (document.title && translations[document.title]) { document.title = translations[document.title]; } } // 初始化函数(增强版) function init() { console.log('Wplace 汉化插件初始化中...'); translatePageTitle(); translateAllElements(document.body); // 立即检查主要按钮 monitorPaintButton(); monitorMoveButton(); // 新增 setTimeout(() => { translateAllElements(document.body); monitorButtonTextChanges(); monitorPixelInfoPanel(); monitorPaintButton(); monitorMoveButton(); // 新增 }, 100); setTimeout(() => { translateAllElements(document.body); monitorButtonTextChanges(); monitorPixelInfoPanel(); monitorPaintButton(); monitorMoveButton(); // 新增 }, 500); const observer = observeChanges(); setupButtonClickMonitoring(); setupInteractionListeners(); startHighFrequencyMonitoring(); setupLoadTimeChecks(); setInterval(translatePageTitle, 2000); console.log('Wplace 汉化插件已启动 (v1.1.0)'); window.addEventListener('beforeunload', () => { if (observer) observer.disconnect(); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();