// ==UserScript== // @name 文达-Futbin Futgg中文翻译插件 - WonderFut // @namespace http://tampermonkey.net/ // @version 0.83 // @description 在常用的FC UT攻略网站 fut.gg、futbin.com 和 easysbc.io 上自动为指定名词添加中文翻译 // @author (开发)御羽卓一 yuyuzhuoyi & (翻译)FC冰红茶 // @match https://fut.gg/* // @match https://www.fut.gg/* // @match https://www.futbin.com/* // @match https://futbin.com/* // @match https://www.easysbc.io/* // @match https://easysbc.io/* // @match https://www.ea.com/ea-sports-fc/ultimate-team/web-app/* // @grant none // @license CC BY-NC 4.0 // @downloadURL https://update.greasyfork.icu/scripts/556098/%E6%96%87%E8%BE%BE-Futbin%20Futgg%E4%B8%AD%E6%96%87%E7%BF%BB%E8%AF%91%E6%8F%92%E4%BB%B6%20-%20WonderFut.user.js // @updateURL https://update.greasyfork.icu/scripts/556098/%E6%96%87%E8%BE%BE-Futbin%20Futgg%E4%B8%AD%E6%96%87%E7%BF%BB%E8%AF%91%E6%8F%92%E4%BB%B6%20-%20WonderFut.meta.js // ==/UserScript== (function() { 'use strict'; // ==================== 翻译词典配置 ==================== // 版本 0.3:支持专业术语和玩家常用说法两种模式 // 翻译模式开关:true = 使用玩家常用说法,false = 使用专业术语 const usePlayerSlang = true; // 修改此值为 true 可切换为玩家常用说法 // 是否在中文后面显示原文和括号 // true => "中文 (English)" / "民间叫法(官方翻译)" // false => 只显示中文或民间叫法,不带括号和原文 const showOriginalWithBrackets = false; // 翻译词典:每个词条包含专业术语和玩家常用说法两种翻译 const translationDict = { // 化学 'Wall': { professional: '铜墙铁壁', playerSlang: '铜墙铁壁' }, 'Glove': { professional: '手套', playerSlang: '手套' }, 'Shield': { professional: '盾', playerSlang: '盾' }, 'Cat': { professional: '猫', playerSlang: '猫' }, 'Anchor': { professional: '中流砥柱', playerSlang: '锚' }, 'Architect': { professional: '建筑师', playerSlang: '建筑师' }, 'Artist': { professional: '艺术家', playerSlang: '艺术家' }, 'Backbone': { professional: '主心骨', playerSlang: '主心骨' }, 'Basic': { professional: '基础', playerSlang: '基础' }, 'Catalyst': { professional: '催化剂', playerSlang: '催化剂' }, 'Deadeye': { professional: '神枪手', playerSlang: '死眼' }, 'Engine': { professional: '发动机', playerSlang: '引擎' }, 'Finisher': { professional: '终结者', playerSlang: '终结者' }, 'Gladiator': { professional: '角斗士', playerSlang: '角斗士' }, 'Guardian': { professional: '守护者', playerSlang: '守护者' }, 'Hawk': { professional: '鹰', playerSlang: '鹰' }, 'Hunter': { professional: '猎手', playerSlang: '猎手' }, 'Maestro': { professional: '大师', playerSlang: '大师' }, 'Marksman': { professional: '神射手', playerSlang: '神射手' }, 'Powerhouse': { professional: '发动机', playerSlang: '发动机' }, 'Sentinel': { professional: '哨兵', playerSlang: '哨兵' }, 'Shadow': { professional: '影子', playerSlang: '影子' }, 'Sniper': { professional: '狙击手', playerSlang: '狙击手' }, // 比赛风格/技能 'Finesse Shot': { professional: '推射', playerSlang: '搓射' }, 'Chip Shot': { professional: '吊射', playerSlang: '吊射' }, 'Power Shot': { professional: '大力射门', playerSlang: '大力射门' }, 'Dead Ball': { professional: '死球', playerSlang: '定位球射门' }, 'Precision Header': { professional: '精准头球', playerSlang: '头球射门' }, 'Acrobatic': { professional: '杂耍', playerSlang: '花式射门' }, 'Low Driven Shot': { professional: '大力低射', playerSlang: '低射' }, 'Game Changer': { professional: '颠覆者', playerSlang: '外脚背射门' }, 'Gamechanger': { professional: '颠覆者', playerSlang: '外脚背射门' }, 'Whipped Pass': { professional: '弧线传中', playerSlang: '弧线传中' }, 'Inventive': { professional: '别出心裁(独辟蹊径)', playerSlang: '花哨传球' }, 'Tiki Taka': { professional: 'Tiki Taka', playerSlang: 'Tiki Taka' }, 'Long Ball Pass': { professional: '远距离传球', playerSlang: '长传' }, 'Pinged Pass': { professional: '大力传球', playerSlang: '大力传球' }, 'Incisive Pass': { professional: '切入传球', playerSlang: '直塞' }, 'Jockey': { professional: '跟防', playerSlang: '螃蟹步' }, 'Block': { professional: '封堵', playerSlang: '封堵' }, 'Intercept': { professional: '拦截', playerSlang: '钩子' }, 'Anticipate': { professional: '预判', playerSlang: '狐狸' }, 'Slide Tackle': { professional: '滑铲', playerSlang: '滑铲' }, 'Aerial Fortress': { professional: '空中堡垒', playerSlang: '空中堡垒' }, 'Technical': { professional: '技术', playerSlang: '游龙' }, 'Rapid': { professional: '灵动迅捷', playerSlang: '跑跑' }, 'First Touch': { professional: '第一脚触球', playerSlang: '磁铁' }, 'Trickster': { professional: '诡术师', playerSlang: '魔术师' }, 'Press Proven': { professional: '紧逼好手', playerSlang: '紧逼好手' }, 'Quick Step': { professional: '健步如飞', playerSlang: '火箭' }, 'Relentless': { professional: '坚持不懈', playerSlang: '电池' }, 'Long Throw': { professional: '远距离界外球', playerSlang: '手抛球' }, 'Bruiser': { professional: '斗士', playerSlang: '肌肉' }, 'Enforcer': { professional: '执行者', playerSlang: '护球盘带' }, 'Far Throw': { professional: '远距离抛球', playerSlang: '门将抛球' }, 'Footwork': { professional: '步法', playerSlang: '用脚扑救' }, 'Cross Claimer': { professional: '传中没收者', playerSlang: '传中拦截' }, 'Rush Out': { professional: '1对1紧逼', playerSlang: '出击' }, 'Far Reach': { professional: '远距离出击', playerSlang: '拦远射' }, 'Deflector': { professional: '偏转', playerSlang: '击球出界' } }; // 角色词条列表,供 futbin 页面做定向匹配(从Google Sheet动态加载) let roleTerms = new Set(); const isFutbinHost = location.hostname.indexOf('futbin.com') !== -1; const futbinPlayerPathRegex = /^\/\d+\/player\/\d+/; function isFutbinPlayerDetailsPage() { return isFutbinHost && futbinPlayerPathRegex.test(location.pathname); } function isTextNodeInsideFutbinRoleList(textNode) { if (!isFutbinPlayerDetailsPage()) { return false; } if (!textNode || textNode.nodeType !== Node.TEXT_NODE) { return false; } const parentElement = textNode.parentElement; if (!parentElement) { return false; } const roleAnchor = parentElement.closest('a[href*="/roles"]'); if (!roleAnchor) { return false; } const rowWrapper = roleAnchor.closest('.xxs-row.align-center'); const listContainer = roleAnchor.closest('.xs-row.flex-wrap'); return Boolean(rowWrapper && listContainer); } function canTranslateRoleTermInNode(textNode) { if (!isFutbinHost) { return true; } return isTextNodeInsideFutbinRoleList(textNode); } // 化学风格词条列表,只在 futbin 球员详情页特定位置翻译 const chemistryStyleTerms = new Set([ 'Wall', 'Glove', 'Shield', 'Cat', 'Anchor', 'Architect', 'Artist', 'Backbone', 'Basic', 'Catalyst', 'Deadeye', 'Engine', 'Finisher', 'Gladiator', 'Guardian', 'Hawk', 'Hunter', 'Maestro', 'Marksman', 'Powerhouse', 'Sentinel', 'Shadow', 'Sniper' ]); // 仅允许在球员详情页中示例 HTML 所示的三处区域翻译化学风格英文 function canTranslateChemistryStyleInNode(textNode) { if (!isFutbinPlayerDetailsPage()) { return false; } if (!textNode || textNode.nodeType !== Node.TEXT_NODE) { return false; } const parentElement = textNode.parentElement; if (!parentElement) { return false; } // 示例 1:当前已选化学风格(div.xxs-row.align-center.bold 内,带 chemistryIcon 的 svg 旁的文本) const chemistryHeader = parentElement.closest('div.xxs-row.align-center.bold'); if (chemistryHeader && chemistryHeader.querySelector('svg.chemistryIcon')) { return true; } // 示例 2/3:化学风格按钮列表(button.chem-style-button.chem-style-type 内,带 chemistryIcon 的 svg 旁的文本) const chemistryButton = parentElement.closest('button.chem-style-button.chem-style-type'); if (chemistryButton && chemistryButton.querySelector('svg.chemistryIcon')) { return true; } return false; } // 根据开关选择当前使用的翻译词典 const translations = {}; for (const [english, translations_obj] of Object.entries(translationDict)) { translations[english] = usePlayerSlang ? translations_obj.playerSlang : translations_obj.professional; } // ==================== 翻译词典配置结束 ==================== const sixStatSheetUrl = 'https://docs.google.com/spreadsheets/d/11MELa9ps6Eulr-82bw_gXP5owuV4J6HYrGBOlxH44_E/export?format=csv&gid=0'; // futbin 球员详情页 - 基本信息(Skills / Weak Foot / Height / Foot / Age / Squad 等)翻译表 const futbinBasicInfoSheetUrl = 'https://docs.google.com/spreadsheets/d/11MELa9ps6Eulr-82bw_gXP5owuV4J6HYrGBOlxH44_E/export?format=csv&gid=1427260659'; const futbinRoleSheetUrl = 'https://docs.google.com/spreadsheets/d/11MELa9ps6Eulr-82bw_gXP5owuV4J6HYrGBOlxH44_E/export?format=csv&gid=757113362'; const roleSheetUrl = 'https://docs.google.com/spreadsheets/d/11MELa9ps6Eulr-82bw_gXP5owuV4J6HYrGBOlxH44_E/export?format=csv&gid=1284075031'; let sixStatMap = null; let sixStatLoaded = false; let sixStatLoading = false; let futbinBasicInfoMap = null; let futbinBasicInfoLoaded = false; let futbinBasicInfoLoading = false; let futbinRoleMap = null; let futbinRoleLoaded = false; let futbinRoleLoading = false; let roleMap = null; let roleLoaded = false; let roleLoading = false; // 已处理的元素集合,避免重复处理 const processedElements = new WeakSet(); // 根据是否显示原文/括号来格式化最终展示文本 function formatWithOptionalOriginal(mainText, originalText, options = {}) { const { noSpaceBeforeBracket = false } = options; if (!showOriginalWithBrackets || !originalText) { return mainText; } // 默认中文和括号之间带空格;部分地方(如“火箭(健步如飞)”)不需要空格 const space = noSpaceBeforeBracket ? '' : ' '; return `${mainText}${space}(${originalText})`; } // 安全转义用于正则的特殊字符 function escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // futbin 球员详情页 - 阵容 / 卡面系列翻译加载状态(例如 Team of the Week / TOTW 等) let futbinSquadMap = null; let futbinSquadLoaded = false; let futbinSquadLoading = false; function loadSixStatTranslations() { if (sixStatLoaded || sixStatLoading) { return; } if (location.hostname.indexOf('futbin.com') === -1) { sixStatLoaded = true; sixStatMap = {}; return; } sixStatLoading = true; fetch(sixStatSheetUrl) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.text(); }) .then(text => { const map = {}; const lines = text.split(/\r?\n/); for (let i = 1; i < lines.length; i++) { const line = lines[i]; if (!line) { continue; } const parts = line.split(','); if (parts.length < 2) { continue; } const english = (parts[0] || '').trim(); const official = (parts[1] || '').trim(); const slang = (parts[2] || '').trim(); if (!english) { continue; } let chinese = ''; if (usePlayerSlang) { chinese = slang || official; } else { chinese = official || slang; } if (!chinese) { continue; } map[english] = chinese; } sixStatMap = map; sixStatLoaded = true; if (Object.keys(sixStatMap).length > 0) { applySixStatTranslations(document); } }) .catch(() => { sixStatLoaded = true; }); } // 载入 futbin 球员详情页「基本信息」翻译(Skills / Weak Foot / Height / Foot / Age / Squad 等) function loadFutbinBasicInfoTranslations() { if (futbinBasicInfoLoaded || futbinBasicInfoLoading) { return; } if (!isFutbinHost) { futbinBasicInfoLoaded = true; futbinBasicInfoMap = {}; return; } futbinBasicInfoLoading = true; fetch(futbinBasicInfoSheetUrl) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.text(); }) .then(text => { const map = {}; const lines = text.split(/\r?\n/); // 忽略第一行(标题行) for (let i = 1; i < lines.length; i++) { const line = lines[i]; if (!line) { continue; } const parts = line.split(','); if (parts.length < 2) { continue; } // 第一列是原文,第二列是官译,第三列是俗称 const english = (parts[0] || '').trim().replace(/^"|"$/g, ''); const official = (parts[1] || '').trim().replace(/^"|"$/g, ''); const slang = (parts[2] || '').trim().replace(/^"|"$/g, ''); if (!english) { continue; } let chinese = ''; if (usePlayerSlang) { chinese = slang || official; } else { chinese = official || slang; } if (!chinese) { continue; } map[english] = chinese; } futbinBasicInfoMap = map; futbinBasicInfoLoaded = true; // 数据加载完成后,立即尝试翻译当前页面已存在的 Basic Info 区块 if (Object.keys(futbinBasicInfoMap).length > 0) { applyFutbinBasicInfoTranslations(document); } }) .catch(() => { futbinBasicInfoLoaded = true; }); } // 载入 futbin 球员详情页「阵容 / 卡面系列」(Team of the Week / TOTW 等)翻译 // 对应 Google Sheet:gid=1197755959 function loadFutbinSquadTranslations() { if (futbinSquadLoaded || futbinSquadLoading) { return; } if (!isFutbinHost || !isFutbinPlayerDetailsPage()) { futbinSquadLoaded = true; futbinSquadMap = {}; return; } futbinSquadLoading = true; fetch('https://docs.google.com/spreadsheets/d/11MELa9ps6Eulr-82bw_gXP5owuV4J6HYrGBOlxH44_E/export?format=csv&gid=1197755959') .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.text(); }) .then(text => { const map = {}; const lines = text.split(/\r?\n/); // 忽略第一行(标题行) for (let i = 1; i < lines.length; i++) { const line = lines[i]; if (!line) { continue; } const parts = line.split(','); if (parts.length < 2) { continue; } // 第一列是原文,第二列是官译,第三列是俗称 const english = (parts[0] || '').trim().replace(/^"|"$/g, ''); const official = (parts[1] || '').trim().replace(/^"|"$/g, ''); const slang = (parts[2] || '').trim().replace(/^"|"$/g, ''); if (!english) { continue; } let chinese = ''; if (usePlayerSlang) { chinese = slang || official; } else { chinese = official || slang; } if (!chinese) { continue; } map[english] = chinese; } futbinSquadMap = map; futbinSquadLoaded = true; if (Object.keys(futbinSquadMap).length > 0) { // 初次加载完就对整页做一遍阵容翻译 applyFutbinSquadTranslations(document); } }) .catch(() => { futbinSquadLoaded = true; }); } function applySixStatTranslations(root) { if (!sixStatLoaded || !sixStatMap || !Object.keys(sixStatMap).length) { return; } if (!root) { root = document; } if (location.hostname.indexOf('futbin.com') === -1) { return; } const elements = root.querySelectorAll('div.player-stat-name.text-ellipsis'); elements.forEach(el => { if (!el || el.dataset.sixStatTranslated === '1') { return; } const text = (el.textContent || '').trim(); if (!text) { return; } const chinese = sixStatMap[text]; if (!chinese) { return; } // 六维属性名称,根据配置决定是否保留原文和括号 el.textContent = formatWithOptionalOriginal(chinese, text); el.dataset.sixStatTranslated = '1'; }); } // futbin 球员详情页 - 基本信息(Skills / Weak Foot / Height / Foot / Age / Squad 等)翻译 // futbin 球员详情页 - 阵容 / 卡面系列(Team of the Week / TOTW9 等)翻译 function applyFutbinSquadTranslations(root) { if (!futbinSquadLoaded || !futbinSquadMap || !Object.keys(futbinSquadMap).length) { return; } if (!root) { root = document; } if (!isFutbinPlayerDetailsPage()) { return; } // 帮助函数:把文字按「英文 + 可选尾部数字」拆开,然后查翻译 function translateSquadText(text) { const original = (text || '').trim(); if (!original) { return null; } // 把 "TOTW9" 分成 base="TOTW",suffix="9" const match = original.match(/^([A-Za-z ]+?)(\d+)?$/); let base = original; let suffix = ''; if (match) { base = match[1].trim(); suffix = match[2] || ''; } const chineseBase = futbinSquadMap[base]; if (!chineseBase) { return null; } const main = suffix ? chineseBase + suffix : chineseBase; // 是否保留原文由全局开关控制 return formatWithOptionalOriginal(main, original); } // 示例 1:页面顶部版本列表里的「Team of the Week」 // Team of the Week const versionSpans = root.querySelectorAll( 'a.xxs-row.align-center.green-text.xs-font.bold span.text-ellipsis' ); versionSpans.forEach(span => { if (!span || span.dataset.futbinSquadTranslated === '1') { return; } const newText = translateSquadText(span.textContent); if (!newText) { return; } span.textContent = newText; span.dataset.futbinSquadTranslated = '1'; }); // 示例 2:player-info 基本信息里的 Squad 行(例如 "TOTW9") //