// ==UserScript==
// @name Hugging Face 中文化插件
// @namespace https://github.com/your-username/hf-chinese
// @description 中文化 Hugging Face 界面的菜单及内容
// @copyright 2025
// @icon https://huggingface.co/front/assets/huggingface_logo-noborder.svg
// @version 1.0.2
// @author 蛋定的文弱书生
// @license GPL-3.0
// @match https://huggingface.co/*
// @match https://*.huggingface.co/*
// @run-at document-end
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_notification
// @grant GM_getValue
// @grant GM_setValue
// @supportURL https://github.com/1972074121/Youhou/issues
// ==/UserScript==
(function (window, document, undefined) {
'use strict';
// 语言设置
const lang = 'zh';
// 词库配置
const I18N = {
zh: {
// 公共词库(所有页面通用)
public: {
static: {
// 导航栏
"Models": "模型",
"Datasets": "数据集",
"Spaces": "空间",
"Docs": "文档",
"Solutions": "解决方案",
"Pricing": "价格",
"Sign in": "登录",
"Sign Up": "注册",
"Search": "搜索",
// 侧边栏
"Filters": "筛选器",
"All": "全部",
"Text": "文本",
"Image": "图像",
"Audio": "音频",
"Video": "视频",
"Multimodal": "多模态",
"Table": "表格",
"Fill-Mask": "掩码填充",
"Token Classification": "标记分类",
"Text Generation": "文本生成",
"Text2Text Generation": "文本到文本生成",
"Summarization": "摘要生成",
"Conversational": "对话",
"Feature Extraction": "特征提取",
"Translation": "翻译",
"Multiple Choice": "多项选择",
"Text Classification": "文本分类",
"Question Answering": "问答",
"Sentence Similarity": "句子相似度",
"Zero-Shot Classification": "零样本分类",
"Enterprise": "企业版",
"Audio-Text-to-Text": "音频文本转文本",
"Image-Text-to-Text": "图像文本转文本",
"Visual Question Answering": "视觉问答",
"Document Question Answering": "文档问答",
"Video-Text-to-Text": "视频文本转文本",
"Visual Document Retrieval": "视觉文档检索",
"Any-to-Any": "任意到任意",
"Computer Vision": "计算机视觉",
"Depth Estimation": "深度估计",
"Image Classification": "图像分类",
"Object Detection": "目标检测",
"Image Segmentation": "图像分割",
"Text-to-Image": "文本到图像",
"Image-to-Text": "图像到文本",
"Image-to-Image": "图像到图像",
"Image-to-Video": "图像到视频",
"Unconditional Image Generation": "无条件图像生成",
"Video Classification": "视频分类",
"Text-to-Video": "文本到视频",
"Zero-Shot Image Classification": "零样本图像分类",
"Mask Generation": "掩码生成",
"Zero-Shot Object Detection": "零样本目标检测",
"Text-to-3D": "文本到3D",
"Image-to-3D": "图像到3D",
"Image Feature Extraction": "图像特征提取",
"Keypoint Detection": "关键点检测",
"Natural Language Processing": "自然语言处理",
"Table Question Answering": "表格问答",
"Text Ranking": "文本排序",
"Text-to-Speech": "文本到语音",
"Text-to-Audio": "文本到音频",
"Automatic Speech Recognition": "自动语音识别",
"Audio-to-Audio": "音频到音频",
"Audio Classification": "音频分类",
"Voice Activity Detection": "语音活动检测",
"Tabular": "表格数据",
"Tabular Classification": "表格分类",
"Tabular Regression": "表格回归",
"Time Series Forecasting": "时间序列预测",
"Reinforcement Learning": "强化学习",
"Robotics": "机器人学",
"Other": "其他",
"Graph Machine Learning": "图机器学习",
// 按钮
"Load more": "加载更多",
"Subscribe": "订阅",
"Download": "下载",
"Upload": "上传",
"Create": "创建",
"Settings": "设置",
"Profile": "个人资料",
"Logout": "退出登录",
// 模型卡片
"Model card": "模型卡片",
"Files and versions": "文件与版本",
"Community": "社区",
"Training metrics": "训练指标",
"Training logs": "训练日志",
"Deploy": "部署",
"Use in Transformers": "在Transformers中使用",
"Hosted inference API": "托管推理API",
"Contributors": "贡献者",
"License": "许可证",
// 空间相关
"Duplicate this Space": "复制此空间",
"Embed this Space": "嵌入此空间",
"App": "应用",
"Files": "文件",
"Sessions": "会话",
"Hardware": "硬件",
"Storage": "存储",
"Variables": "变量",
"Logs": "日志",
// 文档
"Previous": "上一页",
"Next": "下一页",
"On this page": "本页内容",
"Table of contents": "目录",
"Getting Started": "入门指南",
"Tutorials": "教程",
"Conceptual Guides": "概念指南",
"How-to Guides": "操作指南",
"API Documentation": "API文档",
// 新增翻译内容
"Tasks": "任务",
"Libraries": "库",
"Languages": "语言",
"Licenses": "许可证",
"Website": "网站",
"HuggingChat": "Hugging聊天",
"Collections": "收藏集",
"Organizations": "组织",
"Blog": "博客",
"Posts": "帖子",
"Daily Papers": "每日论文",
"Learn": "学习",
"Discord": "Discord社区",
"Forum": "论坛",
"Github": "GitHub",
"Enterprise Hub": "企业中心",
"Expert Support": "专家支持",
"Inference Endpoints": "推理端点",
"Notifications": "通知",
"Inbox": "收件箱",
"New Model": "新建模型",
"New Dataset": "新建数据集",
"New Space": "新建空间",
"New Collection": "新建收藏集",
"Create organization": "创建组织",
"Usage Quota": "使用配额",
"Private Storage": "私有存储",
"Zero GPU": "零GPU",
"Inference Usage": "推理使用量",
"Subscribe to PRO": "订阅PRO版",
"Access Tokens": "访问令牌",
"Billing": "账单",
"Changelog": "更新日志",
"Sign Out": "退出登录",
"AI & ML interests": "AI与ML兴趣",
"Recent Activity": "最近活动",
"Account": "账户",
"Authentication": "认证",
"SSH and GPG Keys": "SSH和GPG密钥",
"Inference Providers": "推理提供商",
"Webhooks": "Webhooks",
"Papers": "论文",
"Local Apps and Hardware": "本地应用与硬件",
"Gated Repositories": "受限仓库",
"Content Preferences": "内容偏好",
"Connected Apps": "已连接应用",
"Theme": "主题",
"Discussions": "讨论",
"Pull requests": "拉取请求",
"Welcome to the community": "欢迎来到社区专区",
"The community tab is the place to discuss and collaborate with the HF community!": "此处是您与 HF 社区交流协作的专属空间!",
"New discussion": "发起新讨论",
"New pull request": "创建拉取请求",
"Watch all activity": "查看所有活动动态",
"View closed": "查看已关闭项",
"Discussions": "讨论",
"Short": "排序",
"Recently created": "最近创建",
"Most reactions": "最多互动",
"Trending": "热门内容",
"Filter by title": "按标题筛选",
"Resources": "资源",
"Search models, datasets, users...": "搜索模型、数据集、用户...",
"No model card": "无模型卡片",
"New: Create and edit this model card directly on the website!": "新功能:直接在线创建并编辑模型卡片!",
"Contribute a Model Card": "贡献模型卡片",
"Adapters": "适配器",
"Finetunes": "微调模型",
"Merges": "合并模型",
"Quantizations": "量化模型"
},
regexp: [
// 正则替换规则
[/(\d+) days? ago/, '$1天前'],
[/(\d+) hours? ago/, '$1小时前'],
[/(\d+) minutes? ago/, '$1分钟前'],
[/Just now/, '刚刚'],
[/(\d+) downloads?/, '$1次下载'],
[/(\d+) likes?/, '$1个点赞'],
[/View closed (\d+)?/, '查看已关闭项 $1'],
]
},
// 主页特定词库
homepage: {
static: {
"Models, datasets and Spaces": "模型、数据集与空间",
"Discover, explore and share ML resources": "发现、探索并分享机器学习资源",
"Trending": "热门",
"New": "最新",
"Top": "最佳",
"Top contributors": "顶级贡献者",
"Featured Spaces": "精选空间",
"All Spaces": "所有空间",
"Explore": "探索",
"Browse models": "浏览模型",
"Browse datasets": "浏览数据集",
"Browse Spaces": "浏览空间",
}
},
// 模型列表页
models: {
static: {
"Browse models": "浏览模型",
"Sort:": "排序:",
"Most likes": "最多点赞",
"Most downloads": "最多下载",
"Recently updated": "最近更新",
"Task": "任务",
"Library": "库",
"Dataset": "数据集",
"Architecture": "架构",
"Languages": "语言",
"Licenses": "许可证",
"Model name or keyword": "模型名称或关键词",
"Search models": "搜索模型",
}
},
// 数据集列表页
datasets: {
static: {
"Browse datasets": "浏览数据集",
"Sort:": "排序:",
"Most likes": "最多点赞",
"Most downloads": "最多下载",
"Recently updated": "最近更新",
"Task": "任务",
"Library": "库",
"Language": "语言",
"Licenses": "许可证",
"Dataset name or keyword": "数据集名称或关键词",
"Search datasets": "搜索数据集",
}
},
// 空间列表页
spaces: {
static: {
"Browse Spaces": "浏览空间",
"Sort:": "排序:",
"Most likes": "最多点赞",
"Recently updated": "最近更新",
"SDK": "SDK",
"Hardware": "硬件",
"License": "许可证",
"Space name or keyword": "空间名称或关键词",
"Search Spaces": "搜索空间",
}
},
// 文档页
docs: {
static: {
"Hugging Face Documentation": "Hugging Face 文档",
"Search the docs": "搜索文档",
"Edit this page": "编辑此页",
"Feedback": "反馈",
}
},
// 模型详情页
model_detail: {
static: {
"Model description": "模型描述",
"Intended uses & limitations": "预期用途与限制",
"How to use": "如何使用",
"Limitations and bias": "限制与偏见",
"Training data": "训练数据",
"Training procedure": "训练过程",
"Evaluation results": "评估结果",
"Citation": "引用",
"Model card authors": "模型卡片作者",
"Model card contributors": "模型卡片贡献者",
}
},
// 空间详情页
space_detail: {
static: {
"Running on": "运行于",
"Last updated": "最后更新",
"Created by": "创建者",
"App files": "应用文件",
"README.md": "自述文件",
}
}
}
};
// 全局变量
let page;
let enable_RegExp = GM_getValue("enable_RegExp", 1);
/**
* watchUpdate 函数:监视页面变化,根据变化的节点进行翻译
*/
function watchUpdate() {
// 检测浏览器是否支持 MutationObserver
const MutationObserver =
window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver;
// 获取当前页面的 URL
const getCurrentURL = () => location.href;
getCurrentURL.previousURL = getCurrentURL();
// 创建 MutationObserver 实例,监听 DOM 变化
const observer = new MutationObserver((mutations, observer) => {
const currentURL = getCurrentURL();
// 如果页面的 URL 发生变化
if (currentURL !== getCurrentURL.previousURL) {
getCurrentURL.previousURL = currentURL;
page = getPage(); // 当页面地址发生变化时,更新全局变量 page
console.log(`链接变化 page= ${page}`);
if (page) {
setTimeout(() => {
// 重新翻译页面
traverseNode(document.body);
}, 500);
}
}
if (page) {
// 使用 filter 方法对 mutations 数组进行筛选,
// 返回 `节点增加、文本更新 或 属性更改的 mutation` 组成的新数组 filteredMutations。
const filteredMutations = mutations.filter(mutation => mutation.addedNodes.length > 0 || mutation.type === 'attributes' || mutation.type === 'characterData');
// 处理每个变化
filteredMutations.forEach(mutation => traverseNode(mutation.target));
}
});
// 配置 MutationObserver
const config = {
characterData: true,
subtree: true,
childList: true,
attributeFilter: ['value', 'placeholder', 'aria-label', 'data-confirm'], // 仅观察特定属性变化
};
// 开始观察 document.body 的变化
observer.observe(document.body, config);
}
/**
* traverseNode 函数:遍历指定的节点,并对节点进行翻译。
* @param {Node} node - 需要遍历的节点。
*/
function traverseNode(node) {
// 跳过忽略
if (node.id && /react-.*|notification|toast|modal|dialog/i.test(node.id)) {
return;
}
if (node.nodeType === Node.ELEMENT_NODE) { // 元素节点处理
// 元素节点属性翻译
if (["INPUT", "TEXTAREA"].includes(node.tagName)) { // 输入框 按钮 文本域
if (["button", "submit", "reset"].includes(node.type)) {
transElement(node, 'value');
} else {
transElement(node, 'placeholder');
}
} else if (node.tagName === 'BUTTON') {
if (node.hasAttribute('aria-label')) {
transElement(node, 'aria-label', true);
}
if (node.hasAttribute('title')) {
transElement(node, 'title', true);
}
if (node.hasAttribute('data-confirm')) {
transElement(node, 'data-confirm', true);
}
} else if (node.tagName === 'OPTGROUP') { // 翻译 <optgroup> 的 label 属性
transElement(node, 'label');
} else if (node.tagName === 'A') {
if (node.hasAttribute('title')) {
transElement(node, 'title', true);
}
}
let childNodes = node.childNodes;
childNodes.forEach(traverseNode); // 遍历子节点
} else if (node.nodeType === Node.TEXT_NODE) { // 文本节点翻译
if (node.length <= 500) {
transElement(node, 'data');
}
}
}
/**
* getPage 函数:获取当前页面的类型。
* @returns {string|boolean} 当前页面的类型,如果无法确定类型,那么返回 false。
*/
function getPage() {
const pathname = location.pathname; // 当前路径
// 根据路径判断页面类型
if (pathname === '/') {
return 'homepage';
} else if (pathname.startsWith('/models')) {
return pathname.split('/').length > 2 ? 'model_detail' : 'models';
} else if (pathname.startsWith('/datasets')) {
return pathname.split('/').length > 2 ? 'dataset_detail' : 'datasets';
} else if (pathname.startsWith('/spaces')) {
return pathname.split('/').length > 2 ? 'space_detail' : 'spaces';
} else if (pathname.startsWith('/docs')) {
return 'docs';
} else if (pathname.split('/').length === 2) {
// 例如:/username/modelname
return 'model_detail';
}
// 默认使用公共词库
return 'public';
}
/**
* transElement 函数:翻译指定元素的文本内容或属性。
* @param {Element} el - 需要翻译的元素。
* @param {string} field - 需要翻译的文本内容或属性的名称。
* @param {boolean} isAttr - 是否需要翻译属性。
*/
function transElement(el, field, isAttr = false) {
let text = isAttr ? el.getAttribute(field) : el[field]; // 需要翻译的文本
if (!text) return;
let str = translateText(text); // 翻译后的文本
// 替换翻译后的内容
if (str) {
if (!isAttr) {
el[field] = str;
} else {
el.setAttribute(field, str);
}
}
}
/**
* translateText 函数:翻译文本内容。
* @param {string} text - 需要翻译的文本内容。
* @returns {string|boolean} 翻译后的文本内容,如果没有找到对应的翻译,那么返回 false。
*/
function translateText(text) {
// 内容为空, 空白字符和或数字, 不存在英文字母和符号,. 跳过
if (!text || !/[a-zA-Z,.]+/.test(text)) {
return false;
}
let _key = text.trim(); // 去除首尾空格的 key
let _key_neat = _key.replace(/\xa0|[\s]+/g, ' '); // 去除多余空白字符( 空格 换行符)
let str = fetchTranslatedText(_key_neat); // 翻译已知页面 (局部优先)
if (str && str !== _key_neat) { // 已知页面翻译完成
return text.replace(_key, str); // 替换原字符,保留首尾空白部分
}
return false;
}
/**
* fetchTranslatedText 函数:从特定页面的词库中获得翻译文本内容。
* @param {string} key - 需要翻译的文本内容。
* @returns {string|boolean} 翻译后的文本内容,如果没有找到对应的翻译,那么返回 false。
*/
function fetchTranslatedText(key) {
// 静态翻译
let str = (I18N[lang][page]?.static?.[key]) ||
(I18N[lang].public?.static?.[key]); // 默认翻译 公共部分
if (typeof str === 'string') {
return str;
}
// 正则翻译
if (enable_RegExp) {
let res = (I18N[lang][page]?.regexp || []).concat(I18N[lang].public?.regexp || []); // 正则数组
for (let [a, b] of res) {
try {
let regex = new RegExp(a, 'g');
if (regex.test(key)) {
return key.replace(regex, b);
}
} catch (e) {
console.error('正则表达式错误:', a, e);
}
}
}
return false; // 没有翻译条目
}
function registerMenuCommand() {
const toggleRegExp = () => {
enable_RegExp = !enable_RegExp;
GM_setValue("enable_RegExp", enable_RegExp);
GM_notification(`已${enable_RegExp ? '开启' : '关闭'}正则功能`);
GM_unregisterMenuCommand(id);
id = GM_registerMenuCommand(`${enable_RegExp ? '关闭' : '开启'}正则功能`, toggleRegExp);
// 刷新页面以应用更改
location.reload();
};
let id = GM_registerMenuCommand(`${enable_RegExp ? '关闭' : '开启'}正则功能`, toggleRegExp);
}
/**
* init 函数:初始化翻译功能。
*/
function init() {
// 获取当前页面的翻译规则
page = getPage();
console.log(`开始page= ${page}`);
if (page) {
// 立即翻译页面
traverseNode(document.body);
}
// 监视页面变化
watchUpdate();
// 添加菜单命令
registerMenuCommand();
}
// 执行初始化
setTimeout(init, 1000);
})(window, document);