// ==UserScript==
// @name Wplace 汉化脚本
// @name:en Wplace Chinese Localization Script
// @name:zh-CN Wplace 汉化脚本
// @namespace http://tampermonkey.net/
// @version 1.2.5
// @description 将网站 wplace.live 的界面实时翻译成中文,优化中文用户的使用体验。
// @description:en Translates the wplace.live interface into Chinese in real-time for a better user experience.
// @description:zh-CN 将网站 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
//
// @homepageURL https://greasyfork.org/zh-CN/scripts/546403
// @supportURL https://greasyfork.org/zh-CN/scripts/546403/feedback
// @source https://greasyfork.org/zh-CN/scripts/546403/code
//
// @grant none
// ==/UserScript==
(function() {
'use strict';
console.log('[Wplace汉化] 脚本开始加载...');
// 创建一个翻译字典,将英文原文映射到中文译文
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": "©\n\t\t\t\t\t\tOpenMapTiles 数据来自",
"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:": "已放置的像素点:",
"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": "商店",
//必须做i8n才能完全解决这个问题
//"Move ↑": "移至上方",
//"Move ↓": "移至下方",
"Eraser": "擦除",
"Color Picker": "拾取像素点颜色",
"Offline": "你已离线",
"Explore": "看看别处",
"Favorite": "收藏",
"Share": "分享",
"Mute": "禁用音效",
"Unmute": "启用音效",
"+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, use a 16x16 image": "长宽以 16x16 为佳",
"Preview:": "预览:",
"Add": "添加",
"Discord Username": "Discord 用户名",
// ========== 弹窗 & 标题 ==========
"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 to 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.": "你没有像素点了。",
"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,": "按键,即可进行绘制。",
"Refresh your page to get the latest update": "站点已更新。请刷新本页面",
"Your account has been banned. Reason: ": "您的账户已被封禁。封禁原因:",
"Your account has been suspended until": "您的账户已被暂时停用。解除停用状态时间为",
"Breaking the rules": "违反本站规则",
". Reason: Inappropriate conent (+18, inappropriate link, highly suggestive content, ...)": "创作不当内容(成人内容、不当链接、强暗示性内容等)",
". Reason: Hate speech (Racism, homophobia, hate groups, ...)": "。封禁原因:发表仇恨言论(发表种族歧视言论、发表恐同言论、隶属于仇恨特定群体的某个团体,或宣扬该团体,等)",
". Reason: Inappropriate content (+18, inappropriate link, highly suggestive content, ...)": "。封禁原因:创作不当内容(成人内容、不当链接、强暗示性内容等)",
". Reason: Doxxing (Released other's personal information without their consent)": "。封禁原因:擅自传播隐私信息(未经同意即公开他人个人信息)",
". Reason: Botting (Use of software to completely automate painting)": "。封禁原因:使用全自动机器人(使用软件放置像素点,且完全无真人参与)",
". Reason: Griefing (Messed up artworks for no reason)": "。封禁原因:恶意涂抹(无故破坏他人艺术作品)",
"You have broken one of Wplace's rules": "您违反了 Wplace 规则之一",
"Your account has been suspended for breaking the rules": "由于违反本站规则,您的账户已被暂时停用",
// ========== 规则列表 ==========
"😈 Do not paint over other artworks using random colors or patterns just to mess things up": "😈 禁止使用随机颜色或图样恶意涂抹他人艺术作品",
"🚫 No inappropriate content (+18, hate speech, inappropriate 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.": "违反上述规则可能会导致你的账户被封禁。",
// ========== 举报功能 ==========
"Report User": "举报该用户",
"Select the reason:": "选择举报理由:",
"Inappropriate content:": "创作不当内容",
"+18, inappropriate link, highly suggestive content, ...": "如成人内容、不当链接、强暗示性内容等",
"Hate speech:": "发表仇恨言论",
"Racism, homophobia, hate groups, ...": "如发表种族歧视言论、发表恐同言论、隶属于仇恨特定群体的某个团体,或宣扬该团体,等",
"Doxxing": "擅自传播隐私信息",
"Released other's personal information without their consent": "未经同意即公开他人个人信息",
"Botting": "使用全自动机器人",
"Use of software to completely automate painting": "使用软件放置像素点,且完全无真人参与",
"Griefing": "恶意涂抹",
"Messed up artworks for no reason": "无故破坏他人艺术作品",
"Other": "其他",
"Other reason not listed": "存在未列于其上的其他理由",
"Multi-accounting": "单人使用多个账户",
"Use more than one account to paint pixels": "使用复数个账户放置像素点",
"Extra context on what happened": "可在此附加所发生事件的具体信息",
"Report": "举报",
"Report sent successfully": "举报已成功发送",
"Report failed. Please try again later": "举报发送失败。请稍后再试。",
// ========== 排行榜 & 筛选 ==========
"Regions": "区域",
"Countries": "国家和地区",
"Players": "用户",
"Alliances": "联盟",
"Today": "今日",
"Week": "本周",
"Month": "本月",
"All time": "总计",
"No pixels painted": "无人于此作画",
"today": "(截至今日)",
// ========== 国家 & 地区 ==========
"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": "米色",
"Light 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": "离开当前联盟",
//倒计时
//不管是正则还是直接写,都没有效果,必须要联系官方做好i18n才可以
/*"Next charge in 0:29": "将于 0:29 后恢复一像素点储备",
"Next charge in 0:28": "将于 0:28 后恢复一像素点储备",
"Next charge in 0:27": "将于 0:79 后恢复一像素点储备",
"Next charge in 0:26": "将于 0:26 后恢复一像素点储备",
"Next charge in 0:25": "将于 0:25 后恢复一像素点储备",
"Next charge in 0:24": "将于 0:24 后恢复一像素点储备",
"Next charge in 0:23": "将于 0:23 后恢复一像素点储备",
"Next charge in 0:22": "将于 0:22 后恢复一像素点储备",
"Next charge in 0:21": "将于 0:21 后恢复一像素点储备",
"Next charge in 0:20": "将于 0:20 后恢复一像素点储备",
"Next charge in 0:19": "将于 0:19 后恢复一像素点储备",
"Next charge in 0:18": "将于 0:18 后恢复一像素点储备",
"Next charge in 0:17": "将于 0:17 后恢复一像素点储备",
"Next charge in 0:16": "将于 0:16 后恢复一像素点储备",
"Next charge in 0:15": "将于 0:15 后恢复一像素点储备",
"Next charge in 0:14": "将于 0:14 后恢复一像素点储备",
"Next charge in 0:13": "将于 0:13 后恢复一像素点储备",
"Next charge in 0:12": "将于 0:12 后恢复一像素点储备",
"Next charge in 0:11": "将于 0:11 后恢复一像素点储备",
"Next charge in 0:10": "将于 0:10 后恢复一像素点储备",
"Next charge in 0:09": "将于 0:09 后恢复一像素点储备",
"Next charge in 0:08": "将于 0:08 后恢复一像素点储备",
"Next charge in 0:07": "将于 0:07 后恢复一像素点储备",
"Next charge in 0:06": "将于 0:06 后恢复一像素点储备",
"Next charge in 0:05": "将于 0:05 后恢复一像素点储备",
"Next charge in 0:04": "将于 0:04 后恢复一像素点储备",
"Next charge in 0:03": "将于 0:03 后恢复一像素点储备",
"Next charge in 0:02": "将于 0:02 后恢复一像素点储备",
"Next charge in 0:01": "将于 0:01 后恢复一像素点储备",*/
// 其他常见文本
"Version": "当前版本",
"Unlock": "需解锁",
"Pixel:": "坐标:",
"Permanently unlock the color": "一次花费,永久使用",
"No country found.": "未搜索到相关国家或地区(仅支持搜索英语世界通行称谓或其变体,如 Viet Nam,Türkiye)",
"Wplace is a collaborative, real-time pixel canvas layered over the world map, where anyone can paint and create art together.": "Wplace 是一片覆盖在世界地图图层之上的协作式实时像素画布,任何人都可以在其上进行绘画,创作艺术。",
//JS 中的文本
"Phone verification required": "需验证您的手机号码",
"Could not install the app:": "未能作为应用安装:",
"Install App": "作为应用安装:",
"Hide UI": "隐藏 UI",
"Change picture": "更换头像",
"Are you absolutely sure?": "是否确定删除账户?",
"This will permanently delete your account and all associated data. This action cannot be undone": "进行该操作后,你的账户将被永久删除,所有相关数据也将一并被抹除。该操作无法被撤销。",
"Does not need to be equipped to provide the bonus": "Does not need to be equipped to provide the bonus",
"Equipped": "已装备",
"Equip": "装备",
"You can paint more than 1 pixel": "你有复数个像素点可用于绘制",
"Not set": "尚未设置",
"You are not in an alliance": "您未加入联盟",
"Get invited to an alliance": "您可以以受邀请的形式加入联盟",
"OR": "也可以",
"Create an alliance": "组建一个联盟",
"Invite link": "联盟邀请链接",
"Send the link below to everybody you want to invite to the alliance": "您可以把下方链接发送给您希望他们加入自己联盟的人",
"Copied": "链接已复制",
"No description": "无描述",
"Invite": "邀请",
"this week": "本周",
"this month": "本月",
"Last pixel": "最后一个像素点",
"Create alliance": "组建联盟",
"Alliance Name": "联盟名称",
"Create": "组建",
"Give admin": "授予其联盟管理身份",
"Ban from alliance": "封禁该成员",
"No action": "无操作",
"Unban": "解封",
"No banned users": "尚无被封禁的成员",
"Update": "更新",
"Error giving admin to user": "授予该成员联盟管理身份时出现错误",
"Users": "Users",
"Banned": "已封禁",
"Limit reached": "已达上限",
"Select the headquarters location": "请选择联盟大本营位置",
"Pixels painted inside the country": "放置于该国家/区域内像素点的总数目",
"You are not allowed to use multiple accounts. Use your main account to paint.": "禁止单人使用多个账户进行绘制。请使用你的主账户。",
"SMS sent to": "已将短信发送至",
"Phone successfully verified": "手机号码验证成功",
"Not a valid phone number": "该手机号码无效",
"Giving admin to user": "正在授予该用户联盟管理身份",
"Profile updated": "已更新个人资料",
"Account successfully deleted": "成功删除账户",
"Could not logout. Try refreshing the page.": "退出登录失败。请尝试刷新页面。",
"Phone verification": "验证手机号码",
"Please verify your phone number to continue playing. This helps us keep bots out and ensure a safe, creative experience for everyone.": "如需继续游玩,请您验证手机号码。该操作有助于我们防范机器人账户,为去全体用户创造安全的,洋溢着创造力的游戏环境",
"Send Code": "发送验证码",
"Input the code": "输入验证码",
"Sent to": "发送至",
"Resend Code": "重新发送",
"Try another number": "请尝试其他手机号码",
"Moderation": "Moderation",
"Clear area": "清理区域",
"Select the area's first corner": "选择需清理区域的一个顶点",
"Select the area's opposite corner": "选择需清理区域的对向顶点",
// chunk 中的 JS 文本
"Unexpected server error. Try again later.": "服务器发生意外错误。请稍后重试。",
"You need to be logged in to paint": "登录本站,即可进行绘画",
"You do not have enough charges to paint. Erase some pixels.": "现有可用像素点不足。请擦除一些像素点。",
"Error while painting:": "绘制时出现错误:",
"Invalid phone number": "该手机号码无效",
"Phone already used": "该手机号码已被使用",
"You have to wait to resend a code": "请稍作等候,再重新发送验证码",
"Invalid code": "验证码错误",
"Operation not allowed. Maybe you have too many favorite locations.": "不允许执行该操作。您收藏的位置可能已达上限。",
"Location name is too big (max. 128 characters)": "位置名称过程(上限为 128 个英语字符)",
"Couldn't complete the purchase. This item does not exist.": "购买失败。该购买项不存在。",
"You do not have enough droplets to buy this item.": "液滴数不足,无法购买该项目。",
"You already have this item. Please refresh the page.": "您已拥有该购买项。请刷新页面。",
"Alliance name exceeded the maximum number of characters": "联盟名称超出最大字符上限",
"Alliance name already taken": "该联盟名称已存在",
"Alliance with empty name": "联盟名称为空",
"You are already in an alliance": "您此前已加入联盟",
"You are not allowed to do this": "您无法做出该操作",
"You or someone in your network is making a lot of requests to the server. Try again later.": "您或与您处于同一网络中的人向服务器发出了过量请求。请稍后再试。",
"We’re currently experiencing high traffic. Some requests may not be processed at this time—please try again later. Thank you for your patience": "本站目前处于流量高峰期,部分请求此时可能无法得到处理,请稍后再试。感谢您的耐心等待。",
"You are not allowed to do this": "您无法做出该操作",
};
console.log('[Wplace汉化] 翻译字典已加载,包含', Object.keys(translations).length, '个条目');
// 标记已翻译的元素,避免重复处理。
// 注意:此属性现在主要用于标记元素自身的*属性*是否已被处理,
// 不再用于阻止对该元素内部*文本节点*的独立检查。
const TRANSLATED_ATTRIBUTE = 'data-wplace-translated';
// 翻译计数器
let translationCount = 0;
/**
* 翻译文本节点
* @param {Node} textNode - 文本节点
*/
function translateTextNode(textNode) {
if (!textNode || textNode.nodeType !== Node.TEXT_NODE) return;
const originalText = textNode.textContent.trim();
if (!originalText) return;
// --- 现有倒计时文本的正则表达式匹配和翻译 ---
const countdownRegex = /^Next charge in (\d+):(\d{2})$/;
const matchCountdown = originalText.match(countdownRegex);
if (matchCountdown) {
const minutes = matchCountdown[1];
const seconds = matchCountdown[2];
const translatedCountdown = `将于 ${minutes}:${seconds} 后恢复一像素点储备`;
if (textNode.textContent !== translatedCountdown) {
textNode.textContent = translatedCountdown;
translationCount++;
console.log('[Wplace汉化] 倒计时翻译:', originalText, '->', translatedCountdown);
}
return;
}
// --- End 现有倒计时部分 ---
// --- 现有动态 “+N Max. Charges” 和 “+N Paint Charges” 文本的正则表达式匹配和翻译 ---
const maxChargesRegex = /^\+(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\sMax\.\sCharges$/;
const matchMaxCharges = originalText.match(maxChargesRegex);
if (matchMaxCharges) {
const amount = matchMaxCharges[1];
const translatedText = `像素点储备上限+${amount}`;
if (textNode.textContent !== translatedText) {
textNode.textContent = translatedText;
translationCount++;
console.log('[Wplace汉化] 动态文本翻译: ', originalText, '->', translatedText);
}
return;
}
const paintChargesRegex = /^\+(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\sPaint\sCharges$/;
const matchPaintCharges = originalText.match(paintChargesRegex);
if (matchPaintCharges) {
const amount = matchPaintCharges[1];
const translatedText = `现有可用像素点立即+${amount}`;
if (textNode.textContent !== translatedText) {
textNode.textContent = translatedText;
translationCount++;
console.log('[Wplace汉化] 动态文本翻译: ', originalText, '->', translatedText);
}
return;
}
// --- End 现有动态 Charges 部分 ---
// --- 新增:像素坐标文本的正则表达式匹配和翻译 ---
// 匹配 "Pixel: X, Y" 格式
const pixelCoordsRegex = /^Pixel:\s*(-?\d+),\s*(-?\d+)$/;
const matchPixelCoords = originalText.match(pixelCoordsRegex);
if (matchPixelCoords) {
const xCoord = matchPixelCoords[1];
const yCoord = matchPixelCoords[2];
const translatedText = `坐标:${xCoord}, ${yCoord}`;
if (textNode.textContent !== translatedText) {
textNode.textContent = translatedText;
translationCount++;
console.log('[Wplace汉化] 像素坐标翻译:', originalText, '->', translatedText);
}
return;
}
// 匹配 "(Tl X: ..., Tl Y: ..., Px X: ..., Px Y: ...)" 格式
// 这里的数字也可能是负数,所以使用 -?\d+
const fullCoordsRegex = /^\(Tl X:\s*(-?\d+),\s*Tl Y:\s*(-?\d+),\s*Px X:\s*(-?\d+),\s*Px Y:\s*(-?\d+)\)$/;
const matchFullCoords = originalText.match(fullCoordsRegex);
if (matchFullCoords) {
const tlX = matchFullCoords[1];
const tlY = matchFullCoords[2];
const pxX = matchFullCoords[3];
const pxY = matchFullCoords[4];
// 这里我们只翻译前缀,保持数字不变,如果需要更复杂的翻译,可以调整
const translatedText = `(TlX: ${tlX}, TlY: ${tlY}, Px X: ${pxX}, Px Y: ${pxY})`;
// 如果希望更简洁,可以将 `瓦片X`, `瓦片Y` 等也定义在字典中,或者直接用变量。
// 考虑到原始信息为 "Tl X", "Px X", 保持原始前缀结合中文释义可能更清晰。
if (textNode.textContent !== translatedText) {
textNode.textContent = translatedText;
translationCount++;
console.log('[Wplace汉化] 完整坐标翻译:', originalText, '->', translatedText);
}
return;
}
// --- End 新增:像素坐标部分 ---
// 原有的字典查找逻辑:仅当原始文本在字典中,且当前文本内容与翻译后的文本不同时才进行翻译
if (translations[originalText] && textNode.textContent !== translations[originalText]) {
textNode.textContent = translations[originalText];
translationCount++;
console.log('[Wplace汉化] 文本翻译:', originalText, '->', translations[originalText]);
}
}
/**
* 翻译元素属性
* @param {Element} element - DOM元素
*/
function translateElementAttributes(element) {
if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
let translatedAnyAttribute = false;
// 翻译title属性
const title = element.getAttribute('title');
if (title && translations[title] && title !== translations[title]) {
element.setAttribute('title', translations[title]);
translatedAnyAttribute = true;
translationCount++;
console.log('[Wplace汉化] 标题翻译:', title, '->', translations[title]);
}
// 翻译placeholder属性
const placeholder = element.getAttribute('placeholder');
if (placeholder && translations[placeholder] && placeholder !== translations[placeholder]) {
element.setAttribute('placeholder', translations[placeholder]);
translatedAnyAttribute = true;
translationCount++;
console.log('[Wplace汉化] 占位符翻译:', placeholder, '->', translations[placeholder]);
}
// 翻译aria-label属性
const ariaLabel = element.getAttribute('aria-label');
if (ariaLabel && translations[ariaLabel] && ariaLabel !== translations[ariaLabel]) {
element.setAttribute('aria-label', translations[ariaLabel]);
translatedAnyAttribute = true;
translationCount++;
console.log('[Wplace汉化] 无障碍标签翻译:', ariaLabel, '->', translations[ariaLabel]);
}
// --- 新增:翻译data-tip属性 ---
const dataTip = element.getAttribute('data-tip');
if (dataTip && translations[dataTip] && dataTip !== translations[dataTip]) {
element.setAttribute('data-tip', translations[dataTip]);
translatedAnyAttribute = true;
translationCount++;
console.log('[Wplace汉化] Data-tip翻译:', dataTip, '->', translations[dataTip]);
}
// --- End 新增部分 ---
// 如果有任何属性被翻译,就标记此元素。
if (translatedAnyAttribute) {
element.setAttribute(TRANSLATED_ATTRIBUTE, 'true');
}
}
/**
* 深度遍历并翻译节点
* @param {Node} node - 要遍历的节点
*/
function translateNode(node) {
if (!node) return;
if (node.nodeType === Node.TEXT_NODE) {
translateTextNode(node);
} else if (node.nodeType === Node.ELEMENT_NODE) {
// 翻译当前元素自身的属性
translateElementAttributes(node);
// 遍历子节点
const walker = document.createTreeWalker(
node,
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
{
acceptNode: function(node) {
// 优化:移除此过滤器。
// 每个文本节点应独立进行翻译检查,不再依赖父元素的 TRANSLATED_ATTRIBUTE。
// translateTextNode 内部的 `textNode.textContent !== translations[originalText]` 检查已足够防止重复。
return NodeFilter.FILTER_ACCEPT;
}
},
false
);
let currentNode = walker.nextNode();
while (currentNode) {
if (currentNode.nodeType === Node.TEXT_NODE) {
translateTextNode(currentNode);
} else if (currentNode.nodeType === Node.ELEMENT_NODE) {
// 对子元素的属性进行翻译。
// 递归调用 translateElementAttributes 已经包含在此处,因为它在 walker 遍历到的每个 Element 节点上都会被调用
translateElementAttributes(currentNode);
}
currentNode = walker.nextNode();
}
}
}
/**
* 翻译页面元数据
*/
function translateMetadata() {
console.log('[Wplace汉化] 开始翻译页面元数据...');
// 设置语言属性
if (document.documentElement) {
document.documentElement.lang = 'zh-CN';
console.log('[Wplace汉化] 页面语言已设置为 zh-CN');
}
// 翻译页面标题
if (document.title && translations[document.title] && document.title !== translations[document.title]) { // Added check for difference
const originalTitle = document.title;
document.title = translations[document.title];
console.log('[Wplace汉化] 页面标题翻译:', originalTitle, '->', document.title);
translationCount++;
}
// 翻译meta标签
const metaSelectors = [
'meta[property="og:title"]',
'meta[name="twitter:title"]',
'meta[name="description"]',
'meta[itemprop="description"]',
'meta[property="og:description"]',
'meta[name="twitter:description"]',
'meta[name="keywords"]',
'meta[name="apple-mobile-web-app-title"]'
];
metaSelectors.forEach(selector => {
const metaElement = document.querySelector(selector);
if (metaElement) {
const content = metaElement.getAttribute('content');
if (content && translations[content] && content !== translations[content]) {
const originalContent = content;
metaElement.setAttribute('content', translations[content]);
console.log('[Wplace汉化] Meta标签翻译:', originalContent, '->', translations[content]);
translationCount++;
}
}
});
}
/**
* 初始化MutationObserver
*/
function initMutationObserver() {
console.log('[Wplace汉化] 初始化DOM变化监听器...');
const observer = new MutationObserver((mutations) => {
let hasChanges = false;
// 优化:避免在每次 mutation 都重新遍历整个 body
// 遍历 mutaions 时,检查 mutation.target 并根据类型决定如何处理
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) {
translateNode(node); // 深度翻译新添加的节点
hasChanges = true;
}
});
} else if (mutation.type === 'attributes') {
// 仅翻译被修改的元素属性
translateElementAttributes(mutation.target);
// translatedAnyAttribute logic inside translateElementAttributes already handles console.log
hasChanges = true;
} else if (mutation.type === 'characterData') {
// 监听文本节点内容的改变
translateTextNode(mutation.target);
hasChanges = true;
}
});
if (hasChanges && translationCount > 0) { // Using translationCount for a more granular log when actual translations happen
console.log('[Wplace汉化] 本轮DOM变化处理完成');
}
});
// 配置观察选项
const config = {
childList: true, // 监听子节点的添加或移除
subtree: true, // 监听所有子孙节点的变化
attributes: true, // 监听属性变化
attributeFilter: ['title', 'placeholder', 'aria-label', 'data-tip'], // 过滤特定属性
characterData: true // 新增:监听文本节点内容的改变
};
// 开始观察
observer.observe(document.body, config);
console.log('[Wplace汉化] DOM变化监听器已启动');
return observer;
}
/**
* 翻译现有内容
*/
function translateExistingContent() {
console.log('[Wplace汉化] 开始翻译页面现有内容...');
const startTime = Date.now();
if (document.body) {
const initialTranslationCount = translationCount; // 记录开始前的总翻译数
translateNode(document.body);
const currentRoundTranslations = translationCount - initialTranslationCount;
const endTime = Date.now();
console.log('[Wplace汉化] 现有内容翻译完成,耗时:', (endTime - startTime), 'ms,本轮翻译项目:', currentRoundTranslations, '累计翻译项目:', translationCount);
}
}
/**
* 主初始化函数
*/
function init() {
console.log('[Wplace汉化] 开始初始化...');
// 立即翻译元数据
translateMetadata();
// 当DOM准备就绪时
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
console.log('[Wplace汉化] DOM内容已加载,开始处理页面内容...');
translateExistingContent();
initMutationObserver();
});
} else {
// DOM已经准备就绪
console.log('[Wplace汉化] DOM已准备就绪,立即处理页面内容...');
translateExistingContent();
initMutationObserver();
}
// 监听页面可见性变化,用于SPA路由切换
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
setTimeout(() => {
console.log('[Wplace汉化] 页面重新可见,检查新内容...');
translateExistingContent();
}, 100);
}
});
// 监听pushstate和popstate事件(SPA路由变化)
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
// 确保 originalPopState 不为 null,因为 window.onpopstate 默认可能是 null
const originalPopState = typeof window.onpopstate === 'function' ? window.onpopstate : null;
history.pushState = function(...args) {
originalPushState.apply(history, args);
setTimeout(() => {
console.log('[Wplace汉化] 路由变化(pushState),重新翻译内容...');
translateExistingContent();
}, 200);
};
history.replaceState = function(...args) {
originalReplaceState.apply(history, args);
setTimeout(() => {
console.log('[Wplace汉化] 路由变化(replaceState),重新翻译内容...');
translateExistingContent();
}, 200);
};
window.onpopstate = function(...args) {
if (originalPopState) { // 仅当原始 onpopstate 存在时才调用
originalPopState.apply(window, args);
}
setTimeout(() => {
console.log('[Wplace汉化] 路由变化(popstate),重新翻译内容...');
translateExistingContent();
}, 200);
};
console.log('[Wplace汉化] 初始化完成');
}
// 启动脚本
init();
// 添加全局状态查询函数(调试用)
window.wplaceTranslator = {
getTranslationCount: () => translationCount,
getTranslations: () => translations,
retranslate: () => {
console.log('[Wplace汉化] 手动重新翻译...');
translateExistingContent();
}
};
console.log('[Wplace汉化] 脚本加载完成!可以通过 window.wplaceTranslator 访问调试功能');
})();