Nexusmods Localization

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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();

})();