translation-hub-sdk
v0.3.33
Published
Runtime i18n SDK: zero-invasion DOM translation, glossary, pivot, micro-batch, dev editor
Maintainers
Readme
translation-hub-sdk
运行时国际化 SDK。零侵入接入:业务代码无需修改,SDK 自动扫描页面中文、批量翻译、缓存、术语命中。
从 i18n-jsautotranslate 实战经验中提取,替代其 DOM 集成层,同时支持直连 MTranServer 或走 Translation Hub server。
特性
- 零侵入:业务代码不动一行,SDK 用 MutationObserver 自动扫描页面中文文本
- 切换语言不丢原文:DOM 翻译后保留中文原文,可任意次切换语言
- 微批处理 + 并发限制:100ms 窗口内合并请求,同时最多 4 个在途
- Pivot 翻译:MTranServer 不支持非英语互译时,自动
zh → en → target(可关闭) - 术语表(Glossary):品牌名、专业术语统一翻译,命中后绕过翻译引擎
- 混合文本切分:中英文混排时只翻译中文段,英文段保留原样;拼接时智能加空格
- HTML 属性翻译:placeholder / title / aria-label / alt 自动翻译
- SVG 文本翻译:自动扫描 SVG
<text>节点,ECharts 用 SVGRenderer 即可零侵入翻译图表文案 - LocalStorage 缓存:按语言分桶 + 翻译失败不写入 + 静态 JSON 覆盖更新
- 服务端译文同步:SDK 在 init/switchLanguage 时拉取
/runtime/overrides,让服务端修订立即生效 - 语言检测:navigator.languages → 翻译引擎语言 ID,覆盖 70+ 浏览器语言变体
- 多 Provider:MTranServer / Edge 浏览器公共 API / Translation Hub server,可单独用或组合 fallback 链
- 静态数据懒加载:构建时打包 approved 翻译,运行时按语言按需 fetch 写入缓存
- Vite 构建插件:
vitePluginTranslateExport一行接入构建时拉取翻译数据 - URL ?lang= 解析:
parseUrlLang/readLangFromUrl兼容新旧格式(cn/zh-CN/chinese_simplified等),由createTranslationHub在init()时自动优先处理首次进入的?lang= - 跳过翻译标记:用
class="no-translate"或data-no-translate属性标记元素不翻译 - vue-i18n 兼容:
onBeforeSwitchLanguage钩子处理时序冲突,5 种新语言保持中文
支持的语言
SDK 当前支持 7 种语言,源语言固定为简体中文,目标语言 6 种:
| TranslateLangId | 中文名 | BCP-47 | Edge API | 是否源语言 |
|---|---|---|---|---|
| chinese_simplified | 简体中文 | zh-Hans | zh-CHS | ✅ 源 |
| english | 英语 | en | en | 目标 |
| japanese | 日语 | ja | ja | 目标 |
| korean | 韩语 | ko | ko | 目标 |
| french | 法语 | fr | fr | 目标 |
| german | 德语 | de | de | 目标 |
| spanish | 西班牙语 | es | es | 目标 |
为什么没有繁体中文/俄语/阿拉伯语等:与上游 MTranServer 默认支持的语言对齐。扩展步骤见 QUESTION.md。
安装
# npm registry(推荐)
npm install translation-hub-sdk
# 本地 link(开发期)
cd packages/sdk && pnpm build && pnpm link --global
# 在业务项目里:pnpm link --global translation-hub-sdk
# workspace 协议(monorepo 内)
# 业务项目 package.json: "translation-hub-sdk": "workspace:*"
# 离线 tarball
cd packages/sdk && pnpm build && npm pack
# 在业务项目里:npm install /path/to/translation-hub-sdk-x.x.x.tgz三种翻译服务(service 选项)
| service | Provider | 适用场景 | 服务端 glossary | 服务端缓存 |
|---|---|---|---|---|
| translate.service(默认) | MTranServer(+ Edge fallback) | 自部署 MTranServer,客户端直连 | ❌ | ❌ |
| hub.server | Translation Hub server | 对接托管翻译服务:服务端处理术语表、缓存与翻译引擎选路 | ✅ | ✅ |
| client.edge | Edge 浏览器公共 API | 无需部署,国内不稳定 | ❌ | ❌ |
快速开始
1. 最小接入(工厂,约 5 行)
import { createTranslationHub } from 'translation-hub-sdk';
const handle = createTranslationHub({
service: 'translate.service', // 或 'hub.server' / 'client.edge'
hubProjectKey: 'xxx', // 仅 hub.server 必填
enableDevEditor: import.meta.env.MODE !== 'production', // hub.server 模式下控制是否显示右下角编辑胶囊(编辑文案 + 批准当前页面草稿),生产环境务必关掉
glossary: { translateHub: { english: 'translateHub' } }, // 可选
onLanguageChange: (lang) => { /* 同步框架层状态 */ },
});
await handle.init(); // 必须 app.mount 之后调工厂内部自动处理:Provider 选择、静态数据懒加载、URL ?lang= 优先级、init/start 流程、语言切换钩子。所有配置项及默认值见 API.md - createTranslationHub。
2. 三种场景的接入示例
import { TranslationHub, MTranServerProvider, EdgeTranslateProvider } from 'translation-hub-sdk';
const hub = new TranslationHub({
provider: new MTranServerProvider({ url: 'http://localhost:8989' }),
fallback: new EdgeTranslateProvider(),
glossary: { translateHub: { english: 'translateHub', japanese: 'translateHub' } },
});
await hub.init();
await hub.start();import { TranslationHub, HubServerProvider } from 'translation-hub-sdk';
const hub = new TranslationHub({
provider: new HubServerProvider({
endpoint: 'http://localhost:3000/api/v1',
projectKey: 'your-project-key', // 在托管服务注册项目后获得
}),
skipPivot: true, // 服务端翻译服务已处理 pivot
glossary: { translateHub: { english: 'translateHub' } },
});
await hub.init();
await hub.start();import { TranslationClient, MTranServerProvider } from 'translation-hub-sdk';
const client = new TranslationClient({
provider: new MTranServerProvider({ url: 'http://localhost:8989' }),
glossary: { translateHub: { english: 'translateHub' } },
});
const { translation, source } = await client.translate('登录', 'english');
// translation = 'Login', source = 'translated'
const results = await client.translateBatch(['登录', '退出', 'translateHub'], 'english');
// [
// { translation: 'Login', source: 'translated' },
// { translation: 'Logout', source: 'translated' },
// { translation: 'translateHub', source: 'glossary' }
// ]DOM 翻译覆盖范围
SDK 自动扫描以下 DOM 位置的中文文案并翻译:
| 场景 | 是否翻译 | 说明 |
|---|---|---|
| Text 节点(<div>/<p>/<span> 等内文本) | ✅ | 中英文混排时只翻中文段,英文段保留 |
| HTML 属性(placeholder / title / aria-label / alt) | ✅ | 切换语言时先还原属性原文再重译 |
| SVG <text>(含 ECharts SVGRenderer) | ✅ | ECharts 必须用 SVGRenderer 而不是 CanvasRenderer |
| Canvas 渲染的图表 | ❌ | 是位图不是 DOM,改用 SVGRenderer |
| JS 变量里的中文(如 alert('成功')) | ❌ | 不在 DOM 里,用 await hub.translate(msg) 手动翻译 |
| 纯英文/数字 | ❌ | 自动跳过 |
跳过某元素翻译:加 class="no-translate" 或 data-no-translate 属性,父元素标记则子孙全部跳过:
<div class="no-translate">这段不翻译</div>
<div data-no-translate>这段也不翻译</div>
<div class="no-translate"><MyComponent /> <!-- 子组件内所有中文都跳过 --></div>自定义标记名称(避免和业务 class 冲突):
new TranslationHub({
skipTranslateClass: 'i18n-skip',
skipTranslateAttr: 'data-i18n-skip',
});手动翻译 / 重扫 DOM:
const { translation } = await hub.translate('登录成功');
alert(translation);
hub.reTranslate(); // 路由切换后重扫新渲染的 DOM
hub.clearCache(); // 不传清所有语言
hub.clearCache('english'); // 只清英文ECharts 接入要点见 QUESTION.md - ECharts 图表翻译。
项目接入指南(Vite + Vue 实战)
业务代码零侵入——业务组件完全不动,只需在入口文件初始化、路由钩子里 reTranslate、UI 里调 switchLanguage。
1. src/utils/translate.ts(项目入口,约 40 行)
import { shallowRef } from 'vue';
import {
createTranslationHub,
LANGUAGE_OPTIONS,
localLangToTranslateLang,
parseUrlLang,
type TranslateLangId,
type GlossaryEntries,
} from 'translation-hub-sdk';
export const currentLang = shallowRef<TranslateLangId>('chinese_simplified');
export { LANGUAGE_OPTIONS, localLangToTranslateLang as localeTypeToLocalLang, parseUrlLang };
// 品牌名术语表(绝不送翻译引擎)
const GLOSSARY: GlossaryEntries = {
BrandX: { english: 'BrandX', japanese: 'BrandX', korean: 'BrandX' },
BrandY: { english: 'BrandY', japanese: 'BrandY', korean: 'BrandY' },
};
const env = import.meta.env as any;
const handle = createTranslationHub({
service: env.VITE_TRANSLATE_SERVICE || 'translate.service',
mtranServerUrl: env.VITE_TRANSLATE_HOST || 'http://localhost:8989',
hubServerEndpoint: window.location.origin + (env.VITE_TRANSLATE_HUB_ENDPOINT || '/translate'),
hubProjectKey: env.VITE_TRANSLATE_HUB_PROJECT_KEY || '',
enableDevEditor: import.meta.env.MODE !== 'production', // 生产环境关掉右下角编辑胶囊(编辑文案 + 批准当前页面草稿)
langStorageKey: env.VITE_TRANSLATE_LANG_KEY || 'locale-lang',
langStorageFormat: 'local', // 写入 zh-CN / en(读取仍兼容旧 chinese_simplified / english)
defaultLang: env.VITE_DEFAULT_LANG || 'chinese_simplified',
staticDataBaseUrl: (env.BASE_URL || '/').replace(/\/$/, '') + '/translations',
glossary: GLOSSARY,
onLanguageChange: (lang) => { currentLang.value = lang; },
});
export const translationHub = handle.hub;
export const initTranslate = handle.init;
export const switchLanguage = handle.switchLanguage;
export const reTranslate = handle.reTranslate;
export const getCurrentLanguage = handle.getCurrentLanguage;
// vue-i18n 只有 zh-CN / en 两种 locale,5 种新语言让 vue-i18n 保持中文
export function localLangToLocaleType(lang: string): 'zh-CN' | 'en' {
return lang === 'english' ? 'en' : 'zh-CN';
}⚠️
hubServerEndpoint必须是绝对 URL(含协议+域名+端口)SDK 内部用
new URL(endpoint + '/runtime/glossary')拼接请求地址。如果传相对路径如/translate,new URL()会抛异常,导致 glossary / overrides 接口静默失败(被 catch 吞掉,不会报错也不会发请求)。通过 Vite proxy 转发:
hubServerEndpoint: window.location.origin + '/translate'直连服务端:
hubServerEndpoint: 'http://10.1.4.227:9002/api/v1'
2. main.ts(入口初始化)
import { initTranslate } from './utils/translate';
app.mount('#app');
await initTranslate(); // 必须在 mount 之后,等 DOM 渲染完成3. router/index.ts(路由切换后重扫 DOM)
import { reTranslate } from './utils/translate';
router.afterEach(() => {
// 100ms 延迟让 Vue 完成新页面 patch,否则 reTranslate 扫不到新 DOM
setTimeout(() => reTranslate(), 100);
});4. UI 切换语言(Locale 下拉)
import { switchLanguage, currentLang, LANGUAGE_OPTIONS } from './utils/translate';
// 模板里 v-for LANGUAGE_OPTIONS 渲染下拉
// 用户选中后调 changeLanguage(item.value)vue-i18n 项目切换语言必须按 3 步顺序(详见 QUESTION.md - vue-i18n 时序冲突):
const changeLanguage = async (lang: TranslateLangId) => {
if (lang === currentLang.value) return;
// 切英文用 skipRestore 保留本地文案;切中文需还原原文(源语言)
// onBeforeSwitchLanguage 在 createTranslationHub 里全局配置,SDK 自动调用
const skipRestore = lang === 'english';
await switchLanguage(lang, skipRestore ? { skipRestore: true } : undefined);
router.replace({ query: { ...route.query, lang } });
};5. 构建时静态数据打包(vite 插件)
// vite.config.ts
import { defineConfig, loadEnv } from 'vite';
import { vitePluginTranslateExport } from 'translation-hub-sdk/vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
vitePluginTranslateExport({
endpoint: env.VITE_TRANSLATE_HUB_ENDPOINT || 'http://localhost:3000/api/v1',
projectKey: env.VITE_TRANSLATE_HUB_PROJECT_KEY || '',
// 可选:
// langs: ['english', 'japanese'], // 默认 6 种非中文语言
// outDir: 'dist/translations',
// silent: true, // 配置缺失时静默跳过(默认 false 输出 console.log 警告)
}),
],
};
});打包内容优先级:glossary > approved(不包含 draft,未审核的 AI 翻译不打包)。
6. 登录/登出:保留语言 key
清空 localStorage 时保留语言持久化 key:
const PRESERVE_KEYS = [
import.meta.env.VITE_TRANSLATE_LANG_KEY || 'locale-lang',
];
export function clear() {
const preserved = PRESERVE_KEYS.map(k => [k, localStorage.getItem(k)]);
localStorage.clear();
sessionStorage.clear();
clearAllCookies();
preserved.forEach(([k, v]) => v !== null && localStorage.setItem(k, v));
}PRESERVE_KEYS 必须与 SDK langStorageKey 一致。
langStorageFormat说明:默认'translate'存TranslateLangId(chinese_simplified/english/ ...)。如需与后端业务代码对齐(zh-CN/en),设langStorageFormat: 'local',SDK 写入时自动转换。读取永远兼容两种格式,旧用户的语言选择不会丢失。
框架与构建工具支持
SDK 是纯 TypeScript 库,零运行时依赖,不绑定任何框架。核心模块只用浏览器原生 API(MutationObserver / TreeWalker / localStorage / fetch),所有现代浏览器都支持。
| 框架 | 接入要点 |
|---|---|
| Vue 2 / 3 | onLanguageChange 同步 shallowRef |
| React / Next.js | onLanguageChange 同步 useState,init() 在 useEffect 里调 |
| Svelte / SvelteKit | onLanguageChange 同步 store |
| Angular | onLanguageChange 同步 service 里的 signal / BehaviorSubject |
| SolidJS | onLanguageChange 同步 createSignal |
| Vanilla JS | 不传 onLanguageChange 即可 |
| 构建工具 | 静态数据打包 |
|---|---|
| Vite | 用专用插件 vitePluginTranslateExport(translation-hub-sdk/vite 子路径导入) |
| Webpack / Rspack / esbuild | 自己写 5 行脚本调 /runtime/export 接口拉翻译数据,输出到 dist/translations/{lang}.json |
| Next.js / Nuxt | 用 getStaticProps / loader 调 /runtime/export,或构建脚本同上 |
SSR 注意:TranslationHub 在 typeof document === 'undefined' 时自动跳过 DOM 扫描;init() 必须在客户端调(useEffect / onMounted),不能在服务端调。
环境变量约定
SDK 不强制读环境变量,但建议业务项目用以下命名:
| 变量 | 示例 | 说明 |
|------|------|------|
| VITE_TRANSLATE_HOST | http://localhost:8989 | MTranServer 地址(场景 1) |
| VITE_TRANSLATE_HUB_ENDPOINT | http://localhost:3000/api/v1 | Translation Hub server 地址(场景 2) |
| VITE_TRANSLATE_HUB_PROJECT_KEY | xxx-xxx-xxx | 托管服务注册后获得的 projectKey |
| VITE_TRANSLATE_LANG_KEY | locale-lang | 持久化语言的 localStorage key |
| VITE_TRANSLATE_SERVICE | translate.service | client.edge | hub.server | 翻译引擎选择 |
| VITE_DEFAULT_LANG | chinese_simplified | 默认语言 |
测试
pnpm --filter translation-hub-sdk test233 个单测覆盖语言检测、术语表(含 id-based 跨 batch 占位符)、Pivot、缓存、Provider 适配、TranslationClient、混合文本切分、TranslationHub 集成(含 langStorageFormat 双向兼容)、静态数据懒加载、Vite 构建插件。
文档
- API.md:所有类型、函数、类的完整签名和说明
- QUESTION.md:实战坑、原理细节、不常用场景(ECharts / vue-i18n 时序 / 扩展新语言 / 缓存策略 / 服务端译文同步机制等)
新增 API 时同时更新 README 接入示例 + API.md 详细签名;遇到实战坑或原理性问题追加到 QUESTION.md。
Translation Hub server
service: 'hub.server' 模式依赖 Translation Hub server——一个翻译管理后端,提供项目注册、术语表管理、译文审核、缓存、多翻译引擎选路等能力,配套 admin 后台做人工修订。本 SDK 是它的客户端,服务端代码还在开发中,稍后开源,敬请期待。
License
MIT
