纯原生 JavaScript HTML 翻译库,支持 原文 / 双语对照 / 译文 三种模式无缝切换。专为邮件、电商页面、CMS 内容等复杂 HTML 场景设计,完整保留原始 DOM 结构、样式属性和内联标签。
是 html-translator 的改进版本,公开 API 与使用姿势与 html-translator 完全一致,但底层依赖替换为更通用、更主流、Star 数 > 10K 的开源库:
| 模块 | html-translator | mail-translator |
|---|---|---|
| HTML Sanitizer | isomorphic-dompurify (~2k ⭐) | dompurify (~17k ⭐) |
| HTML Parser | linkedom (~2k ⭐) | jsdom (~22k ⭐) / 原生 DOMParser |
| 批处理队列 | p-queue (~3k ⭐) | 自实现(原生 Promise + 计数器) |
| LRU 缓存 | lru-cache (~3k ⭐) | 自实现(原生 Map 插入顺序) |
| CSS 注入 | goober (~2k ⭐) | 自实现(原生 <style> 注入) |
- 三态模式切换 — 同一份翻译结果支持
original(原文)、bilingual(沉浸式双语对照)、translated(纯译文)三种输出 - HTML 结构零丢失 — 基于 DOM Patching 而非字符串拼接,所有标签、属性、嵌套关系完整保留
- 占位符保护机制 — 自动将
<img>、<a>、<br>等非文本元素替换为占位符,翻译后精确还原,LLM 不会破坏 HTML 结构 - 叶子块元素提取 — 智能识别可翻译的最小语义单元(段落、标题、列表项等),避免跨块翻译导致语义错乱
- 批量翻译 + LRU 缓存 — 内置并发控制和缓存,相同文本不重复调用 API,batchSize/batchLength/concurrency 均可用户自定义
- XSS 安全 — 集成
dompurify,输入输出双重净化 - Node.js & 浏览器双端 — Node.js 端使用
jsdom,浏览器端使用原生DOMParser,无需手动适配 - 自定义译文样式 — 支持通过
options.style配置译文颜色、字号、背景、分隔符等,运行时可动态修改 - 内置翻译 API — 提供 Microsoft Edge Translate 和 Tencent Transmart 两个免费 API,无需 API Key
- 7 个国内大模型适配器 — 兼容 OpenAI Chat Completions 格式,支持智谱/百度/腾讯/豆包/通义千问/DeepSeek/讯飞等免费模型
npm install mail-translatorNode.js 端需要额外安装 jsdom 作为可选依赖(peerDependenciesMeta.optional = true):
npm install jsdom浏览器端无需 jsdom,直接使用原生 DOM 即可。
import { translate, microsoftTransApi } from 'mail-translator';
// 执行翻译(使用内置 Microsoft 翻译 API)
const result = await translate(
'<div class="card"><h2>Welcome</h2><p>Best regards,<br/>The Team</p></div>',
microsoftTransApi,
{ sourceLang: 'en', targetLang: 'zh-CN' }
);
// 获取三种模式的 HTML
console.log(result.getHTML('original')); // 原文 HTML
console.log(result.getHTML('translated')); // 纯译文 HTML
console.log(result.getHTML('bilingual')); // 沉浸式双语 HTML(含内联样式)
// 运行时切换模式
result.setMode('translated');
console.log(result.getHTML()); // 当前模式的 HTML
// 获取翻译元数据
const meta = result.getMetadata();
console.log(meta.unitCount); // 翻译单元数
console.log(meta.charCount); // 字符数
console.log(meta.apiCallCount); // API 调用次数
console.log(meta.duration); // 耗时(ms)项目内置两个免费翻译 API(借鉴 kiss-translator),无需 API Key,浏览器中直接可用:
| API | 说明 | 浏览器直接可用 |
|---|---|---|
microsoftTransApi |
Microsoft Edge Translate,JWT 鉴权,支持真批量 | ✅ |
tencentTransApi |
Tencent Transmart,支持真批量 | ✅ |
import { translate, microsoftTransApi, tencentTransApi } from 'mail-translator';
// 使用 Microsoft
const r1 = await translate(html, microsoftTransApi, { sourceLang: 'en', targetLang: 'zh-CN' });
// 使用腾讯
const r2 = await translate(html, tencentTransApi, { sourceLang: 'en', targetLang: 'zh-CN' });项目内置 7 个国内大模型翻译适配器工厂,所有接口兼容 OpenAI Chat Completions 格式,只需传入 API Key 即可使用:
| 厂商 | 工厂函数 | 默认免费模型 | 免费额度 | 注册方式 |
|---|---|---|---|---|
| 智谱 AI | createZhipuTransApi(key) |
GLM-4-Flash | 永久免费,无限调用 | 手机号 |
| 百度千帆 | createBaiduTransApi(key) |
ERNIE-Lite-8K | 永久免费,QPS 限速 | 百度账号 + 实名 |
| 腾讯混元 | createHunyuanTransApi(key) |
Hunyuan-Lite | 永久免费,不限 Token | 微信/QQ 登录 |
| 火山引擎 | createDoubaoTransApi(key, {model}) |
Doubao-Lite | 每日 200 万 Tokens 刷新 | 实名认证 |
| 阿里云百炼 | createQwenTransApi(key) |
Qwen-Turbo | 每月 100 万 Tokens(永久) | 支付宝/淘宝 |
| DeepSeek | createDeepSeekTransApi(key) |
DeepSeek-Chat | 500 万+ Tokens 永久 | 手机号 |
| 讯飞星火 | createSparkTransApi(key) |
Spark Lite | 永久免费,无限 Token | 实名认证 |
import { translate, createZhipuTransApi, createQwenTransApi } from 'mail-translator';
// 创建智谱适配器(永久免费)
const zhipuApi = createZhipuTransApi('your-zhipu-api-key');
const result = await translate(html, zhipuApi, { sourceLang: 'en', targetLang: 'zh-CN' });
// 创建通义千问适配器
const qwenApi = createQwenTransApi('your-dashscope-api-key');
const result2 = await translate(html, qwenApi, { sourceLang: 'en', targetLang: 'zh-CN' });
// 指定模型
const qwenPlus = createQwenTransApi('key', { model: 'qwen-plus' });提示:如需接入其他 OpenAI 兼容的大模型 API,可使用通用工厂函数
createOpenAICompatibleTransApi(config)自行适配。
translate() 的第二个参数是一个翻译 API 适配器对象,只需实现一个 translate 方法:
interface TransApi {
/**
* @param texts - 待翻译文本数组,可能包含占位符如 {{__A1}}、<__T1>...</__T1>
* @param options - 翻译选项
* @returns 翻译结果数组,必须保留所有占位符原样不动
*/
translate(
texts: string[],
options: {
targetLang: string;
sourceLang: string;
context?: string;
glossary?: Record<string, string>;
}
): Promise<string[]>;
}译文中必须原样保留所有占位符! 占位符代表被保护的 HTML 标签(图片、链接、换行等),如果 LLM 修改、删除或重新排序了占位符,对应的 HTML 元素将无法还原。
占位符格式说明:
| 格式 | 含义 | 示例 |
|---|---|---|
{{__A1}} |
原子元素(img/video/audio 等) | <img src="..."> |
<__T1>content</__T1> |
行内标签(a/span/b/i 等) | <a href="...">text</a> |
{{__E1}} |
HTML 实体 | |
const transApi = {
async translate(texts, options) {
const prompt = `Translate the following texts from ${options.sourceLang} to ${options.targetLang}.
IMPORTANT: Keep all placeholders like {{__A1}}, <__T1>...</__T1> exactly as they are. Do NOT modify, remove or reorder them.
Return ONLY a JSON array of translated strings.`;
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
body: JSON.stringify({
model: 'gpt-4o',
messages: [
{ role: 'system', content: prompt },
{ role: 'user', content: JSON.stringify(texts) },
],
}),
});
const data = await response.json();
return JSON.parse(data.choices[0].message.content);
},
};通过 options.batch 可自定义批量翻译的分批策略,适合不同场景的性能调优:
// 方式一:通过 translate() 的 options.batch 传入(每次调用可不同)
const result = await translate(html, api, {
targetLang: 'zh-CN',
batch: { batchSize: 20, batchLength: 5000 }
});
// 方式二:直接配置全局队列(影响后续所有调用)
import { globalBatchQueue } from 'mail-translator';
globalBatchQueue.setBatchOptions({ batchSize: 15, concurrency: 5 });
// 方式三:创建自定义队列实例
import { TranslationBatchQueue } from 'mail-translator';
const myQueue = new TranslationBatchQueue({
batchSize: 30,
batchLength: 20000,
concurrency: 8,
cacheMax: 500,
cacheTTL: 7200000 // 2小时
});| 参数 | 默认值 | 说明 |
|---|---|---|
batchSize |
10 | 每批最大文本数量 |
batchLength |
10000 | 每批最大字符总长度 |
concurrency |
3 | 最大并发请求数 |
cacheMax |
1000 | LRU 缓存最大条目数 |
cacheTTL |
3600000 | 缓存过期时间(毫秒) |
通过 options.style 配置译文外观,支持译文文字、分隔符、容器等全方位自定义:
const result = await translate(html, microsoftTransApi, {
sourceLang: 'en',
targetLang: 'zh-CN',
style: {
// 译文文字样式
target: {
color: '#ff0000', // 文字颜色
fontSize: '16px', // 字号
fontWeight: 'bold', // 粗细
backgroundColor: '#ffff00', // 背景色
padding: '2px 6px', // 内边距
borderRadius: '4px', // 圆角
textDecoration: 'underline' // 文字装饰
},
// 分隔符样式(原文与译文之间的分隔线)
separator: {
display: 'block', // 'block' | 'inline' | 'none'
height: '2px', // 高度
backgroundColor: '#ccc', // 颜色
margin: '8px 0' // 外边距
},
// 译文容器样式
wrapper: {
margin: '8px 0',
padding: '0'
},
// 原文样式
source: {
color: 'inherit',
fontSize: 'inherit'
},
// 自定义 CSS(优先级最高,覆盖以上所有)
customCSS: '.kt-target { text-shadow: 1px 1px 2px rgba(0,0,0,0.3); }'
}
});不传入 style 选项时使用默认样式(DEFAULT_STYLE_CONFIG):
const DEFAULT_STYLE_CONFIG = {
target: {
color: '#0066cc',
fontSize: 'inherit',
fontWeight: 'normal',
backgroundColor: 'rgba(0, 102, 204, 0.06)',
padding: '2px 6px',
borderRadius: '3px'
},
separator: {
display: 'block',
height: '1px',
backgroundColor: '#d0d7de',
margin: '6px 0'
},
wrapper: { margin: '6px 0', padding: '0' },
source: { color: 'inherit', fontSize: 'inherit', fontWeight: 'inherit' },
customCSS: ''
};TranslationResult 提供样式相关方法,支持翻译后动态修改:
const result = await translate(html, api, options);
// 获取当前样式配置
const style = result.getStyle();
// 获取生成的 CSS 文本
const css = result.getCSS();
// 运行时更新样式(立即生效)
result.setStyle({ target: { color: '#00ff00', fontSize: '20px' } });
// 获取不含内联 <style> 的纯双语 HTML(用于已注入样式的场景)
const rawHTML = result.getBilingualHTMLRaw();
// 获取自包含双语 HTML(含内联 <style> 标签,可直接插入任何容器)
const selfContainedHTML = result.getHTML('bilingual');import { injectTranslationStyles, getTranslationCSSText } from 'mail-translator';
// 注入到 document.head
injectTranslationStyles();
// 注入到指定容器(如 ShadowRoot)
injectTranslationStyles(shadowRoot);
// 注入自定义样式
injectTranslationStyles(null, { target: { color: 'red' } });
// 仅获取 CSS 文本(不注入)
const css = getTranslationCSSText({ target: { color: 'red' } });输入 HTML
│
▼
┌─────────────┐
│ Sanitizer │ ← DOMPurify XSS 净化
└──────┬──────┘
▼
┌─────────────┐
│ Parser Walker│ ← jsdom/原生 DOMParser 解析 + 叶子块元素提取
└──────┬──────┘
▼
┌─────────────┐
│ Serializer │ ← 占位符替换(保护 img/a/br 等)
└──────┬──────┘
▼
┌─────────────┐
│ Batch Queue │ ← 自实现并发 + LRU 缓存
└──────┬──────┘
▼
┌─────────────┐
│ Trans API │ ← 用户自定义或内置翻译服务
└──────┬──────┘
▼
┌─────────────┐
│ Deserializer │ ← 占位符还原为原始 HTML
└──────┬──────┘
▼
┌─────────────┐
│ DOM Patching │ ← 原地替换 innerHTML,构建三态输出
└──────┬──────┘
▼
original / bilingual / translated
npm test # 运行单元测试(84 个测试用例)
npm run test:watch # 监听模式项目附带一个单文件 HTML 演示页面,可直接通过 file:// 协议打开,无需 HTTP 服务器:
npm run demo # 构建浏览器 bundle 并打开 demo.html或手动操作:
npm run build # 构建 dist/mail-translator.browser.js
open examples/demo.html # 直接在浏览器打开- 左侧(占 32%):输入原始 HTML,选择翻译 API、源语言、目标语言,点击「翻译」按钮
- 右侧(占 68%):三态切换展示结果(原文 / 译文 / 双语),并显示翻译元数据
- 内置示例:简单段落、邮件样式、电商卡片、嵌套结构 4 个预设场景
- 样式自定义面板:可折叠面板,支持自定义译文颜色、字号、背景、分隔符等,实时预览
- 单文件依赖:demo.html 仅引用
dist/mail-translator.browser.js(IIFE 格式,自包含 dompurify)
| 文件 | 格式 | 用途 |
|---|---|---|
dist/index.cjs |
CommonJS | Node.js require() |
dist/index.mjs |
ESM | Node.js import / 打包工具 |
dist/mail-translator.browser.js |
IIFE | 浏览器 <script> 标签直接引用,暴露全局变量 MailTranslator,自包含 dompurify |
浏览器端使用:
<script src="dist/mail-translator.browser.js"></script>
<script>
const { translate, microsoftTransApi } = window.MailTranslator;
const result = await translate(html, microsoftTransApi, { sourceLang: 'en', targetLang: 'zh-CN' });
</script>mail-translator/
├── src/
│ ├── index.js # 公开 API 入口
│ ├── translate.js # 核心入口 translate()
│ ├── types.js # 节点分类常量、占位符前缀
│ ├── dom-environment.js # 跨环境 DOM 抽象(jsdom/原生 DOMParser)
│ ├── sanitizer.js # DOMPurify 三重清洗
│ ├── parser-walker.js # 叶子块元素提取
│ ├── serializer.js # PlaceholderManager
│ ├── deserializer.js # restoreTranslation
│ ├── batch-queue.js # 自实现并发队列
│ ├── lru-cache.js # 自实现 LRU 缓存
│ ├── style-injector.js # 原生 CSS 注入 + 自定义样式
│ ├── mode-manager.js # TranslationResult + 三态切换 + 样式管理
│ ├── builtin-apis.js # 内置翻译 API(Microsoft + Tencent + 7个LLM适配器)
│ └── translate.test.js # 单元测试
├── examples/
│ └── demo.html # 单文件演示页面(引用 dist/ 构建产物)
├── dist/ # 构建产物
│ ├── index.cjs
│ ├── index.mjs
│ └── mail-translator.browser.js
├── package.json
├── jest.config.cjs
├── rollup.config.js
└── README.md
MIT