智能替换网页词汇,创造沉浸式双语学习环境,在日常浏览中自然习得语言。
基于「可理解性输入」理论,让语言学习融入日常生活
- 精准的翻译与筛词:AI提示词严格 JSON 输出,并按照规则进行过滤,当原文与译文一致时跳过,减少无效处理。
- 智能的语言判断与分词:使用
LanguageDetector并配合正则进行语言判断,其中中文分词采用segmentit。 - 丰富的词典与发音:学习语言为英语时优先使用 Wiktionary,展示音标 / 词性 / 释义 / 例句;支持多种发音源(Chrome内置TTS/有道API/谷歌API)可选,点击单词左键发音。
- 更优的性能:批量段落翻译(单次API请求处理多段落)+ 滚动渲染(仅处理可见区域)+ LRU 热词缓存,减少 API 调用次数。
- 更好的交互体验:单词卡片集成 发音 / 记忆 / 已学会 按钮;支持右键菜单和快捷键处理/还原页面。
Sapling 基于语言学家 Stephen Krashen 的「可理解性输入 (Comprehensible Input)」理论设计:
语言习得发生在我们理解比当前水平稍高一点的输入时 (i+1)
核心逻辑:在用户浏览母语内容时,将部分词汇智能替换为学习语言,反之亦然。这种方式:
- 保持内容可理解性(大部分词汇保持原文)
- 在上下文中接触新词汇(自然语境记忆)
- 控制语言接触压力(细水长流,避免认知负荷)
Sapling 从VocabMeld二次开发而来(使用了早期代码),并从以下优秀项目中汲取灵感,并在多个维度上进行了显著改进:
| 项目 | 核心优势 | 主要局限性 |
|---|---|---|
| Ries | 综合效果优秀,单词难度划分合理 | 闭源架构、无法使用自定义大模型 API |
| illa-helper | 类似 Ries 开源版本 | 翻译质量一般、经常误译短语、难度划分不准、不支持标记已学单词 |
| qiayi | 较早出现的沉浸式局部翻译插件 | 长期未维护、无 AI 能力、整体效果一般 |
- 访问 Releases 页面
- 下载最新的
Sapling-nightly-YYYYMMDD.zip文件(如Sapling-nightly-20250101.zip) - 解压下载的文件
- 打开 Chrome 浏览器,访问
chrome://extensions/ - 开启右上角的"开发者模式"
- 点击"加载已解压的扩展程序"
- 选择解压后的文件夹
- 克隆本仓库:
git clone https://github.com/zpano/Sapling.git - 进入项目目录:
cd Sapling - 安装依赖并构建:
npm install && npm run build - 打开 Chrome 浏览器,访问
chrome://extensions/ - 开启右上角的"开发者模式"
- 点击"加载已解压的扩展程序"
- 选择
Sapling文件夹
- 点击扩展图标,进入设置页面
- 选择预设服务(推荐 DeepSeek)或自定义配置
- 填入 API 密钥
- 点击"测试连接"确认配置正确
- OpenAI 兼容 API:支持任何 OpenAI 格式的 API(OpenAI、DeepSeek、Moonshot、Groq、Ollama 等)
- 自定义配置:用户可配置 API 端点、密钥、模型名称
- 连接测试:提供一键测试 API 连通性功能
- 提示词优化:英文prompt + JSON 严格输出,减少误译与格式偏差
LLM 根据以下规则选择替换词汇:
- 避免替换:专有名词、数字、代码、URL
- 优先选择:常用词汇、有学习价值的词汇
- 难度评估:为每个词汇标注 CEFR 等级(A1-C2)
- 语言上下文:将母语/学习语言注入选择逻辑,减少误判
- 动态数量:根据用户设置的单词量(低/中/高)动态调整翻译数量
- 一致性过滤:原文与译文一致(英文忽略大小写)时自动跳过替换
- 母语页面:将母语词汇替换为学习语言(如 中文 → English)
- 学习语言页面:将学习语言词汇替换为母语(如 English → 中文)
- 自动检测:根据页面主要语言自动决定翻译方向
| 等级 | 描述 | 词汇特征 |
|---|---|---|
| A1 | 入门级 | 最基础的日常词汇,如 hello, thank you |
| A2 | 初级 | 简单日常交流词汇,如 weather, family |
| B1 | 中级 | 一般性话题词汇,如 opinion, experience |
| B2 | 中高级 | 抽象概念词汇,如 consequence, implement |
| C1 | 高级 | 专业/学术词汇,如 ubiquitous, paradigm |
| C2 | 精通级 | 罕见/文学词汇,如 ephemeral, quintessential |
难度过滤逻辑:用户选择 B2 时,仅显示 B2、C1、C2 难度的词汇(即该等级及以上),跳过过于简单的词汇。
三档强度设置:
| 强度 | 每段最大替换数 | 使用场景 |
|---|---|---|
| 较少 | 4 词 | 轻度学习,保持阅读流畅 |
| 适中 | 8 词 | 日常学习,平衡阅读与学习 |
| 较多 | 14 词 | 强化学习,最大化词汇接触 |
- 容量:默认 2048,范围 2048-8192(每 1024 为一档,可在设置页滑块/输入框调整)
- 存储格式:
原文:源语言:目标语言作为键 - 持久化:使用
storage.local(Chrome Storage Local)存储,跨会话保留 - 淘汰策略:达到上限时淘汰最旧的缓存
- 配置同步:缓存上限使用
storage.remote(Chrome Storage Sync)保存
- 发送 API 请求前,检查文本中是否有已缓存词汇
- 已缓存词汇直接使用缓存结果,不发送给 API
- 只将未缓存的词汇发送给 LLM 处理
- 新词汇处理完成后加入缓存
- 缓存词汇数量(上限由用户设置)
- 命中率百分比
- 搜索功能:支持按单词或翻译搜索
- 难度筛选:按 CEFR 等级(A1-C2)筛选
- 难度显示:每个单词显示难度标签
- 可在设置页面查看和清空
- 智能缓存策略:优先使用缓存结果,大幅提升响应速度
- 先展示缓存:缓存结果立即显示,无需等待 API
- 异步处理:未缓存词汇后台异步处理,不阻塞页面
- 避免重复:已替换的词汇不会被重复替换
用户可将已掌握的词汇加入白名单,这些词汇:
- 不再被替换:浏览时保持原文显示
- 不发送给 API:从请求文本中预先移除
- 持久存储:使用
storage.local存储(避免 sync 配额限制) - 难度记录:保存词汇的难度信息,便于管理
- 页面交互:悬停提示框点击“已学会”,或右键点击译词标记
- 自动恢复:标记后可恢复当前词或同词全部替换(设置可选)
- 搜索功能:支持按单词或翻译搜索
- 难度筛选:按 CEFR 等级(A1-C2)筛选
- 难度显示:每个单词显示难度标签
- 删除功能:支持单个删除已学会的词汇
用户可将页面上的生词加入"需记忆"列表,便于后续复习。
- 悬停提示框:在已替换词汇上点击“记忆”
- 右键菜单:选中文本后右键“添加到需记忆列表”
- 自动翻译:添加到记忆列表后,立即触发翻译并更新页面,走缓存流程
- 搜索功能:支持按单词或翻译搜索
- 难度筛选:按 CEFR 等级(A1-C2)筛选
- 难度显示:每个单词显示难度标签
- 清空功能:一键清空记忆列表
- 跳过明显的代码文本(变量声明、命令行等)
- 跳过特定 HTML 标签(script, style, code, pre 等)
- 跳过隐藏元素;默认跳过可编辑区域(
contenteditable),但可对特定只读/预览区域做白名单处理
- 默认只处理可见区域及 30% 缓冲区的内容
- 滚动时按需处理新进入可见区域的内容
- 为每段文本生成指纹(取内容前100字符的哈希)
- 已处理的指纹存入 Set,避免重复处理
- 页面重新处理时清空指纹缓存
- 中文分词采用 segmentit,提升词边界准确度与替换质量
支持三种显示样式(可在设置中选择):
| 样式 | 显示格式 | 说明 |
|---|---|---|
| 译文(原文) | translated(original) |
默认样式 |
| 仅译文 | translated |
只显示译文,悬停查看原文 |
| 原文(译文) | original(translated) |
原文在前,译文在后 |
所有样式都支持:
- 翻译词以高亮显示,带虚线下划线
- 原文以灰色显示(在括号中或通过悬停查看)
鼠标悬停在替换词汇上时显示:
- 音标(永远展示学习语言的发音)
- 难度等级徽章(如 B2)
- 词性与释义(英语学习词优先 Wiktionary,可展示例句)
- 操作按钮:发音 / 记忆 / 已学会
- Windows/Linux:
Alt+T;macOS:Option+T:快速切换处理/还原当前页面 - 也可通过点击扩展的“处理当前页面”按钮或页面右键菜单“处理/还原当前页面”,状态自动切换并同步显示
| 指标 | 说明 | 统计规则 |
|---|---|---|
| 累计接触 | 总共接触的新词汇数 | 只计算未命中缓存的词汇 |
| 今日接触 | 当天接触的新词汇数 | 每日 0 点重置 |
| 已学会 | 白名单中的词汇数 | 用户手动标记 |
| 需记忆 | 需记忆列表中的词汇数 | 用户手动添加 |
| 已缓存 | 热词缓存中的词汇数 | 自动管理 |
| 命中率 | 缓存命中百分比 | hits / (hits + misses) |
- API 端点 URL
- API 密钥(安全存储)
- 模型名称
- 预设快捷选择(OpenAI/DeepSeek/Moonshot/Groq/Ollama)
- 母语语言(zh-CN, zh-TW, en, ja, ko)
- 学习语言(en, zh-CN, zh-TW, ja, ko, fr, de, es)
- 难度等级(A1-C2)
- 替换强度(较少/适中/较多)
- 插件启用开关(Popup 中一键启用/禁用)
- 自动处理开关(开启后自动处理新页面)
- 音标显示开关
- 左键发音开关:开启后点击译词直接发音
- 发音来源:Wiktionary / 有道翻译(英语) / Google 翻译 TTS(多语言)
- 有道口音(type=1 英音 / type=2 美音;仅在学习语言为英语时生效)
- 翻译显示样式:三种样式可选
- 译文(原文) - 默认样式
- 仅译文 - 只显示译文,悬停查看原文
- 原文(译文) - 原文在前,译文在后
- 保存方式:修改后自动保存;同时提供右上角“手动保存”按钮,右下角提示保存成功/失败
- 已学会词汇:查看、搜索、筛选、删除已学会的词汇
- 需记忆词汇:查看、搜索、筛选需记忆的词汇
- 已缓存词汇:查看、搜索、筛选已缓存的词汇
- 所有列表都支持:
- 搜索功能(按单词或翻译)
- 难度筛选(A1-C2)
- 难度标签显示
- 清空功能
- 黑名单:永不处理的域名列表
- 白名单:始终处理的域名列表
- 缓存容量上限:滑块/输入框调节(范围 2048-8192,每 1024 为一档;调小会自动删除最旧缓存)
- 并发上限(1-20,默认 5):同时发起的 API 请求数
- 每次请求最大段落数(1-10,默认 3):单次 API 请求中包含的段落数量,越大越省 API 调用
- 处理完整页面开关:关闭时只处理可见区域及 30% 缓冲区(默认),开启时处理整个页面
| 服务商 | 端点 | 推荐模型 |
|---|---|---|
| OpenAI | https://api.openai.com/v1/chat/completions |
gpt-4o-mini |
| DeepSeek | https://api.deepseek.com/chat/completions |
deepseek-chat |
| Moonshot | https://api.moonshot.cn/v1/chat/completions |
moonshot-v1-8k |
| Groq | https://api.groq.com/openai/v1/chat/completions |
llama-3.1-8b-instant |
| Ollama | http://localhost:11434/v1/chat/completions |
qwen2.5:7b |
Sapling/
Sapling/
├── _locales/ # 国际化文件
│ ├── en/
│ │ └── messages.json
│ └── zh_CN/
│ └── messages.json
├── css/ # 样式文件
│ ├── content.css # 注入页面的样式
│ ├── options.css # 设置页面样式
│ └── popup.css # 弹出窗口样式
├── dist/ # 构建产物(content script bundle)
│ ├── content.js
│ └── content.js.map
├── icons/ # 图标文件
│ ├── icon.svg
│ └── generate_icons.html # 图标生成工具
├── js/ # JavaScript 源码
│ ├── background.js # 后台脚本
│ ├── content.js # 内容脚本 (核心逻辑)
│ ├── offscreen-audio.js # Offscreen 音频播放
│ ├── options.js # 设置页面脚本
│ ├── popup.js # 弹出窗口脚本
│ ├── config/ # 配置常量
│ │ └── constants.js
│ ├── core/ # 核心模块
│ │ ├── config.js # 配置管理
│ │ └── storage/ # 存储抽象层
│ │ ├── IStorageAdapter.js # 存储适配器接口
│ │ ├── ChromeStorageAdapter.js # Chrome Storage 实现
│ │ ├── StorageNamespace.js # 存储命名空间(回调/Promise 双风格)
│ │ └── StorageService.js # 存储服务门面
│ ├── prompts/ # 提示词模板
│ │ └── ai-prompts.js
│ ├── services/ # 服务模块
│ ├── api-service.js # API 服务
│ ├── cache-service.js # 缓存服务
│ ├── content-segmenter.js # 内容分段
│ └── text-replacer.js # 文本替换
│ ├── ui/ # UI 组件(Toast/Tooltip/发音等)
│ └── utils/ # 工具函数(分词/过滤/语言检测等)
├── vendor/ # 第三方依赖打包
│ ├── segmentit.bundle.js
│ └── segmentit.bundle.js.map
├── manifest.json # Chrome 扩展配置
├── offscreen.html # Offscreen Document
├── options.html # 设置页面
├── popup.html # 弹出窗口
├── package.json # 项目配置
├── scripts/ # 构建脚本
│ └── build.js
└── README.md # 项目说明
-
智能分段与按需加载
- 通过
ContentSegmenter对页面 DOM 进行遍历,将内容按"语义单元"智能分段 - 默认只处理可见区域及 30% 缓冲区的内容,滚动时按需加载后续段落
- 为每个内容段落生成唯一指纹,防止重复处理
- 通过
-
批量翻译与语言队列
- 同语言方向的段落自动分组,合并为单次 API 请求(默认 3 段落/请求)
- 2 秒 debounce 收集段落,或达到批量阈值时立即发送
- 缓存命中的词汇立即显示,未缓存部分异步请求,不阻塞页面
-
精准且高效的 DOM 替换
- 采用原生 Range API 精确定位和替换文本节点,保持页面结构完整
- 自动排除代码块、脚本、样式、已处理内容等
- 动态内容采用防抖优化,MutationObserver 监听 DOM 变化
-
智能缓存与状态管理
- 缓存机制:默认 2048 词容量(可调至 8192),达到上限时淘汰最旧的缓存
- 并发控制:默认 5 个并发请求(可在设置中调节至 1-20)
- 状态追踪:防止重复处理,优化用户体验
const CEFR_LEVELS = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'];
function isDifficultyCompatible(wordDifficulty, userDifficulty) {
const wordIdx = CEFR_LEVELS.indexOf(wordDifficulty);
const userIdx = CEFR_LEVELS.indexOf(userDifficulty);
// 只显示大于等于用户选择难度的词汇
return wordIdx >= userIdx;
}- 推荐配置:母语中文 + 学习英语 + B1难度 + 适中强度
- 快捷键:Windows/Linux
Alt+T;macOSOption+T快速切换处理/还原当前页面(推荐) - 已学会标记:悬停译词点击“已学会”或右键标记,可选择恢复同词
- 记忆按钮:悬停译词点击“记忆”,或选中文本右键添加到需记忆
- 缓存加速:第二次访问同一页面,响应速度显著提升(毫秒级)
- 智能过滤:自动跳过停用词和已缓存词汇,节省 API 调用
- 自动/手动保存:设置会自动保存;右上角可手动保存;右下角会提示保存成功/失败
- Chrome Extension Manifest V3
- Vanilla JavaScript (ES6+)
- CSS Variables + Modern CSS
- esbuild(用于打包
dist/content.js和vendor/segmentit.bundle.js)
项目使用分层存储架构:
┌─────────────────────────────────────┐
│ StorageService │ 高级API
│ storage.remote / storage.local │
├─────────────────────────────────────┤
│ StorageNamespace │ 低级 API(get/set/remove/onChanged)
│ 回调风格 + Promise 风格 │ 支持 DEFAULT_CONFIG 合并
├─────────────────────────────────────┤
│ IStorageAdapter │ 适配器接口
│ ChromeStorageAdapter (当前) │ 未来可替换为 WebDAVAdapter
└─────────────────────────────────────┘
- storage.remote:配置数据(Chrome Storage Sync),支持跨设备同步
- storage.local:大容量数据(Chrome Storage Local),如词汇缓存、已学会词汇
- 修改
options.html/js/options.js、popup.html/js/popup.js、js/background.js等:在chrome://extensions/点击刷新即可 - 修改内容脚本相关代码(
js/content.js及其依赖模块):先运行npm run build(或npm run watch),再到chrome://extensions/刷新扩展
项目使用 GitHub Actions 自动构建和发布:
- 触发条件: 每天 UTC 0:00(北京时间 8:00)自动运行,也支持手动触发
- 构建条件: 仅在过去 24 小时内有新提交时才会构建
- 文件名格式:
Sapling-nightly-20250101.zip(日期格式) - Release 标签:
nightly-20250101 - 状态: Pre-release(开发预览版)
- Release 内容: 包含过去 24 小时内所有 commit 的简短信息
- 用途: 用于测试和预览最新功能
手动触发 Nightly Build:
- 访问 GitHub 仓库的 Actions 页面
- 选择 "Nightly Build" workflow
- 点击 "Run workflow" 按钮
