Gutenberg i18n Filters 详解:用 wp.hooks 过滤器覆写编辑器翻译文本
2026/9/17 4:05:34 网站建设 项目流程

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.gettext
  • i18n.gettext_with_context
  • i18n.ngettext
  • i18n.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 ); };

从中可以读出三条关键设计:

  1. 先查表、后过滤dcnpgettext(Tannin 的核心查表函数)先返回翻译结果,过滤器只在结果之上做覆写或修饰。
  2. 两级过滤器串联:先触发通用过滤器(如i18n.gettext),再触发 domain 专属过滤器(如i18n.gettext_woocommerce),后者的translation入参是前者处理后的结果,形成管道式串联。
  3. domain 缺失时的回退规则getFilterDomain定义为( domain ) => domain || 'default'(见 create-i18n.ts),所以当调用方没有传入 domain(例如翻译 WordPress 核心字符串)时,专属过滤器会退化为i18n.gettext_default等形式。

_x()_n()_nx()的实现结构与__()完全同构,只是参数更多:_x()额外透传context_n()透传singlepluralnumber_nx()则同时透传numbercontext(见 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),用于区分不同的注册者,也是后续removeFilterhasFilter的依据。钩子系统本身由 _Hooks 类 提供,applyFilters会按优先级依次调用该过滤器上的所有回调并传递返回值。

五、text domain 专属过滤器(推荐做法)

从性能角度出发,优先使用带 text domain 的过滤器:这样你的回调只会针对该 domain 下的字符串执行,而不会在每一次__()调用时都被调用。

用法是在标准过滤器名后追加下划线和 domain 名。例如要过滤 domain 为woocommerce的字符串,可用的过滤器是:

  • i18n.gettext_woocommerce
  • i18n.gettext_with_context_woocommerce
  • i18n.ngettext_woocommerce
  • i18n.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_default
  • i18n.gettext_with_context_default
  • i18n.ngettext_default
  • i18n.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))也验证了同样的顺序,并覆盖了复数数量12两种分支。

由此可以推导出两条实用结论:

  • 若你希望"兜底覆写",让所有 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(setLocaleDataaddLocaleDataresetLocaleDatagetLocaleData等)参见 i18n README 与 types.ts。

九、实战建议与注意事项

  1. 命名空间必须唯一addFilter的第二个参数(如my-plugin/override-xxx)是移除过滤器时removeFilter的依据,务必保持稳定且唯一。
  2. 尽量用 domain 专属过滤器:回调只在目标 domain 下触发,避免在每次翻译调用上产生无谓开销。
  3. 牢记translation可能是原文:当语言包缺失时,dcnpgettext会回退返回原文,你的过滤器仍应正确处理这种情况(通常返回translation兜底)。
  4. 上下文与复数参数不可遗漏_x()/_nx()context_n()/_nx()number都是精确匹配的关键,覆写逻辑应同时比对它们,避免误伤其他场景的同名文案。
  5. 配合服务端国际化:前端过滤器只影响已加载到浏览器的字符串;完整的国际化流程(含 POT 提取、语言包生成)可参考 internationalization 指南。

十、延伸阅读

  • i18n Filters 官方文档(本文主体来源)
  • @wordpress/i18n 包文档:安装与全部 API(___x_n_nxsprintfsetLocaleDataisRTL等)
  • 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),仅供参考

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

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

立即咨询