- 前端
- CMS
【免费下载链接】wp-calypso
The JavaScript and API powered WordPress.com
本篇技术文章以client/lib/translator-jumpstart/模块及其 README 为核心,讲解 wp-calypso(WordPress.com 的纯 JS + REST API 管理前端)如何把外部 Community Translator 服务接入页面:从字符串采集、翻译器启动器的显示条件,到脚本注入、Jed 翻译表的热更新与调试手段。读完本文,你将能够完整理解该模块的调用链与关键状态机,并在仓库中定位每一处实现依据。
模块定位:它负责什么
client/lib/translator-jumpstart/是一个极小的模块,目录内只有两个文件:说明文档 README.md 和全部实现 index.jsx。README 明确了它的职责:
- 采集字符串:收集经 i18n mixin
i18n.translate()处理的字符串,并把数据挂到全局对象window.translatorJumpstart上,供外部 Community Translator 读取; - 按需显示启动器(launcher):需同时满足以下条件——
community-translator在 config 中启用;- 用户在自己的设置中勾选了Enable on page translation选项;
- 用户 UI 语言不是英语;
- 通过特性检查(较大的屏幕,且以“无触摸输入”近似判断“有鼠标右键”);
- 点击启动器后加载脚本:Community Translator 脚本通过
loadScript按需注入,而非首屏加载。
从源码结构看,README 中描述的“采集”机制在现行实现中具体落地为 index.jsx 末尾注册的翻译钩子:
// wrap translations from i18n i18n.registerTranslateHook( ( translation, options ) => { return communityTranslatorJumpstart.wrapTranslation( options.original, translation, options ); } );即每次i18n.translate()产出译文时都会经过wrapTranslation统一处理,这正是“把页面字符串喂给外部翻译器”的入口。
字符串采集:wrapTranslation 如何给译文打上“可翻译标记”
wrapTranslation( originalFromPage, displayedTranslationFromPage, optionsFromPage )的实现位于 index.jsx#L83-L130。其逻辑与边界条件值得逐条拆解:
- 前置短路:若
isEnabled()或isActivated()为假,或调用方传入了textOnly选项(表示该字符串不应参与页内编辑翻译),直接返回原译文; - 类型防御:非字符串的 original、非对象的 options 均被安全归一化(options 回退为
{}); - 打标记:构造一个
<data>React 元素,携带如下属性——className: 'translatable':Community Translator 在 DOM 中识别可翻译节点的依据;data-singular:英文原文(singular 形式);data-context:当options.context为字符串时写入,对应 gettext 的 context(/* translators: */);data-plural:当options.plural为字符串时写入,供复数翻译使用。
一个容易踩坑的细节:<data>元素是 React 返回的冻结对象,源码先Object.assign复制一份,再覆写toString使其返回译文(否则字符串拼接时会得到[object Object]),最后再次Object.freeze以模拟原始 ReactElement 的行为(见 index.jsx#L117-L129)。
激活状态下(_shouldWrapTranslations === true),页面上所有带data-singular等属性的节点即构成 Community Translator 的操作目标集;这也解释了为什么“启用/停用”需要重渲染(后文toggle()中的i18n.reRenderTranslations())。
启动器显示条件:isEnabled 与 isValidBrowser 的逐层判断
README 列出的四个显示条件,在 isEnabled() 中按顺序实现为五个短路检查:
| README 条件 | 源码实现 | 说明 |
|---|---|---|
| UI 语言不是英语 | canBeTranslated( currentUser.localeSlug ) | 若 locale 为空或不可翻译则禁用;canBeTranslated来自@automattic/i18n-utils(utils.ts#L67) |
| (隐含)区域变体可用 | canBeTranslated( currentUser.localeVariant ) | 对“没有官方 GP 翻译集”的 locale 变体(如某些地区变体)禁用 |
| 用户设置已加载 | _isUserSettingsReady | 在 userSettings 就绪前一律返回 false,避免误判 |
| 用户开启了Enable on page translation | _isTranslatorEnabled | 由trackTranslatorStatus()写入(见“埋点与状态”一节) |
| 大屏幕 + 非触摸设备 | isValidBrowser() | 见下文 |
isValidBrowser()(index.jsx#L292-L298)当前实现非常直接:
isValidBrowser() { if ( isMobile() ) { return false; } return true; },其中isMobile来自@automattic/viewport(对应 packages/viewport)。源码注释(index.jsx#L71)解释了动机:“在 touch 交互方案完善之前,暂时对移动设备禁用翻译器”,这与 README 中“大屏幕上、无触摸 ≈ 有右键”的特性检查描述是一致的——Community Translator 的交互(右键菜单/悬浮编辑)依赖鼠标能力。
页面翻译数据:window.translatorJumpstart 的结构
外部 Community Translator 脚本运行前,会把自身需要的“页面翻译上下文”读取自window.translatorJumpstart。这个对象由模块内的translationDataFromPage常量初始化(index.jsx#L17-L27),并在toggle()中一次性赋给 window(index.jsx#L237):
const translationDataFromPage = { localeCode: 'en', languageName: 'English', pluralForms: 'nplurals=2; plural=(n != 1)', contentChangedCallback() {}, glotPress: { url: 'https://translate.wordpress.com', project: 'wpcom', translation_set_slug: 'default', }, };各字段的含义与更新时机:
localeCode/languageName/pluralForms:当前 UI 语言信息,由updateTranslationData()在 locale 文件加载后更新;contentChangedCallback:组件内容变化时的回调,初始为空函数,翻译器加载后会被替换(见“脚本加载与激活”);glotPress:告知 Community Translator 与哪个 GlotPress 实例和项目通信——这正是 README “Environment specific functionality”一节的核心。
updateTranslationData()(index.jsx#L174-L202)的更新规则:
- 若
localeCode未变化则直接跳过(避免重复处理); pluralForms依次尝试plural_forms、Plural-Forms、plural-forms三种键名,兜底沿用默认值;languageName从@automattic/languages(packages/languages)中按langSlug查找,并用正则/^(?:[a-z]{2,3}|[a-z]{2}-[a-z]{2})\s+-\s+/去掉语言条目名里的语言代码前缀;glotPress.translation_set_slug根据 locale 变体查translateSetSlugs映射表,缺省为'default'。
环境相关行为:GlotPress 实例、项目与翻译集
README 指出:启动器决定了 Community Translator 与哪个 GlotPress 实例、哪个项目通信,默认是 WordPress.com 的翻译项目(projects/wpcom);在非生产环境中,使用的项目为test而非wpcom。
结合源码可以看到translationDataFromPage.glotPress的静态默认值即为该实例与wpcom项目(index.jsx#L22-L26),而翻译集 slug 通过translateSetSlugs(index.jsx#L14-L16)做了一层“locale 变体 → GP 翻译集”的映射:
// lookup for the translation set slug on GP const translateSetSlugs = { de_formal: 'formal', };也就是说德语正式变体(de_formal)会被路由到 GP 上的formal翻译集,其余语言走default。非生产环境的test项目选择,从源码结构看属于部署环境注入/配置的范畴(当前仓库中该对象以wpcom为代码内默认值),实际以运行环境的注入结果为准。
脚本注入与激活流程:toggle() 的状态机
toggle()(index.jsx#L209-L269)是启动器点击后的核心入口,可分为三条路径:
路径一:脚本尚未加载。先把translationDataFromPage赋给window.translatorJumpstart,再用loadjQueryDependentScript( injectUrl, callback )注入外部脚本(该函数来自@automattic/load-script,对应 packages/load-script)。注入 URL 由setInjectionURL()拼成:
const communityTranslatorBaseUrl = 'https://widgets.wp.com/community-translator/'; const communityTranslatorVersion = '1.160729'; // ... injectUrl = communityTranslatorBaseUrl + jsFile + '?v=' + communityTranslatorVersion;即从 widgets 域拉取community-translator.min.js并附带版本参数(index.jsx#L204-L207)。加载成功后执行:
window.communityTranslator.registerTranslatedCallback( communityTranslatorJumpstart.updateTranslation ); activate();路径二:激活(activate)。
function activate() { _shouldWrapTranslations = true; i18n.reRenderTranslations(); window.communityTranslator.load(); debug( 'Translator activated' ); return true; }先置位_shouldWrapTranslations(isActivated()返回它),再重渲染所有译文——这一步让全页节点都带上data-singular等标记,最后调用翻译器load()。
路径三:停用(deactivate)。对称地调用window.communityTranslator.unload()、清掉_shouldWrapTranslations并reRenderTranslations(),把数据标记从 DOM 中全部移除。
另有一个防御分支:若在init之前就触发了 toggle(injectUrl尚未生成),只记录 debug 日志并返回 false。
翻译结果回写:updateTranslation 与 Jed 格式
用户在 Community Translator 中提交翻译后,翻译器会回调updateTranslation( newTranslation )(index.jsx#L272-L290):
updateTranslation( newTranslation ) { const locale = i18n.getLocale(); const key = newTranslation.key; const plural = newTranslation.plural; const translations = newTranslation.translations; // jed expects: // 'context\004singular': [plural, translatedSingular, translatedPlural...] locale[ key ] = [ plural ].concat( translations ); i18n.setLocale( locale ); },关键点在于它直接改写 i18n 底层 locale 对象并调i18n.setLocale():key 采用 Jed 约定的context\004singular形式,数组首元素是 plural 规则,其后是单数/复数译文。也就是说页内翻译不是 DOM 文本替换,而是把用户译文合并进 Jed 语言表,后续所有渲染都自然使用新译文。
与之配合的还有一个组件更新钩子(index.jsx#L306-L312):
// callback when translated component changes. // the callback is overwritten by the translator on load/unload, so we're returning it within an anonymous function. i18n.registerComponentUpdateHook( () => { if ( typeof translationDataFromPage.contentChangedCallback === 'function' ) { return translationDataFromPage.contentChangedCallback(); } } );translationDataFromPage.contentChangedCallback就是translationDataFromPage里的空函数槽:Community Translator 在 load/unload 时覆写它,用于在页面内容变化时通知翻译器重新扫描。注意toggle()开头还会重新安装一个“未注册时告警”版本(index.jsx#L210-L217),若翻译器已激活但回调未被注册,会输出一条一次性 debug 警告。
初始化时机与语言切换的重新初始化
init( user, isUserSettingsReady )(index.jsx#L132-L172)负责建立用户数据并串联整个启动过程,顺序为:
- 从
i18n.getLocale()取当前语言 JSON,解析出localeSlug(即 localeCode)与localeVariant; - 保存
_user,无用户数据则 debug 记录“initialization failed because user data is not ready”并返回; - 有语言数据时调用
updateTranslationData(),否则记录“trying to initialize translator without loaded language”; - 幂等保护:
initialized已为 true 时直接返回; - 更新
_isUserSettingsReady,未就绪则中止; isEnabled()为假时记录“not initializing, not enabled”并返回;全部通过后才置initialized = true。
模块最后还有一行订阅(index.jsx#L329-L330):
// re-initialize when new locale data is loaded i18n.subscribe( communityTranslatorJumpstart.init.bind( communityTranslatorJumpstart ) );这保证了用户切换 UI 语言后(locale 数据重新加载),整个模块会自动重新走一遍初始化——语言名、复数规则、翻译集 slug 全部随新 locale 刷新。
埋点与状态:trackTranslatorStatus
trackTranslatorStatus( isTranslatorEnabled )(index.jsx#L314-L327)是 README 中“用户在自己的设置里开启Enable on page translation”这一条件的写入方:
export function trackTranslatorStatus( isTranslatorEnabled ) { const newSetting = isTranslatorEnabled; const changed = _isTranslatorEnabled !== newSetting; const tracksEvent = newSetting ? 'calypso_community_translator_enabled' : 'calypso_community_translator_disabled'; if ( changed && _isTranslatorEnabled !== undefined ) { debug( tracksEvent ); recordTracksEvent( tracksEvent, { locale: i18n.getLocaleSlug() } ); } _isTranslatorEnabled = newSetting; }它做两件事:更新模块内部_isTranslatorEnabled状态(isEnabled()依赖它),并在状态发生变化时通过recordTracksEvent(来自calypso/lib/analytics/tracks)上报calypso_community_translator_enabled/calypso_community_translator_disabled事件,事件属性携带当前 locale slug。首次调用(_isTranslatorEnabled === undefined)只记录状态不上报,避免初始化噪声。
调试指南:用 debug 库验证整条链路
README 给出的调试方法可直接在浏览器控制台使用(该模块使用debug库,命名空间为calypso:community-translator,见 index.jsx#L9):
- 只看翻译器本身:
localStorage.setItem( 'debug', 'calypso:community-translator' ) - 同时包含 i18n 显示译文的信息:
localStorage.setItem( 'debug', 'calypso:i18n,calypso:community-translator' )
结合源码中的 debug 日志点,可以精确验证每个阶段:Successfully initialized(init 通过)、loading community translator/Script loaded!(脚本注入)、Translator activated/Translator deactivated(toggle)、skipping updating translation data with same localeCode(语言未变化时的短路)等。
小结:一次“开启页内翻译”的完整链路
把上述各部分串起来,用户在 wp.com 客户端开启页内翻译后的完整链路是:
- 用户在设置页切换开关 →
trackTranslatorStatus(true)置位_isTranslatorEnabled并上报埋点; i18n.subscribe触发init()→updateTranslationData()写入 locale、复数规则、window.translatorJumpstart所需的 GlotPress 配置;- 启动器出现的前提(语言可翻译、设置就绪、非移动端)由
isEnabled()/isValidBrowser()把关; - 点击启动器 →
toggle()注入community-translator.min.js(widgets 域、版本参数固定为1.160729); - 脚本就绪后
registerTranslatedCallback(updateTranslation)+activate():_shouldWrapTranslations = true→reRenderTranslations()让全页译文节点带上data-singular/data-context/data-plural标记; - 用户提交翻译 →
updateTranslation()把译文按 Jed 格式合并进 locale →i18n.setLocale()生效,无需刷新页面。
如需继续深入,可依次查看 translator-jumpstart 模块说明、模块实现、canBeTranslated 语言判定 以及 load-script 脚本加载包。需要留意的前提限制:该功能默认仅面向桌面端(移动端被isValidBrowser()排除)、仅在语言可翻译且用户显式开启时激活,且外部脚本来自 widgets 域,属于 wp.com 线上环境的服务。
- 前端
- CMS
【免费下载链接】wp-calypso
The JavaScript and API powered WordPress.com
相关推荐
如何用kiss-translator轻松实现网页双语翻译:新手完整指南
如何用kiss translator轻松实现网页双语翻译:新手完整指南 还在为看不懂外文网页而烦恼吗?kiss translator这款开源双语翻译插件,能让你
前端如何快速实现屏幕翻译?Screen Translator 完整使用指南
如何快速实现屏幕翻译?Screen Translator 完整使用指南 Screen Translator 是一款强大的开源屏幕翻译工具,集成了屏幕捕捉、OCR
桌面应用OCRScreen Translator完整教程:三步实现屏幕文字智能翻译
还在为阅读外文内容而烦恼吗?Screen Translator作为一款强大的屏幕捕获、OCR识别和智能翻译工具,能够将屏幕上的任何文字即时翻译成你需要的语言,彻
桌面应用OCR
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考