Skip to content

AnyListen/mail-translator

Repository files navigation

mail-translator

纯原生 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-translator

Node.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

传统翻译 API(无需 API Key)

项目内置两个免费翻译 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' });

大模型翻译 API(OpenAI 兼容格式)

项目内置 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) 自行适配。

🔧 自定义翻译 API

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 实体 &nbsp;

接入示例

OpenAI / 兼容 API

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

📄 License

MIT

About

纯原生 JavaScript HTML 翻译库,支持 **原文 / 双语对照 / 译文** 三种模式无缝切换。专为邮件、电商页面、CMS 内容等复杂 HTML 场景设计,完整保留原始 DOM 结构、样式属性和内联标签。

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages