☰
wp-calypso 客户端 translator-jumpstart:页内社区翻译(Community Translator)集成的完整实现解析
2026/9/25 3:56:07 网站建设 项目流程
  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载

本篇技术文章以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 mixini18n.translate()处理的字符串,并把数据挂到全局对象window.translatorJumpstart上,供外部 Community Translator 读取;
  • 按需显示启动器(launcher):需同时满足以下条件——
    1. community-translator在 config 中启用;
    2. 用户在自己的设置中勾选了Enable on page translation选项;
    3. 用户 UI 语言不是英语;
    4. 通过特性检查(较大的屏幕,且以“无触摸输入”近似判断“有鼠标右键”);
  • 点击启动器后加载脚本: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。其逻辑与边界条件值得逐条拆解:

  1. 前置短路:若isEnabled()或isActivated()为假,或调用方传入了textOnly选项(表示该字符串不应参与页内编辑翻译),直接返回原译文;
  2. 类型防御:非字符串的 original、非对象的 options 均被安全归一化(options 回退为{});
  3. 打标记:构造一个<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)的更新规则:

  1. 若localeCode未变化则直接跳过(避免重复处理);
  2. pluralForms依次尝试plural_forms、Plural-Forms、plural-forms三种键名,兜底沿用默认值;
  3. languageName从@automattic/languages(packages/languages)中按langSlug查找,并用正则/^(?:[a-z]{2,3}|[a-z]{2}-[a-z]{2})\s+-\s+/去掉语言条目名里的语言代码前缀;
  4. 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)负责建立用户数据并串联整个启动过程,顺序为:

  1. 从i18n.getLocale()取当前语言 JSON,解析出localeSlug(即 localeCode)与localeVariant;
  2. 保存_user,无用户数据则 debug 记录“initialization failed because user data is not ready”并返回;
  3. 有语言数据时调用updateTranslationData(),否则记录“trying to initialize translator without loaded language”;
  4. 幂等保护:initialized已为 true 时直接返回;
  5. 更新_isUserSettingsReady,未就绪则中止;
  6. 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 客户端开启页内翻译后的完整链路是:

  1. 用户在设置页切换开关 →trackTranslatorStatus(true)置位_isTranslatorEnabled并上报埋点;
  2. i18n.subscribe触发init()→updateTranslationData()写入 locale、复数规则、window.translatorJumpstart所需的 GlotPress 配置;
  3. 启动器出现的前提(语言可翻译、设置就绪、非移动端)由isEnabled()/isValidBrowser()把关;
  4. 点击启动器 →toggle()注入community-translator.min.js(widgets 域、版本参数固定为1.160729);
  5. 脚本就绪后registerTranslatedCallback(updateTranslation)+activate():_shouldWrapTranslations = true→reRenderTranslations()让全页译文节点带上data-singular/data-context/data-plural标记;
  6. 用户提交翻译 →updateTranslation()把译文按 Jed 格式合并进 locale →i18n.setLocale()生效,无需刷新页面。

如需继续深入,可依次查看 translator-jumpstart 模块说明、模块实现、canBeTranslated 语言判定 以及 load-script 脚本加载包。需要留意的前提限制:该功能默认仅面向桌面端(移动端被isValidBrowser()排除)、仅在语言可翻译且用户显式开启时激活,且外部脚本来自 widgets 域,属于 wp.com 线上环境的服务。

  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载
上一篇:3分钟彻底搞懂SMAPI:星露谷物语模组加载神器
下一篇:OfficeCLI终极指南:无需安装Office的免费自动化办公神器

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询