Gutenberg i18n Filters 详解:用 wp.hooks 过滤器覆写编辑器翻译文本
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
Gutenberg(WordPress 块编辑器)的前端国际化(i18n)基于@wordpress/i18n包实现,其中__()、_x()、_n()、_nx()四个翻译函数在返回结果前会触发一组可编程的过滤器(filters),允许开发者在运行时覆写任意翻译字符串。本文以官方文档 i18n Filters 为主线,结合 @wordpress/i18n 包源码,讲解这组过滤器的完整签名、参数含义、text domain 专属过滤器用法,以及其底层调用链与测试验证,帮助你精准定制编辑器界面文案。
一、i18n 过滤器是什么
@wordpress/i18n提供的__()、_x()、_n()、_nx()四个函数负责在前端完成字符串翻译(与 PHP 侧的__()、_x()、_n()、_nx()一一对应)。它们基于 Tannin,即全局wp.hooks)做二次加工。
也就是说:这四个函数的返回值不是一成不变的,任何插件或主题都可以通过注册过滤器覆写某条翻译,从而在不动源码、不改语言包的前提下定制界面文案。可用的过滤器共四个:
i18n.gettexti18n.gettext_with_contexti18n.ngettexti18n.ngettext_with_context
在 create-i18n.ts 中,这些钩子的命名被一段正则约束,任何以i18n.(n?gettext|has_translation)开头的钩子都会被视为 i18n 过滤器参与联动:
const I18N_HOOK_REGEXP = /^i18n\.(n?gettext|has_translation)(_|$)/;二、四个过滤器的回调签名
过滤器回调接收的参数与其 PHP 侧对应函数一致。注意第一个参数是已经查表得到的翻译结果(translation),后续参数才是原文与上下文信息;回调必须返回一个字符串(通常原样返回translation)。
2.1 i18n.gettext
对应__(),无上下文、无复数形式:
function i18nGettextCallback( translation, text, domain ) { return translation; }| 参数 | 说明 |
|---|---|
translation | 已查得的译文(可能仍是原文) |
text | 待翻译的原始字符串 |
domain | 文本域(text domain),未指定时为undefined |
2.2 i18n.gettext_with_context
对应_x(),带 gettext 上下文消歧:
function i18nGettextWithContextCallback( translation, text, context, domain ) { return translation; }| 参数 | 说明 |
|---|---|
translation | 已查得的译文 |
text | 待翻译的原始字符串 |
context | 翻译上下文,用于区分同一原文在不同场景的不同译法 |
domain | 文本域 |
2.3 i18n.ngettext
对应_n(),按数字选择单复数:
function i18nNgettextCallback( translation, single, plural, number, domain ) { return translation; }| 参数 | 说明 |
|---|---|
translation | 已查得的单数或复数译文 |
single | 单数形式的原文 |
plural | 复数形式的原文 |
number | 用于判定单复数的数字 |
domain | 文本域 |
2.4 i18n.ngettext_with_context
对应_nx(),同时带上下文与复数:
function i18nNgettextWithContextCallback( translation, single, plural, number, context, domain ) { return translation; }| 参数 | 说明 |
|---|---|
translation | 已查得的译文 |
single | 单数形式的原文 |
plural | 复数形式的原文 |
number | 用于判定单复数的数字 |
context | 翻译上下文 |
domain | 文本域 |
三、源码视角:过滤器如何接入翻译流程
默认的 i18n 单例在 default-i18n.ts 中创建,它把全局的defaultHooks注入 i18n 实例:
const i18n = createI18n( undefined, undefined, defaultHooks );因此默认实例的四个翻译函数一定会经过过滤器管线。以__()为例,create-i18n.ts 中的实现是这样的:
const __ = ( text, domain ) => { let translation = dcnpgettext( domain, undefined, text ); if ( ! hooks ) { return translation; } translation = hooks.applyFilters( 'i18n.gettext', translation, text, domain ); return hooks.applyFilters( 'i18n.gettext_' + getFilterDomain( domain ), translation, text, domain ); };从中可以读出三条关键设计:
- 先查表、后过滤:
dcnpgettext(Tannin 的核心查表函数)先返回翻译结果,过滤器只在结果之上做覆写或修饰。 - 两级过滤器串联:先触发通用过滤器(如
i18n.gettext),再触发 domain 专属过滤器(如i18n.gettext_woocommerce),后者的translation入参是前者处理后的结果,形成管道式串联。 - domain 缺失时的回退规则:
getFilterDomain定义为( domain ) => domain || 'default'(见 create-i18n.ts),所以当调用方没有传入 domain(例如翻译 WordPress 核心字符串)时,专属过滤器会退化为i18n.gettext_default等形式。
_x()、_n()、_nx()的实现结构与__()完全同构,只是参数更多:_x()额外透传context,_n()透传single、plural、number,_nx()则同时透传number与context(见 create-i18n.ts)。
四、基本用法:覆写单条翻译
在插件或主题的 JS 入口中,使用wp.hooks.addFilter注册回调即可。以下示例把块编辑器中"Create Reusable block"这条字符串替换为组织内部的惯用语:
// Define our filter callback. function myPluginGettextFilter( translation, text, domain ) { if ( text === 'Create Reusable block' ) { return 'Save to MyOrg block library'; } return translation; } // Adding the filter wp.hooks.addFilter( 'i18n.gettext', 'my-plugin/override-add-to-reusable-blocks-label', myPluginGettextFilter );addFilter的第二个参数是唯一的命名空间标识(namespace),用于区分不同的注册者,也是后续removeFilter、hasFilter的依据。钩子系统本身由 _Hooks 类 提供,applyFilters会按优先级依次调用该过滤器上的所有回调并传递返回值。
五、text domain 专属过滤器(推荐做法)
从性能角度出发,优先使用带 text domain 的过滤器:这样你的回调只会针对该 domain 下的字符串执行,而不会在每一次__()调用时都被调用。
用法是在标准过滤器名后追加下划线和 domain 名。例如要过滤 domain 为woocommerce的字符串,可用的过滤器是:
i18n.gettext_woocommercei18n.gettext_with_context_woocommercei18n.ngettext_woocommercei18n.ngettext_with_context_woocommerce
示例:
// Define our filter callback. function myPluginGettextFilter( translation, text, domain ) { if ( text === 'You’ve fulfilled all your orders' ) { return 'All packed up and ready to go. Good job!'; } return translation; } // Adding the filter wp.hooks.addFilter( 'i18n.gettext_woocommerce', 'my-plugin/override-fulfilled-all-orders-text', myPluginGettextFilter );关于defaultdomain 的约定:如果要过滤的字符串其 domain 为undefined(例如 WordPress 核心字符串),则在构造过滤器名时使用"default":
i18n.gettext_defaulti18n.gettext_with_context_defaulti18n.ngettext_defaulti18n.ngettext_with_context_default
这一点与源码中getFilterDomain的回退逻辑完全吻合:undefineddomain 会映射为字符串default,从而命中上述过滤器。
六、过滤器调用顺序:通用优先、domain 其次
单元测试 create-i18n.ts(test) 对这一行为做了精确断言。测试同时注册了通用过滤器与 domain 过滤器,并验证了结果拼接顺序:
test( '__() calls filters', () => { const hooks = createHooksWithI18nFilters(); const i18n = createI18n( undefined, undefined, hooks ); expect( i18n.__( 'hello' ) ).toEqual( 'hello/i18n.gettext/i18n.gettext_default' ); expect( i18n.__( 'hello', 'domain' ) ).toEqual( 'hello/i18n.gettext/i18n.gettext_domain' ); } );可见处理链是:查表结果 → 通用过滤器 → domain 专属过滤器。_x()、_n()、_nx()的测试(create-i18n.ts(test))也验证了同样的顺序,并覆盖了复数数量1与2两种分支。
由此可以推导出两条实用结论:
- 若你希望"兜底覆写",让所有 domain 的同类字符串都受影响,注册通用过滤器;
- 若你只针对某个 domain(或核心
default)覆写,注册 domain 专属过滤器,且它能覆盖通用过滤器的结果(因为它在链尾)。
七、进阶主题:hasTranslation、isRTL 与订阅机制
原文档聚焦于四个gettext过滤器,但 i18n 钩子体系还包括一个官方未在本文列出、却被正则明确支持的过滤器:i18n.has_translation(同样支持i18n.has_translation_default与 domain 专属变体)。
hasTranslation()用于判断某字符串在当前 locale 数据中是否已有译文,create-i18n.ts 中它同样经过"通用过滤器 + domain 专属过滤器"两级管线,回调签名为:
function hasTranslationFilterCallback( hasTranslation, single, context, domain ) { return hasTranslation; }测试中的用法示例:当single === 'Always'且无 context、domain 为default时强制返回true(见 create-i18n.ts(test))。
与之相关的还有两个实用 API:
isRTL():通过_x( 'ltr', 'text direction' )判断当前语言是否为从右到左(RTL),例如希伯来语he、阿拉伯语ar;英语、法语等 LTR 语言返回false(create-i18n.ts)。subscribe():订阅 locale 数据变化;当i18n.*过滤器被添加或移除时,实例会通过hookAdded/hookRemoved动作通知订阅者重新渲染(create-i18n.ts)。
八、多实例场景:createI18n 与自定义 hooks
如果你不想使用全局的wp.hooks,可以通过createI18n()创建带独立 locale 数据与 hooks 的 i18n 实例:
import { createHooks } from '@wordpress/hooks'; import { createI18n } from '@wordpress/i18n'; const hooks = createHooks(); const i18n = createI18n( localeData, 'my-domain', hooks ); // 每个实例维护自己的 locale 数据与过滤器管线单元测试 create-i18n.ts(test) 证明了多个实例互不干扰:分别setLocaleData后,同一'hello'在两个实例中返回不同译文。createI18n的完整 API(setLocaleData、addLocaleData、resetLocaleData、getLocaleData等)参见 i18n README 与 types.ts。
九、实战建议与注意事项
- 命名空间必须唯一:
addFilter的第二个参数(如my-plugin/override-xxx)是移除过滤器时removeFilter的依据,务必保持稳定且唯一。 - 尽量用 domain 专属过滤器:回调只在目标 domain 下触发,避免在每次翻译调用上产生无谓开销。
- 牢记
translation可能是原文:当语言包缺失时,dcnpgettext会回退返回原文,你的过滤器仍应正确处理这种情况(通常返回translation兜底)。 - 上下文与复数参数不可遗漏:
_x()/_nx()的context、_n()/_nx()的number都是精确匹配的关键,覆写逻辑应同时比对它们,避免误伤其他场景的同名文案。 - 配合服务端国际化:前端过滤器只影响已加载到浏览器的字符串;完整的国际化流程(含 POT 提取、语言包生成)可参考 internationalization 指南。
十、延伸阅读
- i18n Filters 官方文档(本文主体来源)
- @wordpress/i18n 包文档:安装与全部 API(
__、_x、_n、_nx、sprintf、setLocaleData、isRTL等) - create-i18n.ts 源码:过滤器管线的具体实现
- default-i18n.ts 源码:默认单例与全局
wp.hooks的绑定 - i18n 单元测试:过滤器顺序、domain 回退、复数分支的权威行为验证
- @wordpress/hooks 源码:
addFilter/applyFilters等钩子原语的实现 - Filters 参考指南目录:Gutenberg 其他 JavaScript 过滤器
【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考