Nexusmods Localization

Localization support for Nexus Mods. Built-in Simplified Chinese. Supports Excel-based custom translation.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Nexusmods Localization
// @name:zh-CN   Nexus Mods 本地化
// @namespace    https://github.com/saiyajiang/Nexusmods-Localization
// @version      0.3.3
// @description  Localization support for Nexus Mods. Built-in Simplified Chinese. Supports Excel-based custom translation.
// @description:zh-CN  Nexus Mods 网站本地化,内置简体中文,支持 Excel 自定义翻译
// @author       saiyajiang
// @license      MIT
// @homepageURL  https://github.com/saiyajiang/Nexusmods-Localization
// @supportURL   https://github.com/saiyajiang/Nexusmods-Localization/issues
// @match        https://www.nexusmods.com/*
// @match        https://nexusmods.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @run-at       document-idle
// @antifeature  adult-content 此脚本运行于包含成人内容的网站(Nexus Mods)
// ==/UserScript==

/**
 * Nexusmods-Localization — 油猴脚本
 * @license MIT
 *
 * ⚠️  声明:本项目代码由 AI 辅助生成。
 *
 * 原项目参考:https://github.com/SychO3/nexusmods-chinese (v0.2.2, MIT License)
 *
 * 架构说明:
 *   翻译词条存储在 GM_setValue 中(持久化),来源:
 *     1. 内置默认词条(脚本代码中的 DEFAULT_TRANSLATIONS)
 *     2. 用户导入的 Excel/CSV 词条(覆盖默认值)
 *   用户可通过油猴菜单导入/导出 CSV 文件来自定义翻译。
 */

(function () {
  'use strict';

  // ═══════════════════════════════════════════════
  //  存储键名
  // ═══════════════════════════════════════════════
  const STORAGE_KEY = 'nx_translations';       // 所有翻译词条 { english: chinese }
  const CUSTOM_KEY = 'nx_custom_translations';  // 仅用户自定义的词条
  const DATE_L10N_KEY = 'nx_date_l10n';
  const LANG_KEY = 'nx_lang';  // 语言偏好:'zh-CN' | 'en' | 'auto'
  const TIME24_KEY = 'nx_time24';  // 时间格式:true=24小时制, false=12小时制(AM/PM)

  // ═══════════════════════════════════════════════
  //  语言检测与选择
  //  auto  → 检测 navigator.language,zh 开头用中文,否则英文(不翻译)
  //  zh-CN → 强制简体中文
  //  en    → 强制英文(跳过所有翻译)
  // ═══════════════════════════════════════════════
  function resolveLanguage() {
    const pref = GM_getValue(LANG_KEY, 'auto');
    if (pref === 'zh-CN') return 'zh-CN';
    if (pref === 'en') return 'en';
    // auto: 检测浏览器/系统语言
    const nav = (navigator.language || navigator.userLanguage || 'en').toLowerCase();
    return nav.startsWith('zh') ? 'zh-CN' : 'en';
  }

  const CURRENT_LANG = resolveLanguage();

  // ═══════════════════════════════════════════════
  //  Toast 通知(替代 alert/confirm)
  // ═══════════════════════════════════════════════
  function showToast(msg, type = 'info', duration = 2500) {
    const colors = {
      info:    'background:#2563eb;color:#fff',
      success: 'background:#16a34a;color:#fff',
      error:   'background:#dc2626;color:#fff',
      warning: 'background:#d97706;color:#fff',
    };
    let container = document.getElementById('nx-toast-container');
    if (!container) {
      container = document.createElement('div');
      container.id = 'nx-toast-container';
      container.style.cssText = 'position:fixed;top:16px;right:16px;z-index:2147483647;display:flex;flex-direction:column;gap:8px;pointer-events:none;';
      document.body.appendChild(container);
    }
    const el = document.createElement('div');
    el.textContent = msg;
    el.style.cssText = `${colors[type] || colors.info};padding:10px 18px;border-radius:8px;font-size:14px;line-height:1.5;box-shadow:0 4px 12px rgba(0,0,0,.3);opacity:0;transition:opacity .3s;pointer-events:auto;max-width:360px;word-break:break-word;`;
    container.appendChild(el);
    requestAnimationFrame(() => { el.style.opacity = '1'; });
    setTimeout(() => {
      el.style.opacity = '0';
      setTimeout(() => el.remove(), 300);
    }, duration);
  }

  function showConfirm(msg, onConfirm, onCancel) {
    let container = document.getElementById('nx-toast-container');
    if (!container) {
      container = document.createElement('div');
      container.id = 'nx-toast-container';
      container.style.cssText = 'position:fixed;top:16px;right:16px;z-index:2147483647;display:flex;flex-direction:column;gap:8px;pointer-events:none;';
      document.body.appendChild(container);
    }
    const box = document.createElement('div');
    box.style.cssText = 'background:#1e293b;color:#f1f5f9;padding:16px 20px;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.4);font-size:14px;line-height:1.6;max-width:360px;pointer-events:auto;';
    const msgEl = document.createElement('div');
    msgEl.textContent = msg;
    msgEl.style.marginBottom = '12px';
    const btns = document.createElement('div');
    btns.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;';
    const btnConfirm = document.createElement('button');
    btnConfirm.textContent = '确定';
    btnConfirm.style.cssText = 'padding:6px 16px;border-radius:6px;border:none;cursor:pointer;font-size:13px;background:#dc2626;color:#fff;';
    const btnCancel = document.createElement('button');
    btnCancel.textContent = '取消';
    btnCancel.style.cssText = 'padding:6px 16px;border-radius:6px;border:1px solid #475569;cursor:pointer;font-size:13px;background:transparent;color:#94a3b8;';
    btnConfirm.onclick = () => { box.remove(); onConfirm && onConfirm(); };
    btnCancel.onclick = () => { box.remove(); onCancel && onCancel(); };
    btns.appendChild(btnCancel);
    btns.appendChild(btnConfirm);
    box.appendChild(msgEl);
    box.appendChild(btns);
    container.appendChild(box);
  }
  const IGNORE_SELECTORS = [
    '.mod_description_container',
    '.prose-lexical.prose',
    '.collection_description',
    '.changelog',
    '[data-no-i18n]',
    'script', 'style', 'textarea', 'input',
    // Material Icons 字体图标:DOM 文本是图标名(如 close/menu),不能翻译否则字体渲染失败
    '.material-icons', '.material-icons-outlined', '.material-icons-round',
    '.material-icons-sharp', '.material-icons-two-tone', '.material-symbols-outlined',
    '.material-symbols-rounded', '.material-symbols-sharp',
  ];
  // 扫描专用排除区域(只排除确定不该翻译的整块区域)
  const SCAN_IGNORE_SELECTORS = [
    // 翻译排除区(继承)
    ...IGNORE_SELECTORS,
    // Footer 版权区
    'footer', '[class*="footer"]',
    // 评论区
    '.comment-body', '.comment-content',
  ];
  const TRANSLATABLE_ATTRS = ['placeholder', 'title', 'aria-label', 'data-tooltip'];

  // ═══════════════════════════════════════════════
  //  内置默认翻译词条
  //  格式:{ "English text": "中文翻译" }
  //  不分页面,全局匹配,简单粗暴但可靠
  //
  //  正则模板词条(REGEXP_TRANSLATIONS):
  //  匹配带变量的文本,如 "Welcome back, xxx" → "欢迎回来, xxx"
  //  格式:[正则, 替换函数]
  // ═══════════════════════════════════════════════
  const REGEXP_TRANSLATIONS = [
    // Welcome back, {username} — 完整版
    [/^Welcome back,\s*(.+)$/i, (m) => `欢迎回来,${m[1]}`],
    // Welcome back, — 半截版(用户名在不同文本节点)
    [/^Welcome back,$/i, () => '欢迎回来,'],
    // Welcome, {username}
    [/^Welcome,\s*(.+)$/i, (m) => `欢迎,${m[1]}`],
    // {count} new notifications
    [/^(\d+)\s+new notifications?$/i, (m) => `${m[1]} 条新通知`],
    // {count} new messages
    [/^(\d+)\s+new messages?$/i, (m) => `${m[1]} 条新消息`],
    // {count} mods updated
    [/^(\d+)\s+mods? updated$/i, (m) => `${m[1]} 个模组已更新`],
    // {count} comments
    [/^(\d+)\s+comments?$/i, (m) => `${m[1]} 条评论`],
    // {count} endorsements
    [/^(\d+)\s+endorsements?$/i, (m) => `${m[1]} 次认可`],
    // {count} downloads
    [/^(\d+)\s+downloads?$/i, (m) => `${m[1]} 次下载`],
    // {count} views
    [/^(\d+)\s+views?$/i, (m) => `${m[1]} 次浏览`],
    // {count} followers
    [/^(\d+)\s+followers?$/i, (m) => `${m[1]} 个关注者`],
    // {count} results(支持逗号分隔:104,844 results)
    [/^([\d,]+)\s+results?$/i, (m) => `${m[1]} 个结果`],
    // Uploaded by {author}
    [/^Uploaded by\s+(.+)$/i, (m) => `上传者:${m[1]}`],
    // Uploaded by(无作者名,碎片文本)
    [/^Uploaded by$/i, () => '上传者:'],
    // Created by {author}
    [/^Created by\s+(.+)$/i, (m) => `创建者:${m[1]}`],
    // Updated {time} ago
    [/^Updated\s+(\d+\s+\w+\s+ago)$/i, (m) => `更新于 ${m[1]}`],
    // Posted {time} ago
    [/^Posted\s+(\d+\s+\w+\s+ago)$/i, (m) => `发布于 ${m[1]}`],
    // {count} mods in collection
    [/^(\d+)\s+mods? in (?:this )?collection$/i, (m) => `${m[1]} 个模组在此合集中`],
    // {game} - {count} mods
    [/^-\s*(\d+)\s+mods?$/i, (m) => `- ${m[1]} 个模组`],
    // by {author}
    [/^by\s+(.+)$/i, (m) => `由 ${m[1]}`],
    // {count}k (如 10.1k 认可数)
    [/^(\d+\.?\d*)k$/i, (m) => `${m[1]}k`],
    // Mark all as read (N) — 通知面板按钮碎片
    [/^Mark all as read \((\d+)\)$/i, (m) => `全部标为已读 (${m[1]})`],
    // {count} items
    [/^(\d+)\s+items?$/i, (m) => `${m[1]} 个条目`],
    // Category: {name}
    [/^Category:\s*(.+)$/i, (m) => `分类:${m[1]}`],
    // You haven't downloaded this mod yet
    [/^You haven't downloaded this mod yet$/i, () => '你尚未下载此模组'],
    // Report Abuse
    [/^Report Abuse$/i, () => '举报滥用'],
    // Permissions and credits
    [/^Permissions and credits$/i, () => '使用权限与致谢'],
    // Original upload
    [/^Original upload$/i, () => '原始上传'],
    // Tags for this mod
    [/^Tags for this mod$/i, () => '此模组的标签'],
    // Tag this mod
    [/^Tag this mod$/i, () => '标记此模组'],
    // content blocking settings
    [/^content blocking settings$/i, () => '内容屏蔽设置'],
    // {Game Name} images (标题中的图片后缀)
    [/^(.+)\s+images$/i, (m) => `${m[1]} 图片`],
    // {count} results (纯数字+results,非正则已有但保险)
    // Some {type} may be hidden based on your {settings link}
    // 匹配中英混合版本(碎片部分翻译后)和纯英文版
    [/^Some\s+(.+)\s+may\s+be\s+hidden\s+based\s+on\s+your\s+(.+)$/i,
      (m) => `部分${m[1]}可能根据你的${m[2]}被隐藏`],
  ];

  const DEFAULT_TRANSLATIONS = {
    // 导航
    'Home': '首页', 'Games': '游戏', 'Mods': '模组', 'Collections': '合集',
    'News': '资讯', 'Forums': '论坛', 'Premium': '高级会员',
    'Log in': '登录', 'Sign up': '注册', 'Sign out': '退出登录',
    'Sign Up': '注册', 'Log out': '退出',
    'My account': '我的账户', 'My profile': '我的主页',
    'My mods': '我的模组', 'My collections': '我的合集', 'My games': '我的游戏',
    'My images': '我的图片', 'My videos': '我的视频',
    'My media': '我的媒体', 'My wallet': '我的钱包',
    'Dashboard': '控制台', 'Settings': '设置', 'Notifications': '通知', 'Messages': '消息',

    // 通用按钮
    'Search': '搜索', 'Search mods': '搜索模组',
    'Submit': '提交', 'Cancel': '取消', 'Confirm': '确认',
    'Save': '保存', 'Save changes': '保存更改',
    'Delete': '删除', 'Edit': '编辑',
    // 注意:不翻译 'Close' 纯文本,避免关闭按钮 ✕ 被替换成"关闭"文字
    // aria-label="Close" 由 _translateAttributes 的 CLOSE_ARIA_LABELS 白名单处理
    'Close dialog': '关闭对话框', 'Close menu': '关闭菜单',
    'Close panel': '关闭面板', 'Close modal': '关闭弹窗',
    'Close notification': '关闭通知', 'Close search': '关闭搜索',
    'Back': '返回', 'Next': '下一步', 'Previous': '上一步',
    'Continue': '继续', 'Done': '完成', 'Apply': '应用',
    'Reset': '重置', 'Clear': '清除', 'Filter': '筛选', 'Sort by': '排序方式',
    'View all': '查看全部', 'Show more': '显示更多', 'Show less': '显示更少',
    'Load more': '加载更多', 'Copy': '复制', 'Share': '分享',
    'Report': '举报', 'Follow': '关注', 'Unfollow': '取消关注',

    // 下载
    'Download': '下载', 'Downloads': '下载量',
    'Manual Download': '手动下载', 'Mod Manager Download': '模组管理器下载',
    'Slow Download': '普通下载', 'Fast Download': '快速下载',
    'Download file': '下载文件', 'Download history': '下载历史',
    'Total downloads': '总下载量',

    // 上传
    'Upload': '上传', 'Upload file': '上传文件',
    'Uploaded': '已上传', 'Uploaded by': '上传者:',
    'Upload mod': '上传模组', 'Uploading...': '上传中…',

    // 分类
    'Category': '分类', 'Categories': '分类',
    'All games': '全部游戏', 'All mods': '全部模组',
    'Popular': '热门', 'Trending': '趋势', 'Latest': '最新',
    'Updated': '已更新', 'Top rated': '最高评分',
    'Most endorsed': '最多认可', 'Most downloaded': '最多下载',
    'New today': '今日新增', 'New this week': '本周新增',
    'Recently updated': '最近更新',

    // 状态
    'Active': '活跃', 'Inactive': '不活跃', 'Hidden': '已隐藏',
    'Published': '已发布', 'Draft': '草稿', 'Archived': '已归档',
    'Deleted': '已删除', 'Banned': '已封禁',
    'Verified': '已验证', 'Unverified': '未验证',
    'Loading': '加载中', 'Loading...': '加载中…',
    'No results found': '未找到结果', 'No results': '无结果',
    'Error': '错误', 'Success': '成功', 'Warning': '警告',

    // 内容标签
    'Description': '描述', 'Files': '文件', 'Images': '图片', 'images': '图片',
    'Videos': '视频', 'Articles': '文章', 'Comments': '评论',
    'Reviews': '评价', 'Changelog': '更新日志',
    'Permissions': '使用权限', 'Requirements': '前置要求',
    'Tags': '标签', 'Bugs': '问题反馈', 'Logs': '日志',
    'Stats': '统计', 'Credits': '致谢',
    'About this mod': '关于本模组', 'About this collection': '关于本合集',

    // 表单
    'Email': '邮箱', 'Email address': '电子邮箱', 'Password': '密码',
    'Username': '用户名', 'Name': '名称', 'Title': '标题',
    'Optional': '可选', 'Required': '必填',
    'Search...': '搜索…', 'Search mods...': '搜索模组…',

    // 统计
    'views': '次浏览', 'View': '浏览', 'Views': '浏览量',
    'endorsements': '次认可', 'Endorsements': '认可数',
    'Unique downloads': '独立下载', 'Total unique downloads': '总独立下载',
    'Followers': '关注者', 'Following': '正在关注',
    'Members': '成员', 'Posts': '帖子',

    // 时间
    'Today': '今天', 'Yesterday': '昨天',
    'This week': '本周', 'This month': '本月', 'This year': '今年',
    'All time': '历史总计',

    // 通知
    'New comment': '新评论', 'New reply': '新回复',
    'New endorsement': '新认可', 'New follower': '新关注者',
    'Mod updated': '模组已更新', 'Mark all as read': '全部标为已读',
    'No notifications': '暂无通知',
    'No unread notifications right now': '当前没有未读通知',
    "You're up to date": '你已全部阅读',
    'See All': '查看全部', 'See all': '查看全部',
    'Oh dear! Something has gone wrong!': '糟糕!出了点问题!',
    'Try reloading the notifications.': '请尝试重新加载通知。',
    'Notification preferences': '通知偏好',

    // 杂项
    'Version': '版本', 'Author': '作者', 'Authors': '作者',
    'Size': '大小', 'Language': '语言', 'Website': '网站',
    'Source code': '源代码', 'License': '许可证',
    'Adult content': '成人内容', 'NSFW': '成人内容',
    'Spoiler': '剧透', 'Pinned': '已置顶', 'Stickied': '已置顶',
    'Locked': '已锁定', 'Closed': '已关闭', 'Open': '开放',
    'Free': '免费', 'Paid': '付费', 'Featured': '精选',

    // 首页欢迎区
    'Welcome back': '欢迎回来', 'Welcome': '欢迎',
    'Tracking centre': '追踪中心', 'Activity feed': '动态',
    'Your feed': '你的动态', 'Recommended mods': '推荐模组',
    'Recently viewed': '最近浏览', 'Favourite games': '收藏游戏',
    'Browse': '浏览', 'Explore': '探索',
    'Go premium': '开通高级会员', 'Sign in': '登录',
    'Create an account': '创建账户',
    'Download mods': '下载模组', 'Mod manager': '模组管理器',
    'My content': '我的内容', 'My content library': '我的内容库',
    'Content library': '内容库', 'My download history': '我的下载历史',
    'Account details': '账户详情', 'Site preferences': '网站偏好',
    'Blocked users': '已屏蔽用户', 'Blocked authors': '已屏蔽作者',
    'Notification preferences': '通知偏好', 'Privacy': '隐私',
    'Content controls': '内容控制', 'Search filters': '搜索筛选',
    'Manage tracked mods': '管理追踪模组', 'Manage tracked games': '管理追踪游戏',
    'Tracked mods': '追踪的模组', 'Tracked games': '追踪的游戏',
    'Premium membership': '高级会员资格', 'Supporter membership': '支持者会员',
    'Free downloads': '免费下载', 'No cooldown': '无冷却',
    'Download history': '下载历史',

    // 首页/通用UI
    'Featured mods': '精选模组', 'Featured collections': '精选合集',
    'Latest mods': '最新模组', 'Popular games': '热门游戏',
    'Trending mods': '趋势模组', 'New and updated mods': '新增与更新模组',
    'Browse all games': '浏览全部游戏', 'Browse all mods': '浏览全部模组',
    'Explore Nexus Mods': '探索 Nexus Mods', 'Join the community': '加入社区',
    'Discover the best mods': '探索最佳模组',
    'Safe to use': '安全使用', 'Totally free': '完全免费',
    'Community-driven': '社区驱动',
    'Skip to content': '跳到内容',
    'Media': '媒体', 'Community': '社区', 'Support': '支持',
    'Open profile menu': '打开个人菜单',
    'Show notifications': '显示通知', 'View messages': '查看消息',
    'Open navigation menu': '打开导航菜单', 'Show search': '显示搜索',
    'Add game': '添加游戏',
    'Find out more': '了解更多',
    'Latest news': '最新资讯',
    'Get fast downloads with': '使用快速下载',
    'Try Premium for free': '免费试用高级会员',
    'auto-install collections': '自动安装合集',
    'uncapped download speeds': '不限速下载',
    'browse ad-free': '无广告浏览',
    'Start free trial': '开始免费试用',
    // Premium 广告区完整句子(碎片 span 合并后匹配)
    'Try Premium for free to auto-install collections, get uncapped download speeds and browse ad-free.': '免费试用高级会员以自动安装合集、不限速下载和无广告浏览。',
    'Get fast downloads with Premium': '使用高级会员快速下载',
    'Use Premium for fast downloads': '使用高级会员快速下载',
    // 已部分翻译的碎片合并文本(延迟调用/MutationObserver 时部分已翻译)
    '免费试用高级会员 to 自动安装合集, get 不限速下载 and 无广告浏览.': '免费试用高级会员以自动安装合集、不限速下载和无广告浏览。',
    '使用快速下载 高级会员': '使用高级会员快速下载',
    'Make mods.': '制作模组。',
    'Earn rewards.': '获得奖励。',
    'Cash payouts': '现金奖励',
    'Free Premium': '免费高级会员',
    'Statistics': '统计信息',
    'Careers': '招聘',
    'About us': '关于我们',
    'Premium features': '高级功能',
    'Discover': '发现',
    'All collections': '所有合集', 'All images': '所有图片',
    'Help': '帮助',
    'API': 'API',
    'Feedback': '反馈',
    'Report a bug': '报告问题',
    'Unban requests': '解封申请',
    'DMCA': 'DMCA',
    'Manage cookie settings': '管理 Cookie 设置',
    'Follow us on Twitter': '在 Twitter 上关注我们',
    'Follow us on TikTok': '在 TikTok 上关注我们',
    'Follow us on Twitch': '在 Twitch 上关注我们',
    'Follow us on Youtube': '在 YouTube 上关注我们',
    'Follow us on Instagram': '在 Instagram 上关注我们',
    'Join us on Discord': '加入我们的 Discord',
    'Discord': 'Discord',
    'Support authors': '支持作者',
    'Contact us': '联系我们',
    'Support Nexus Mods': '支持 Nexus Mods',
    'Network stats': '网络统计',
    'Kudos': '感谢',
    'Server info': '服务器信息',
    'Terms of Service': '服务条款',
    'Privacy Policy': '隐私政策',
    'Popular mods': '热门模组',
    'View more': '查看更多',
    'Buy now': '立即购买',
    'Affiliate link': '推广链接',
    'Mods filter': '模组筛选', 'Time': '时间',
    'Media filter': '媒体筛选',
    'Mod options': '模组选项',
    'Clear game filter': '清除游戏筛选',
    'authenticated': '已认证',
    'Desktop footer': '桌面端页脚',
    'Breadcrumb navigation': '面包屑导航',
    'Downloaded': '已下载',
    'New': '新',
    'premium': '高级会员',
    'Top pick': '精选推荐',
    'Easy install': '轻松安装',
    'MOD REQUEST': '模组请求',
    'Site News': '站点资讯',
    'No. of endorsements': '认可数',
    'My Games': '我的游戏',
    'Surprise': '惊喜',

    // 模组分类标签(出现在卡片上,是 UI 文本不是用户内容)
    'Utilities': '实用工具', 'Armour and Clothing': '盔甲与服装',
    'Armour': '盔甲', 'Clothing': '服装',
    'Modding Tools': '模组工具', 'Addons': '附加组件',
    'Visuals': '视觉效果', 'Environment': '环境',
    'Gameplay': '游戏玩法', 'Characters': '角色',
    'Animations': '动画', 'Weapons': '武器',
    'Mod Organizer 2 Plugins': 'MO2 插件',
    'Dress for Fem V': '女性V服装',
    'For FemV': '女性V',
    'Adult': '成人',

    // 导航下拉菜单
    'Recently added': '最近添加',
    'MOD UPDATES': '模组更新',
    'Mod rewards': '模组奖励',
    'MODDING TUTORIALS': '模组教程',
    'Learn from the community with tutorials and guides.': '通过教程和指南向社区学习。',
    'VORTEX MOD MANAGER': 'Vortex 模组管理器',
    'The elegant, powerful and open-source mod manager.': '优雅、强大且开源的模组管理器。',
    'COLLECTIONS TUTORIALS': '合集教程',
    'Highest rated': '最高评分',
    'SUPPORTER IMAGES': '支持者图片',
    'Upgrade your account to unlock all media content.': '升级账户以解锁所有媒体内容。',
    'Upgrade': '升级',
    'Upload image': '上传图片',
    'Upload video': '上传视频',
    'All news': '全部新闻',
    'Site news': '网站资讯',
    'Competitions': '竞赛',
    'Interviews': '访谈',
    'Game guides': '游戏指南',
    'Tutorial': '教程',
    'Tools': '工具',
    'Vortex help': 'Vortex 帮助',
    'Install Vortex': '安装 Vortex',
    'GIVE FEEDBACK': '提供反馈',
    'Give Feedback': '提供反馈',
    'Give feedback': '提供反馈',
    'Share your ideas, discuss them with the community, and cast your vote on feedback provided.': '分享你的想法,与社区讨论,并对提供的反馈投票。',
    'Contact': '联系',
    'MY STUFF': '我的内容',
    'My stuff': '我的内容',
    'Member': '会员',
    'Try premium free': '免费试用高级会员',
    // 导航下拉菜单补充(从 DOM 发现的 Title Case 版本)
    'Mod updates': '模组更新',
    'Modding tutorials': '模组教程',
    'Vortex mod manager': 'Vortex 模组管理器',
    'Collections tutorials': '合集教程',
    'Supporter images': '支持者图片',
    'Top files': '热门文件',
    'Recent activity': '最近活动',
    'Mod categories': '模组分类',
    'Mods of the month': '本月精选模组',
    'Explore this month\'s nominated mods.': '探索本月提名的模组。',
    // 导航/通用补充(从实际 DOM 发现)
    'Upload mod': '上传模组',
    'Tracking centre': '追踪中心',
    'Trending': '趋势',
    'Most endorsed': '最多认可',
    'New': '新',
    'Authenticated': '已认证',
    // 搜索弹窗
    'All content': '全部内容',
    'Search mods, games, collections, images & videos': '搜索模组、游戏、合集、图片和视频',
    'Customise your search preferences': '自定义搜索偏好',
    'Enter': '确认',
    'Select': '选择',
    'Move': '移动',

    // Images 页面下拉/筛选(v0.2.5 新增)
    '24 Hours': '24 小时', '7 Days': '7 天', '14 Days': '14 天',
    '28 Days': '28 天', '1 Year': '1 年',
    'Most viewed': '最多浏览',
    'Sort direction': '排序方向',
    'Desc': '降序', 'Asc': '升序',
    '20 Items': '20 条', '40 Items': '40 条',
    '60 Items': '60 条', '80 Items': '80 条',
    'Content options': '内容选项',
    'Hide adult content': '隐藏成人内容',
    'Show only adult content': '仅显示成人内容',
    'Clear all': '全部清除',
    'View results': '查看结果',
    'Remove filter for': '移除筛选:',
    'Filters panel': '筛选面板',
    'Media per page': '每页媒体数',
    'Games per page': '每页游戏数',
    'Previous page': '上一页', 'Next page': '下一页',
    'Pagination navigation': '分页导航',
    'Go to previous page': '前往上一页',
    'Go to next page': '前往下一页',
    'Go to page': '前往第',
    'Jump to page': '跳转到页',
    'Bookmark': '收藏',
    'Time range': '时间范围',
    'From': '从', 'To': '至',

    // Games 页面(v0.2.5 补充)
    'Search game': '搜索游戏',

    // Images 页面通用词条(v0.2.6 补充)
    'Get more with Premium': '使用高级会员获取更多',
    'Share image': '分享图片',
    'Report image': '举报图片',
    'Download image': '下载图片',
    'Full size': '原尺寸',
    'Upload images': '上传图片',
    'Gallery': '图库',
    'Favourite': '收藏',
    'Unfavourite': '取消收藏',
    'Add to favourites': '添加到收藏',
    'Remove from favourites': '从收藏中移除',
    // Images 页面通用词条(v0.2.7 补充)
    'Some images may be hidden': '部分图片可能被隐藏',
    'Some mods may be hidden': '部分模组可能被隐藏',
    'Images may be hidden': '图片可能被隐藏',
    'The best screen archery on the internet': '互联网上最好的游戏截图',

    // VORTEX 广告区(v0.3.3 补充)
    'VORTEX': 'VORTEX',
    'The powerful open-source mod manager from Nexus Mods.': '来自 Nexus Mods 的强大开源模组管理器。',

    // 模组详情(v0.2.8 补充)
    'Locations': '位置',
    'Original File': '原始文件',
    'Add media': '添加媒体',
    'Track': '追踪',
    'Vote': '投票',
    'Manual': '手动',
    'Original upload': '原始上传',
    'Tags for this mod': '此模组的标签',
    'Tag this mod': '标记此模组',
    'You haven\'t downloaded this mod yet': '你尚未下载此模组',
    'Report Abuse': '举报滥用',
    'Permissions and credits': '使用权限与致谢',
    'Requirements and permissions': '前置与权限',
    'Nexus requirements': '前置要求',

    // 权限与致谢区域(v0.3.0 补充)
    'Credits and distribution permission': '使用权限与分发许可',
    'Other user\'s assets': '其他用户的资产',
    'This author has not specified whether they have used assets from other authors or not': '此作者未说明是否使用了其他作者的资产',
    'Upload permission': '上传权限',
    'You are not allowed to upload this file to other sites under any circumstances': '在任何情况下都不允许将此文件上传到其他网站',
    'Modification permission': '修改权限',
    'You must get permission from me before you are allowed to modify my files to improve it': '修改我的文件以进行改进之前必须先获得我的许可',
    'Conversion permission': '转换权限',
    'You are not allowed to convert this file to work on other games under any circumstances': '在任何情况下都不允许将此文件转换为用于其他游戏',
    'Asset use permission': '资产使用权限',
    'You must get permission from me before you are allowed to use any of the assets in this file': '使用此文件中的任何资产前必须获得我的许可',
    'Asset use permission in mods/files that are being sold': '商业模组/文件中的资产使用权限',
    'You are not allowed to use assets from this file in any mods that are being sold, for money, on Steam Workshop or other platforms': '不允许在商业模组、Steam Workshop 或其他平台中使用此文件中的资产',
    'Asset use permission in mods/files that earn donation points': '可获捐赠积分的模组/文件中的资产使用权限',
    'You are allowed to earn Donation Points for your mods if they use my assets': '如果你的模组使用了我的资产,你可以获得捐赠积分',
    'Author notes': '作者备注',
    'This author has not provided any additional notes regarding file permissions': '此作者未提供关于文件权限的其他说明',
    'File credits': '文件致谢',
    'This author has not credited anyone else in this file': '此作者在此文件中未致谢任何人',
    'Donation Points system': '捐赠积分系统',
    'This mod is not opted-in to receive Donation Points': '此模组未启用接收捐赠积分',
    'File information': '文件信息', 'Main files': '主文件',
    'Optional files': '可选文件', 'Old versions': '旧版本',
    'Miscellaneous': '其他文件', 'FOMOD installer': 'FOMOD 安装包',
    'Add to collection': '添加到合集', 'Remove from collection': '从合集移除',
    'Track this mod': '追踪此模组', 'Stop tracking': '停止追踪',
    'Report this mod': '举报此模组', 'Endorse this mod': '认可此模组',
    'Mod details': '模组详情', 'Mod page': '模组页面',
    'Original upload date': '首次上传日期', 'Last updated': '最后更新',
    'Virus scan': '病毒扫描',
    'Type': '类型',
    'You must be logged in': '您需要先登录',
    'Log in to endorse': '登录后认可', 'Log in to download': '登录后下载',
    'Log in to track': '登录后追踪',
    'Mod requirements': '前置模组', 'This mod requires': '此模组需要',
    'No requirements': '无前置要求',
    'Mods requiring this file': '依赖此文件的模组',
    'File size': '文件大小', 'Upload date': '上传日期',
    'MD5 hash': 'MD5 校验值', 'Preview file contents': '预览文件内容',
    'Download this file': '下载此文件', 'Delete this file': '删除此文件',
    'View changelog': '查看更新日志',
    'Sticky': '置顶帖', 'Pinned post': '置顶帖子',
    'Add comment': '添加评论', 'Add a new comment': '添加新评论',
    'Post your comment': '发表评论', 'Show replies': '显示回复',
    'Use emoticons': '使用表情', 'Submit Comment': '提交评论',
    'Hide replies': '隐藏回复', 'Reply': '回复', 'Quote': '引用',
    'Edit comment': '编辑评论', 'Delete comment': '删除评论',
    'Report comment': '举报评论', 'No comments yet': '暂无评论',
    'Be the first to comment': '成为第一个评论的人',
    'Write a review': '撰写评价', 'Your review': '您的评价',
    'Overall rating': '综合评分', 'No reviews yet': '暂无评价',
    'Submitted': '已提交',
    'This is an adult content mod': '这是成人内容模组',
    'Endorse': '认可', 'Endorsed': '已认可', 'Abstain': '弃权',

    // 模组列表
    'Relevance': '相关性',
    'Date added': '上传日期', 'Date updated': '更新日期',
    'Rating': '评分', 'File size': '文件大小', 'All categories': '全部分类',
    'Filter by': '按条件筛选', 'Clear filters': '清除筛选',
    'Show NSFW': '显示成人内容', 'Hide NSFW': '隐藏成人内容',
    'Results per page': '每页显示',
    'No mods found': '未找到模组', 'Try adjusting your search': '尝试调整搜索条件',

    // 图片详情页
    'Total views': '总浏览量',
    'Image information': '图片信息',
    'Added on': '添加时间',
    'More images': '更多图片',
    'About this image': '关于此图片',
    'View more from uploader': '查看上传者的更多图片',
    'See who endorsed this image': '查看谁认可了此图片',
    'Click to endorse this image': '点击认可此图片',

    // 合集
    'About this collection': '关于本合集', 'Mods in this collection': '合集中的模组',
    'Curated by': '由…整理', 'Collection requirements': '合集前置要求',
    'Install collection': '安装合集', 'Add to library': '添加到库',
    'Remove from library': '从库中移除',
    'Revision': '修订版本', 'Revisions': '修订历史',
    'View revisions': '查看修订历史',
    'Mods included': '包含模组',
    'Required mods': '必需模组', 'Optional mods': '可选模组',
    'No description': '暂无描述',
    'Collection stats': '合集统计', 'Installs': '安装次数',
    'Browse collections': '浏览合集', 'New collections': '最新合集',
    'Top collections': '最热合集', 'Date created': '创建日期',

    // 游戏
    'Browse mods': '浏览模组', 'Top mods': '热门模组',
    'New mods': '最新模组', 'Total mods': '模组总数',
    'Total endorsements': '总认可数',
    'Forum': '论坛', 'Wiki': '维基',
    'Game details': '游戏详情', 'Release date': '发行日期',
    'Genre': '类型', 'Developer': '开发商', 'Publisher': '发行商',
    'Platforms': '平台', 'Track this game': '追踪此游戏',
    'Stop tracking this game': '停止追踪',

    // 游戏列表页
    'Choose from': '选择',
    'games to mod': '款可模组化的游戏',
    'Get games to mod, cheaper.': '更便宜地获取可模组化的游戏。',
    'Discover offers': '发现优惠',
    'Hide filters': '隐藏筛选',
    'Show filters': '显示筛选',
    'Game genre': '游戏类型',
    'Game genre search': '搜索游戏类型',
    'Vortex Support': 'Vortex 支持',
    'Supported by Vortex': 'Vortex 支持',
    'Show games with Collections': '显示有合集的游戏',
    'No. of mods': '模组数',
    'No. of collections': '合集数',
    'Download count': '下载量',
    'Sort': '排序',
    'Page': '页',
    'Go': '跳转',
    // Images 分类(v0.2.8 补充)
    'Character Presets': '角色预设',
    'Cityscape': '城市景观',
    'Misc': '杂项',
    'Official': '官方',
    'Wallpapers': '壁纸',
    // 游戏类型
    'Action': '动作', 'Adventure': '冒险', 'ARPG': 'ARPG',
    'Dungeon crawl': '地牢探索', 'Fighting': '格斗',
    'FPS': 'FPS', 'Hack and Slash': '砍杀', 'Horror': '恐怖',
    'Indie': '独立', 'Metroidvania': '银河恶魔城', 'MMORPG': 'MMORPG',
    'Music': '音乐', 'Platformer': '平台', 'Puzzle': '解谜',
    'Racing': '竞速', 'Roguelike': 'Roguelike', 'RPG': 'RPG',
    'Sandbox': '沙盒', 'Simulation': '模拟', 'Space sim': '太空模拟',
    'Sports': '体育', 'Stealth': '潜行', 'Strategy': '策略',
    'Survival': '生存', 'Third-Person Shooter': '第三人称射击',
    'Visual Novel': '视觉小说',
    'results': '个结果',

    // 搜索
    'Search results': '搜索结果', 'All results': '全部结果',
    'Users': '用户',
    'Did you mean': '您是否想搜索', 'No results for': '未找到与之匹配的结果:',
    'Try a different search': '请尝试其他关键词', 'Advanced search': '高级搜索',

    // 高级会员
    'Go Premium': '开通高级会员',
    'Premium Member': '高级会员', 'Supporter': '支持者',
    'Benefits': '权益', 'Features': '功能',
    'Fast downloads': '快速下载', 'No ads': '无广告',
    'Priority support': '优先支持', 'Ad-free browsing': '无广告浏览',
    'Unlimited collections': '无限合集',
    'Per month': '每月', 'Per year': '每年', 'Billed annually': '按年结算',
    'Cancel anytime': '随时取消', 'Most popular': '最受欢迎',
    'Get started': '立即开始', 'Learn more': '了解更多',
    'Already a member?': '已是会员?', 'Manage subscription': '管理订阅',

    // 账户
    'Account settings': '账户设置', 'Profile settings': '主页设置',
    'Notification settings': '通知设置', 'Privacy settings': '隐私设置',
    'Security': '安全', 'Change password': '修改密码',
    'Change email': '修改邮箱', 'Two-factor authentication': '两步验证',
    'API key': 'API 密钥', 'Delete account': '删除账户',
    'Linked accounts': '关联账户', 'Display name': '显示名称',
    'About me': '个人简介', 'Avatar': '头像',
    'Profile visibility': '主页可见性', 'Public': '公开', 'Private': '私密',
    'Friends only': '仅好友可见', 'Email notifications': '邮件通知',
    'Site notifications': '站内通知', 'Save settings': '保存设置',
    'Current password': '当前密码', 'New password': '新密码',
    'Confirm new password': '确认新密码',
    'Wallet': '钱包', 'Balance': '余额', 'Transaction history': '交易记录',

    // 用户主页
    'Profile': '主页', 'Activity': '动态',
    'Tracking': '追踪', 'Tracked mods': '追踪的模组',
    'Endorsements given': '已给出的认可', 'Endorsements received': '获得的认可',
    'Files uploaded': '已上传文件', 'Member since': '注册时间',
    'Last active': '最后活跃', 'Reputation': '声誉',
    'Unique author downloads': '作者独立下载', 'Mod downloads': '模组下载',
    'Send a message': '发送消息', 'Follow this user': '关注此用户',
    'Unfollow this user': '取消关注', 'Block this user': '封锁此用户',
    'Report this user': '举报此用户',
    'This user is a Supporter': '该用户是支持者',
    'This user is a Premium Member': '该用户是高级会员',
    'Private profile': '私密主页',
    "This user's profile is private": '该用户的主页已设为私密',
    'No mods uploaded': '暂未上传模组', 'No activity': '暂无动态',

    // 上传
    'Upload a mod': '上传模组', 'Edit mod': '编辑模组',
    'Upload an image': '上传图片', 'Add a video': '添加视频',
    // Upload 托盘碎片词条(HTML: UPLOAD A <strong>MOD</strong> 合并后匹配)
    'UPLOAD A MOD': '上传模组', 'UPLOAD AN IMAGE': '上传图片', 'ADD A VIDEO': '添加视频',
    'Mod name': '模组名称', 'Summary': '摘要',
    'Version number': '版本号',
    'Publish': '发布', 'Save as draft': '保存为草稿', 'Preview': '预览',
    'Add file': '添加文件', 'Remove file': '移除文件',
    'File name': '文件名', 'File type': '文件类型',
    'File description': '文件描述', 'Required files': '必需文件',
    'Main file': '主文件',
    'Old version': '旧版本', 'Image gallery': '图片图库',
    'Add image': '添加图片', 'Primary image': '主图',
    'Thumbnail': '缩略图', 'Credit other mods': '致谢其他模组',
    'Credit other users': '致谢其他用户', 'External credit': '外部致谢',
  };

  // ═══════════════════════════════════════════════
  //  日期本地化
  // ═══════════════════════════════════════════════
  const MONTH_MAP = {
    jan: 1, january: 1, feb: 2, february: 2, mar: 3, march: 3,
    apr: 4, april: 4, may: 5, jun: 6, june: 6, jul: 7, july: 7,
    aug: 8, august: 8, sep: 9, september: 9, oct: 10, october: 10,
    nov: 11, november: 11, dec: 12, december: 12,
  };
  // 日期容器选择器白名单:
  //   标准 <time> 元素 + Nexus Mods 常见 class + 更宽泛的 span/small/p 容器
  //   由于 Next.js SPA 的日期经常不在 <time> 标签内,
  //   需要覆盖更多可能的容器类型
  const DATE_SELECTORS = [
    'time',
    '.stat time',
    '.uploaded-time',
    '.last-updated time',
    '.mod-stats time',
    '[data-date]',
    '[data-timestamp]',
    '.notification-time',
    '.comment-time',
    '.review-date time',
    '.profile-stats time',
    // Next.js SPA 常见的日期包裹元素
    'time[datetime]',
    'span[datetime]',
    'abbr[title]',          // <abbr title="ISO date">human date</abbr>
    'small time',
    '.date time',
    '.timestamp',
    '.file-updated time',
    // 更宽泛的容器:带特定 aria 属性的时间戳
    '[aria-label*="date" i]',
    '[aria-label*="time" i]',
    '[aria-label*="uploaded" i]',
    '[aria-label*="updated" i]',
    // Images / 通用页面更宽泛的容器
    '[class*="date" i]',
    '[class*="time" i]',
    '[class*="uploaded" i]',
    '[class*="updated" i]',
    '.image-stats',
    '.image-meta',
    '.mod-info',
    '.file-info',
    // 更通用的 span/p 容器(images 页面日期常见)
    'span.text-sm',
    'p.text-sm',
    'span.text-xs',
    'p.text-xs',
  ].join(', ');
  const DATE_IGNORE = '.mod-title, h1.game-name, .game-title, a.mod-name, .collection-title, .mod_description_container, .prose-lexical.prose, .changelog, [data-no-date-i18n]';

  function pad2(n) { return String(n).padStart(2, '0'); }
  function to24Hour(h, ampm) {
    if (!ampm) return h;
    if (ampm === 'am') return h === 12 ? 0 : h;
    return h === 12 ? 12 : h + 12;
  }
  /** 将 24h 小时转为 12h 格式字符串,如 "7:15PM" */
  function to12Hour(h, min) {
    const ampm = h >= 12 ? 'PM' : 'AM';
    const h12 = h % 12 || 12;
    return `${h12}:${pad2(min)}${ampm}`;
  }

  function convertDate(text) {
    const use24h = GM_getValue(TIME24_KEY, true); // 默认24小时制
    let m;
    // ── 1. "15 Nov 2025" / "15 November 2025" (可选时间)
    m = text.match(/^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})(?:,?\s*(\d{1,2}):(\d{2})\s*(am|pm)?)?$/i);
    if (m) {
      const mo = MONTH_MAP[m[2].toLowerCase()]; if (!mo) return null;
      const datePart = `${m[3]}-${pad2(mo)}-${pad2(parseInt(m[1],10))}`;
      if (m[4] !== undefined) {
        const h24 = to24Hour(parseInt(m[4],10), (m[6]||'').toLowerCase());
        const timePart = use24h ? `${pad2(h24)}:${m[5]}` : to12Hour(h24, parseInt(m[5],10));
        return `${datePart} ${timePart}`;
      }
      return datePart;
    }
    // ── 1b. "2026-04-22, 9:58PM" / "2026-04-22, 21:58" (ISO日期+时间)
    m = text.match(/^(\d{4})-(\d{2})-(\d{2})(?:,?\s*(\d{1,2}):(\d{2})\s*(am|pm)?)?$/i);
    if (m) {
      const datePart = `${m[1]}-${m[2]}-${m[3]}`;
      if (m[4] !== undefined) {
        const h24 = to24Hour(parseInt(m[4],10), (m[6]||'').toLowerCase());
        const timePart = use24h ? `${pad2(h24)}:${m[5]}` : to12Hour(h24, parseInt(m[5],10));
        return `${datePart} ${timePart}`;
      }
      return datePart;
    }
    // ── 2. "Uploaded at 21:21 03 Nov 2025"
    m = text.match(/^Uploaded at\s+(\d{1,2}):(\d{2})\s+(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/i);
    if (m) {
      const h24 = to24Hour(parseInt(m[1],10), '');
      const min = m[2];
      const day = parseInt(m[3],10);
      const mo = MONTH_MAP[m[4].toLowerCase()]; if (!mo) return null;
      const yr = m[5];
      const timePart = use24h ? `${pad2(h24)}:${min}` : to12Hour(h24, parseInt(min,10));
      return `上传于 ${yr}-${pad2(mo)}-${pad2(day)} ${timePart}`;
    }
    // ── 3. "Updated at 21:21 03 Nov 2025"
    m = text.match(/^Updated at\s+(\d{1,2}):(\d{2})\s+(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/i);
    if (m) {
      const h24 = to24Hour(parseInt(m[1],10), '');
      const min = m[2];
      const day = parseInt(m[3],10);
      const mo = MONTH_MAP[m[4].toLowerCase()]; if (!mo) return null;
      const yr = m[5];
      const timePart = use24h ? `${pad2(h24)}:${min}` : to12Hour(h24, parseInt(min,10));
      return `更新于 ${yr}-${pad2(mo)}-${pad2(day)} ${timePart}`;
    }
    // ── 3b. "Apr 22, 2026" / "April 22, 2026" (美式日期,可选时间)
    m = text.match(/^([A-Za-z]+)\s+(\d{1,2}),?\s+(\d{4})(?:,?\s*(\d{1,2}):(\d{2})\s*(am|pm)?)?$/i);
    if (m) {
      const mo = MONTH_MAP[m[1].toLowerCase()]; if (!mo) return null;
      const datePart = `${m[3]}-${pad2(mo)}-${pad2(parseInt(m[2],10))}`;
      if (m[4] !== undefined) {
        const h24 = to24Hour(parseInt(m[4],10), (m[6]||'').toLowerCase());
        const timePart = use24h ? `${pad2(h24)}:${m[5]}` : to12Hour(h24, parseInt(m[5],10));
        return `${datePart} ${timePart}`;
      }
      return datePart;
    }
    // ── 3c. "Added on Apr 22, 2026, 9:58PM" (带前缀的美式日期)
    m = text.match(/^Added on\s+([A-Za-z]+)\s+(\d{1,2}),?\s+(\d{4})(?:,?\s*(\d{1,2}):(\d{2})\s*(am|pm)?)?$/i);
    if (m) {
      const mo = MONTH_MAP[m[1].toLowerCase()]; if (!mo) return null;
      const datePart = `${m[3]}-${pad2(mo)}-${pad2(parseInt(m[2],10))}`;
      if (m[4] !== undefined) {
        const h24 = to24Hour(parseInt(m[4],10), (m[6]||'').toLowerCase());
        const timePart = use24h ? `${pad2(h24)}:${m[5]}` : to12Hour(h24, parseInt(m[5],10));
        return `添加于 ${datePart} ${timePart}`;
      }
      return `添加于 ${datePart}`;
    }
    // ── 4. 相对时间:"4 weeks ago", "2 days ago", "1 hour ago" …
    m = text.match(/^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i);
    if (m) { const u = {second:'秒',minute:'分钟',hour:'小时',day:'天',week:'周',month:'个月',year:'年'}; return `${m[1]} ${u[m[2].toLowerCase()]||m[2]}前`; }
    // ── 5. "just now"
    if (/^just now$/i.test(text)) return '刚刚';
    // ── 6. "Today at 14:32"
    m = text.match(/^Today at\s+(\d{1,2}):(\d{2})\s*(am|pm)?$/i);
    if (m) {
      const h24 = to24Hour(parseInt(m[1],10), (m[3]||'').toLowerCase());
      const timePart = use24h ? `${pad2(h24)}:${m[2]}` : to12Hour(h24, parseInt(m[2],10));
      return `今天 ${timePart}`;
    }
    // ── 7. "Yesterday at 14:32"
    m = text.match(/^Yesterday at\s+(\d{1,2}):(\d{2})\s*(am|pm)?$/i);
    if (m) {
      const h24 = to24Hour(parseInt(m[1],10), (m[3]||'').toLowerCase());
      const timePart = use24h ? `${pad2(h24)}:${m[2]}` : to12Hour(h24, parseInt(m[2],10));
      return `昨天 ${timePart}`;
    }
    // ── 8. 纯时间格式:"9:59PM", "11:30 AM", "21:50", "14:05"
    //     处理 <time> 内与日期分开的独立时间文本节点
    m = text.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(am|pm)?$/i);
    if (m) {
      const h = parseInt(m[1], 10);
      const min = parseInt(m[2], 10);
      const ampm = (m[4] || '').toLowerCase();
      const h24 = to24Hour(h, ampm);
      return use24h ? `${pad2(h24)}:${m[2]}` : to12Hour(h24, min);
    }
    return null;
  }

  // ═══════════════════════════════════════════════
  //  核心:翻译引擎
  // ═══════════════════════════════════════════════
  class Translator {
    constructor() {
      // 语言判断:英文模式下整个翻译器静默
      this.lang = CURRENT_LANG;
      this.enabled = (this.lang === 'zh-CN');

      // 合并:默认词条 + 用户自定义词条
      const custom = GM_getValue(CUSTOM_KEY, {});
      this.dict = Object.assign({}, DEFAULT_TRANSLATIONS, custom);

      // 构建匹配用的 keys(按长度降序,优先匹配长词)
      this.keys = Object.keys(this.dict).sort((a, b) => b.length - a.length);

      // 忽略选择器合并字符串
      this.ignoreSelector = IGNORE_SELECTORS.join(', ');

      this._processed = new WeakSet();
      this._observer = null;
      this._url = location.href;

      if (this.enabled) {
        console.log(`[NexusL10n] 初始化完成,${this.keys.length} 条翻译词条,语言:${this.lang}`);
      } else {
        console.log(`[NexusL10n] 语言:${this.lang},翻译已禁用`);
      }
    }

    /** 检查节点是否在忽略区域内 */
    _isIgnored(node) {
      const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
      if (!el) return false;
      try { return el.closest(this.ignoreSelector) !== null; } catch (e) { return false; }
    }

    /** 翻译单个文本节点 */
    _translateTextNode(node) {
      if (this._processed.has(node)) return;
      const original = node.nodeValue;
      if (!original || !original.trim()) return;

      // ═══ 优先执行日期本地化 ═══
      // 原因:如果先翻译成中文(如 "Added on Apr 22, 2026" → "添加于..."),
      // 后面的中文检测会跳过日期转换,导致 24h 格式丢失
      if (GM_getValue(DATE_L10N_KEY, true)) {
        const el = node.parentElement;
        if (el) {
          try {
            const inDateContainer = el.closest(DATE_SELECTORS) !== null;
            const inIgnoreArea = el.closest(DATE_IGNORE) !== null;
            const rawText = original.replace(/\s+/g, ' ').trim();

            if (inDateContainer && !inIgnoreArea) {
              const converted = convertDate(rawText);
              if (converted) {
                const leading = original.match(/^\s*/)[0];
                const trailing = original.match(/\s*$/)[0];
                node.nodeValue = leading + converted + trailing;
              }
            } else if (!inIgnoreArea) {
              // 通用日期匹配(不在白名单容器内的短文本)
              if (rawText.length > 0 && rawText.length <= 60 && !/[\u4e00-\u9fff]/.test(rawText)) {
                const converted = convertDate(rawText);
                if (converted) {
                  const leading = original.match(/^\s*/)[0];
                  const trailing = original.match(/\s*$/)[0];
                  node.nodeValue = leading + converted + trailing;
                }
              }
            }
          } catch (e) { /* ignore */ }
        }
      }

      // ═══ 然后执行翻译 ═══
      const key = node.nodeValue.replace(/\s+/g, ' ').trim();
      let translated = null;

      // 1. 精确匹配(字典)
      if (key in this.dict) {
        translated = this.dict[key];
      }

      // 2. 大小写不敏感匹配(fallback)
      //    Nexus Mods 同一文本在不同位置大小写不一致:
      //    导航标题 "Mod updates" vs 下拉菜单 "MOD UPDATES"
      if (translated === null) {
        const keyLower = key.toLowerCase();
        for (const k of this.keys) {
          if (k.toLowerCase() === keyLower) {
            translated = this.dict[k];
            break;
          }
        }
      }

      // 3. 正则模板匹配(处理带变量的文本)
      if (translated === null) {
        for (const [pattern, replacer] of REGEXP_TRANSLATIONS) {
          const m = key.match(pattern);
          if (m) {
            translated = replacer(m);
            break;
          }
        }
      }

      if (translated && translated !== key) {
        const leading = node.nodeValue.match(/^\s*/)[0];
        const trailing = node.nodeValue.match(/\s*$/)[0];
        node.nodeValue = leading + translated + trailing;
      }

      this._processed.add(node);
    }

    /**
     * 翻译内联碎片元素(如 <p> 内多个 <span> 拆分文本)
     * 核心问题:Nexus Mods 用 <span> 拆分同一段落为多个文本节点,
     * 例如 "Try Premium to auto-install collections, get ... and ..."
     * 被拆成:["Try Premium to ", "auto-install collections", ", get ", "uncapped ...", "and ", "browse ad-free", "."]
     * 逐节点翻译会导致 "to", "get", "and" 等碎片词无法正确翻译。
     *
     * 解决方案:合并父元素内所有文本 → 整句翻译 → 将译文拆回各节点
     */
    _translateInlineFragments(parentEl) {
      // 不再用 _processed 标记父元素来跳过,因为 Next.js SPA 路由切换时
      // 可能复用同一个父元素但更新其子文本内容。
      // 改为:收集所有未处理的子文本节点,如果全部已处理则跳过。

      // 收集所有内联子元素(span, a, strong, em 等)中的文本节点
      // 跳过忽略区域(.material-icons 等)内的文本节点
      const children = [];
      const textNodes = [];
      const walk = document.createTreeWalker(parentEl, NodeFilter.SHOW_TEXT);
      let tn;
      while ((tn = walk.nextNode())) {
        if (this._processed.has(tn)) continue;
        // 跳过忽略区域内的文本节点(如图标字体名称)
        if (tn.parentElement && tn.parentElement.closest(this.ignoreSelector)) continue;
        children.push(tn);
        textNodes.push(tn.nodeValue);
      }

      if (textNodes.length < 1) return; // 没有未处理的碎片节点

      // 单个文本节点:直接翻译(不需要合并)
      if (textNodes.length === 1) {
        this._translateTextNode(children[0]);
        return;
      }

      // 合并所有文本节点的内容
      const mergedOriginal = textNodes.join('');
      const mergedKey = mergedOriginal.replace(/\s+/g, ' ').trim();

      if (!mergedKey || mergedKey.length < 2) return;

      // 尝试翻译合并后的文本
      let translated = null;

      // 1. 字典精确匹配
      if (mergedKey in this.dict) {
        translated = this.dict[mergedKey];
      }

      // 2. 大小写不敏感匹配(fallback)
      if (translated === null) {
        const keyLower = mergedKey.toLowerCase();
        for (const k of this.keys) {
          if (k.toLowerCase() === keyLower) {
            translated = this.dict[k];
            break;
          }
        }
      }

      // 3. 正则匹配
      if (translated === null) {
        for (const [pattern, replacer] of REGEXP_TRANSLATIONS) {
          const m = mergedKey.match(pattern);
          if (m) {
            translated = replacer(m);
            break;
          }
        }
      }

      // 4. 如果合并文本已部分翻译(含中文),尝试模糊匹配中英混合词条
      if (translated === null && /[\u4e00-\u9fff]/.test(mergedKey)) {
        // 归一化:统一空格和标点后尝试匹配
        const normalizedKey = mergedKey.replace(/\s*[,,]\s*/g, ', ').replace(/\s*\.。?\s*$/g, '.').replace(/\s+/g, ' ').trim();
        if (normalizedKey in this.dict) {
          translated = this.dict[normalizedKey];
        }
      }

      // 5. 如果仍然未翻译且含中文,替换残留的英文连接词
      if (translated === null && /[\u4e00-\u9fff]/.test(mergedKey)) {
        const connectorMap = [
          [/\bto\b/gi, '以'],
          [/\bget\b/gi, '获得'],
          [/\band\b/gi, '和'],
          [/\bfor\b/gi, '为'],
          [/\bwith\b/gi, '与'],
          [/\bor\b/gi, '或'],
          [/\bthe\b/gi, ''],
          [/\ba\b/gi, ''],
          [/\ban\b/gi, ''],
          [/\bof\b/gi, '的'],
          [/\bin\b/gi, '在'],
          [/\bon\b/gi, '在'],
          [/\bat\b/gi, '在'],
          [/\bby\b/gi, '由'],
          [/\bfrom\b/gi, '从'],
        ];
        let result = mergedKey;
        let changed = false;
        for (const [pattern, replacement] of connectorMap) {
          const newResult = result.replace(pattern, replacement);
          if (newResult !== result) changed = true;
          result = newResult;
        }
        if (changed) {
          // 清理多余空格
          translated = result.replace(/\s{2,}/g, ' ').replace(/\s*([,。、])\s*/g, '$1').trim();
        }
      }

      if (!translated || translated === mergedKey) return;

      // 翻译成功,需要将译文分配回各文本节点
      // 策略:按原始文本节点长度比例分配译文
      // 但更可靠的做法是:如果只有一个节点包含大部分文字,整体替换
      // 简化方案:清空前面所有节点,把完整译文放到最后一个节点
      // 但这会丢失 span 的样式,所以用更好的方案:
      // 保留各节点空白,将翻译文本直接替换

      // 最简方案:将合并后的译文放入第一个非空节点,清空其他节点
      // 这在视觉上是正确的,因为它们在同一个 <p> 或 <h3> 内
      let firstNonEmpty = -1;
      for (let i = 0; i < children.length; i++) {
        if (children[i].nodeValue.trim()) {
          firstNonEmpty = i;
          break;
        }
      }
      if (firstNonEmpty < 0) return;

      // 保留首尾空白
      const firstNode = children[firstNonEmpty];
      const leading = firstNode.nodeValue.match(/^\s*/)[0];
      const lastNode = children[children.length - 1];
      const trailing = lastNode.nodeValue.match(/\s*$/)[0];

      // 清空所有节点
      for (let i = 0; i < children.length; i++) {
        if (i === firstNonEmpty) continue;
        children[i].nodeValue = '';
        this._processed.add(children[i]);
      }

      // 将译文放入第一个非空节点
      firstNode.nodeValue = leading + translated + trailing;
      this._processed.add(firstNode);
    }

    /** 翻译元素属性 */
    _translateAttributes(el) {
      for (const attr of TRANSLATABLE_ATTRS) {
        const val = el.getAttribute(attr);
        if (!val) continue;
        const key = val.replace(/\s+/g, ' ').trim();

        // 精确匹配
        if (key in this.dict) {
          el.setAttribute(attr, this.dict[key]);
          continue;
        }

        // 对 aria-label="Close" 单独处理:翻译属性为"关闭",但不影响文本节点
        if (attr === 'aria-label' && key.toLowerCase() === 'close') {
          el.setAttribute(attr, '关闭');
          continue;
        }

        // case-insensitive fallback
        const keyLower = key.toLowerCase();
        for (const k of this.keys) {
          if (k.toLowerCase() === keyLower) {
            el.setAttribute(attr, this.dict[k]);
            break;
          }
        }
      }
    }

    /** 翻译整个子树 */
    translateSubtree(root) {
      if (!root) return;
      if (this._isIgnored(root)) return;

      // 第一步:合并翻译内联碎片元素(p, h3 等包含多个 span 的元素)
      const inlineParents = root.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, td, th, label, figcaption, blockquote, button, a');
      for (const p of inlineParents) {
        if (this._isIgnored(p)) continue;
        // 检查是否包含多个文本节点(碎片化文本)
        let textCount = 0;
        const tw = document.createTreeWalker(p, NodeFilter.SHOW_TEXT);
        while (tw.nextNode()) textCount++;
        if (textCount >= 2) {
          this._translateInlineFragments(p);
        }
      }

      // 第二步:逐文本节点翻译(处理剩余未被碎片合并覆盖的节点)
      const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
      let node;
      while ((node = walker.nextNode())) {
        if (!this._isIgnored(node)) {
          this._translateTextNode(node);
        }
      }

      // 遍历元素节点翻译属性
      // 注意:input 元素虽然在 IGNORE_SELECTORS 中(跳过文本内容翻译),
      // 但其 placeholder/aria-label 等属性仍需翻译,所以这里不过滤 input
      const elWalker = document.createTreeWalker(
        root, NodeFilter.SHOW_ELEMENT,
        { acceptNode: (n) => {
          const tag = n.tagName.toLowerCase();
          if (tag === 'script' || tag === 'style' || tag === 'textarea') return NodeFilter.FILTER_REJECT;
          // input 的文本内容不需要翻译(已在 IGNORE_SELECTORS 中跳过),
          // 但 placeholder 等属性仍需翻译,所以只 SKIP(继续子节点)不 REJECT
          if (tag === 'input') return NodeFilter.FILTER_ACCEPT;
          if (this._isIgnored(n)) return NodeFilter.FILTER_REJECT;
          return NodeFilter.FILTER_ACCEPT;
        }}
      );
      let el;
      while ((el = elWalker.nextNode())) {
        this._translateAttributes(el);
      }
    }

    /** 翻译整个页面 */
    translatePage() {
      this.translateSubtree(document.body);
    }

    /** 启动 MutationObserver 监听 */
    startObserver() {
      if (this._observer) this._observer.disconnect();

      this._observer = new MutationObserver((mutations) => {
        const pending = [];
        for (const m of mutations) {
          if (m.type === 'childList') {
            for (const node of m.addedNodes) {
              if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) {
                pending.push(node);
              }
            }
            // 子节点添加/删除时,清除父元素的 _processed 标记
            // 以便 _translateInlineFragments 重新处理碎片合并
            if (m.target.nodeType === Node.ELEMENT_NODE) {
              this._processed.delete(m.target);
            }
          } else if (m.type === 'characterData') {
            // 文本变化:重新处理(移除 processed 标记)
            this._processed.delete(m.target);
            // 同时清除父元素的标记,以便 _translateInlineFragments 重新处理碎片
            if (m.target.parentElement) {
              this._processed.delete(m.target.parentElement);
            }
            pending.push(m.target);
          } else if (m.type === 'attributes') {
            // 属性变化(placeholder/aria-label/title 等):翻译该元素的属性
            if (m.target.nodeType === Node.ELEMENT_NODE) {
              this._translateAttributes(/** @type {Element} */ (m.target));
            }
          }
        }
        if (pending.length > 0) {
          requestAnimationFrame(() => {
            for (const node of pending) {
              if (node.nodeType === Node.TEXT_NODE) {
                if (!this._isIgnored(node)) this._translateTextNode(node);
              } else if (node.nodeType === Node.ELEMENT_NODE) {
                this.translateSubtree(node);
              }
            }
          });
        }
      });

      this._observer.observe(document.body, {
        childList: true,
        subtree: true,
        characterData: true,
        attributes: true,
        attributeFilter: TRANSLATABLE_ATTRS,
      });
    }

    /** 监听 SPA 路由变化 */
    watchRoute() {
      // 拦截 pushState/replaceState
      const _pushState = history.pushState;
      const _replaceState = history.replaceState;
      const onUrlChange = () => {
        if (location.href === this._url) return;
        this._url = location.href;
        // 路由变化时清除已处理标记,确保新页面的节点能被重新翻译
        // Next.js SPA 路由切换可能复用 DOM 节点并更新其文本内容,
        // 不清除 _processed 会导致这些节点被跳过
        this._processed = new WeakSet();
        // 路由变化后延迟重新翻译(Next.js SPA 异步渲染需要更长时间)
        setTimeout(() => this.translatePage(), 100);
        setTimeout(() => this.translatePage(), 500);
        setTimeout(() => this.translatePage(), 1500);
        setTimeout(() => this.translatePage(), 3000);
      };

      history.pushState = function (...args) {
        _pushState.apply(this, args);
        onUrlChange();
      };
      history.replaceState = function (...args) {
        _replaceState.apply(this, args);
        onUrlChange();
      };
      window.addEventListener('popstate', onUrlChange);

      // 轮询兜底
      setInterval(() => {
        if (location.href !== this._url) {
          this._url = location.href;
          // 路由变化时同样清除 _processed(与 onUrlChange 一致)
          this._processed = new WeakSet();
          setTimeout(() => this.translatePage(), 100);
          setTimeout(() => this.translatePage(), 500);
          setTimeout(() => this.translatePage(), 1500);
          setTimeout(() => this.translatePage(), 3000);
        }
      }, 1000);
    }

    /** 启动 */
    start() {
      if (!this.enabled) return; // 英文模式,不翻译

      // 立即翻译一次
      this.translatePage();

      // 启动监听
      this.startObserver();
      this.watchRoute();

      // 延迟补翻(等异步内容加载)
      setTimeout(() => this.translatePage(), 300);
      setTimeout(() => this.translatePage(), 1000);
      setTimeout(() => this.translatePage(), 3000);

      // ═══ 导航栏巡检兜底 ═══
      // 问题:Nexus Mods 的 Next.js 应用在某些页面(特别是 /images)上
      // 会在油猴脚本翻译完成后重新水合/重建导航栏,导致 Search/Upload 等文本被重置。
      // MutationObserver 虽然能检测到变化,但在高频率 DOM 操作时可能丢失事件或时序错乱。
      // 方案:每 3 秒扫描一次导航区域,强制重译已知未翻译的 UI 文本(忽略 _processed)。
      this._startNavPatrol();
    }

    /**
     * 导航栏定期巡检:确保 Search/Upload 等固定 UI 文本始终被翻译
     * 只扫描 header 导航区域,不影响性能
     */
    _startNavPatrol() {
      const NAV_SELECTORS = 'header, [class*="header" i], [class*="navbar" i], [class*="nav" i], [class*="topbar" i], [class*="toolbar" i]';
      setInterval(() => {
        try {
          const navAreas = document.querySelectorAll(NAV_SELECTORS);
          for (const area of navAreas) {
            // 收集所有未处理的(或被重置的)英文文本节点
            const walker = document.createTreeWalker(area, NodeFilter.SHOW_TEXT);
            let node;
            while ((node = walker.nextNode())) {
              if (this._isIgnored(node)) continue;
              const text = node.nodeValue.replace(/\s+/g, ' ').trim();
              if (!text || text.length < 2 || text.length > 50) continue;
              // 跳过已包含中文的(说明已经翻译过了)
              if (/[\u4e00-\u9fff]/.test(text)) continue;
              // 检查是否匹配已知词条(精确或大小写不敏感)
              let translated = null;
              if (text in this.dict) {
                translated = this.dict[text];
              } else {
                const keyLower = text.toLowerCase();
                for (const k of this.keys) {
                  if (k.toLowerCase() === keyLower) {
                    translated = this.dict[k];
                    break;
                  }
                }
              }
              // 也检查正则
              if (!translated) {
                for (const [pattern, replacer] of REGEXP_TRANSLATIONS) {
                  const m = text.match(pattern);
                  if (m) { translated = replacer(m); break; }
                }
              }
              if (translated && translated !== text) {
                const leading = node.nodeValue.match(/^\s*/)[0];
                const trailing = node.nodeValue.match(/\s*$/)[0];
                node.nodeValue = leading + translated + trailing;
              }
            }
          }
        } catch (e) { /* ignore */ }
      }, 3000);
    }

    /** 重新加载词条(用户导入后调用) */
    reload() {
      if (!this.enabled) return;
      const custom = GM_getValue(CUSTOM_KEY, {});
      this.dict = Object.assign({}, DEFAULT_TRANSLATIONS, custom);
      this.keys = Object.keys(this.dict).sort((a, b) => b.length - a.length);
      this._processed = new WeakSet();
      this.translatePage();
    }
  }

  // ═══════════════════════════════════════════════
  //  CSV 导入/导出
  //  格式:英文,中文  (UTF-8,首行是表头)
  //  可直接用 Excel 打开编辑
  // ═══════════════════════════════════════════════

  function parseCSV(text) {
    const lines = text.split(/\r?\n/);
    const result = {};
    let startIdx = 0;
    // 跳过表头
    if (lines.length > 0 && /english|中文|翻译|translation/i.test(lines[0])) {
      startIdx = 1;
    }
    for (let i = startIdx; i < lines.length; i++) {
      const line = lines[i].trim();
      if (!line) continue;
      // 简单 CSV 解析:按第一个逗号分割
      const commaIdx = line.indexOf(',');
      if (commaIdx === -1) continue;
      const en = line.substring(0, commaIdx).trim();
      const zh = line.substring(commaIdx + 1).trim();
      if (en && zh) {
        result[en] = zh;
      }
    }
    return result;
  }

  function toCSV(dict) {
    const lines = ['English,中文(翻译)'];
    for (const [en, zh] of Object.entries(dict)) {
      // 如果值包含逗号或引号,用双引号包裹
      const enCell = en.includes(',') || en.includes('"') ? `"${en.replace(/"/g, '""')}"` : en;
      const zhCell = zh.includes(',') || zh.includes('"') ? `"${zh.replace(/"/g, '""')}"` : zh;
      lines.push(`${enCell},${zhCell}`);
    }
    return lines.join('\n');
  }

  function downloadFile(content, filename, mimeType) {
    const blob = new Blob(['\uFEFF' + content], { type: mimeType }); // BOM for Excel
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.head.appendChild(a);
    a.click();
    document.head.removeChild(a);
    URL.revokeObjectURL(url);
  }

  function importCSV() {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = '.csv,.txt';
    input.onchange = (e) => {
      const file = e.target.files[0];
      if (!file) return;
      const reader = new FileReader();
      reader.onload = (ev) => {
        const text = ev.target.result;
        const imported = parseCSV(text);
        const count = Object.keys(imported).length;
        if (count === 0) {
          showToast('导入失败,请检查文件格式。', 'error');
          return;
        }
        // 合并到自定义存储
        const existing = GM_getValue(CUSTOM_KEY, {});
        const merged = Object.assign({}, existing, imported);
        GM_setValue(CUSTOM_KEY, merged);
        showToast(`导入成功!已加载 ${count} 条翻译词条。`, 'success');
        // 重新加载翻译
        if (window._nexusTranslator) {
          window._nexusTranslator.reload();
        } else {
          location.reload();
        }
      };
      reader.readAsText(file, 'UTF-8');
    };
    input.click();
  }

  function exportCSV() {
    const dict = window._nexusTranslator ? window._nexusTranslator.dict : Object.assign({}, DEFAULT_TRANSLATIONS, GM_getValue(CUSTOM_KEY, {}));
    const csv = toCSV(dict);
    downloadFile(csv, 'nexusmods-translations.csv', 'text/csv;charset=utf-8');
  }

  function exportTemplate() {
    const csv = toCSV(DEFAULT_TRANSLATIONS);
    downloadFile(csv, 'nexusmods-translations-template.csv', 'text/csv;charset=utf-8');
  }

  function resetCustom() {
    showConfirm('确定要清除所有自定义翻译词条吗?\n(内置翻译不受影响)', () => {
      GM_setValue(CUSTOM_KEY, {});
      showToast('已清除所有自定义词条,即将刷新页面。', 'success');
      setTimeout(() => location.reload(), 1000);
    });
  }

  // ═══════════════════════════════════════════════
  //  油猴菜单
  // ═══════════════════════════════════════════════

  // 语言切换菜单
  const langPref = GM_getValue(LANG_KEY, 'auto');
  const langLabels = { 'auto': '自动', 'zh-CN': '简体中文', 'en': 'English' };
  const langCurrent = langLabels[langPref] || '自动';
  GM_registerMenuCommand(`🌐 界面语言:${langCurrent}`, () => {
    // 循环切换:auto → zh-CN → en → auto
    const cycle = { 'auto': 'zh-CN', 'zh-CN': 'en', 'en': 'auto' };
    const next = cycle[langPref] || 'auto';
    GM_setValue(LANG_KEY, next);
    const nextLabel = langLabels[next];
    showToast(`已切换为「${nextLabel}」,正在刷新…`, 'info', 1200);
    setTimeout(() => location.reload(), 1200);
  });

  GM_registerMenuCommand('📥 导入翻译词条 (CSV)', importCSV);
  GM_registerMenuCommand('📤 导出当前翻译词条', exportCSV);
  GM_registerMenuCommand('📋 导出默认词条模板', exportTemplate);
  GM_registerMenuCommand('🗑️ 清除自定义词条', resetCustom);

  const dateL10nEnabled = GM_getValue(DATE_L10N_KEY, true);
  const time24Enabled = GM_getValue(TIME24_KEY, true);
  GM_registerMenuCommand(`📅 日期本地化:${dateL10nEnabled ? '开启 ✓' : '关闭'}`, () => {
    GM_setValue(DATE_L10N_KEY, !dateL10nEnabled);
    location.reload();
  });
  GM_registerMenuCommand(`🕐 时间格式:${time24Enabled ? '24小时制 ✓' : '12小时制(AM/PM)'}`, () => {
    GM_setValue(TIME24_KEY, !time24Enabled);
    location.reload();
  });

  // ═══════════════════════════════════════════════
  //  启动!
  // ═══════════════════════════════════════════════
  const translator = new Translator();
  window._nexusTranslator = translator;
  translator.start();

})();