搜索全站
插件 11 分钟阅读

WordPress 插件国际化教程:文本域、load_plugin_textdomain 与 po/mo 文件

从插件头部的 Text Domain 声明,到 __() 系列翻译函数,再到用 WP-CLI 提取 POT、编译成 mo 文件,本文完整讲清 WordPress 插件国际化链路,附可直接套用的代码与目录规范。

U
UFO_PF 2016-10-23 · 阅读 11 分钟
Wo 插件
阅读提示
本文约 4,175 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:你写的插件,中文站长装上去一句看不懂

很多开发者把插件提交到 WordPress.org 之后才发现:后台设置页、按钮文案、错误提示全是英文,中文站长装完一脸茫然,评论区第一条就是”有没有中文版”。更尴尬的是另一种情况——插件里塞了一份中文语言包,结果英文站点的用户也被强制显示中文,因为代码里直接写了中文字面量。

这两个问题分属两件事:国际化(i18n)是让代码”可被翻译”,本地化(l10n)是”真的翻译了并加载”。国内很多插件只做了后半段:把界面文案硬编码成中文,短期内中文用户爽了,但只要站点切到英文、或者要做外贸站多语言版本,就必须推倒重来。正确顺序是先 i18n 后 l10n,代价极低,收益却贯穿插件整个生命周期。

原理分析:WordPress 是怎么找到那句中文的

WordPress 的翻译机制本质是 GNU gettext 的一套封装,理解三个概念就够了:

  • 文本域(Text Domain):每条待翻译字符串归属的”命名空间”。它必须是插件目录名的 slug 形式(小写、连字符),且要和插件头部声明完全一致。文本域写错是最常见的”翻译文件明明在却不生效”的原因。
  • 语言包查找路径:从 WordPress 4.6 起引入了”即时加载(just-in-time translation)”,核心会根据插件头部的 Text Domain 和 Domain Path,自动去 wp-content/languages/plugins/{text-domain}-{locale}.mo 找文件。也就是说,显式调用 load_plugin_textdomain() 已经不是强制项,但为了兼容老版本和自定义目录,多数插件仍会保留。
  • locale 标识:get_locale() 返回的形如 zh_CN、en_US 的字符串,决定加载哪一个 .mo。后台”设置 → 常规 → 站点语言”改的就是它。

翻译函数不是一个,而是一族,选错就会带来转义或转义过度的问题:

场景 返回 直接输出
普通字符串 __() _e()
需要 HTML 转义后输出 esc_html__() esc_html_e()
放到 HTML 属性里 esc_attr__() esc_attr_e()
单复数 _n() —
带歧义上下文 _x() _ex()

规则很简单:只要结果会进 HTML,就用带 esc_ 的版本,别让翻译文件成为 XSS 入口。

保姆级实操

第一步:在插件头部声明文本域与路径

<?php
/**
 * Plugin Name: 迷茫飞订单导出
 * Plugin URI:  https://mimangfei.com
 * Description: 把 WooCommerce 订单导出为 CSV。
 * Version:     1.0.0
 * Author:      mimangfei
 * Text Domain: mimangfei-order-export
 * Domain Path: /languages
 * License:     GPL-2.0-or-later
 */

Domain Path 指插件自带语言包的相对目录,通常叫 /languages。

第二步:所有面向用户的字符串包一层翻译函数

<?php
// 后台菜单
add_action( 'admin_menu', function () {
    add_menu_page(
        __( '订单导出', 'mimangfei-order-export' ),
        __( '订单导出', 'mimangfei-order-export' ),
        'manage_options',
        'mfoe-export',
        'mfoe_render_page'
    );
} );

// 带占位符,务必用 %s / %d,不要在翻译里拼接变量
function mfoe_notice( $count ) {
    printf(
        /* translators: %d 是导出的订单数量 */
        esc_html__( '本次共导出 %d 条订单。', 'mimangfei-order-export' ),
        (int) $count
    );
}

// 单复数
printf(
    esc_html( _n( '%s 个商品', '%s 个商品', $total, 'mimangfei-order-export' ) ),
    number_format_i18n( $total )
);

注意 /* translators: ... */ 注释,它会被提取工具带进 POT,是给译者看的上下文说明。

第三步:兼容旧版本,显式加载文本域

<?php
add_action( 'plugins_loaded', function () {
    load_plugin_textdomain(
        'mimangfei-order-export',
        false,
        dirname( plugin_basename( __FILE__ ) ) . '/languages'
    );
} );

第四步:用 WP-CLI 提取 POT 模板

cd wp-content/plugins/mimangfei-order-export
wp i18n make-pot . languages/mimangfei-order-export.pot \
  --slug=mimangfei-order-export \
  --domain=mimangfei-order-export \
  --exclude=vendor,node_modules,tests

第五步:从 POT 生成中文 po,再编译成 mo

# 复制模板为简体中文语言包
cp languages/mimangfei-order-export.pot languages/mimangfei-order-export-zh_CN.po

# 翻译完成后编译(需要系统装了 gettext)
msgfmt -o languages/mimangfei-order-export-zh_CN.mo \
       languages/mimangfei-order-export-zh_CN.po

# WordPress 6.5+ 还可生成更快的 PHP 翻译文件
wp i18n make-php languages/

# 确认 locale 与文件名匹配
wp eval "echo get_locale();"

常见踩坑

  1. 文本域和插件目录名不一致。比如目录叫 mimangfei-order-export,文本域写成 mfoe,即时加载就找不到文件。WordPress.org 的插件检查也会报这个错。
  2. 把变量拼进翻译字符串。__( '共 ' . $n . ' 条' ) 在英语里语序一变就废。必须用 %s/%d 占位符。
  3. 忘了 esc_ 系列。翻译文件来自第三方时,__() 直接 echo 等于给译者开了 HTML 权限。
  4. 只放了 .po 没放 .mo。WordPress 只读 .mo(或 6.5+ 的 .l10n.php),.po 是源文件,放上去没用。
  5. 改了源码忘了重新提取。新增字符串后 POT 不更新,pot 里根本没有这条,译者无从下手。
  6. 案例:某插件作者把 Text Domain 写成 Mimangfei_Order_Export(带下划线大写),同时在代码里用小写连字符调用。本地测试时因为显式调用了 load_plugin_textdomain() 指向自己的 /languages 目录而正常显示中文;发布到官方目录后,官方语言包走的是即时加载路径 wp-content/languages/plugins/mimangfei_order_export-zh_CN.mo,大小写与 slug 规范化后对不上,结果线上所有中文站点全部回落英文。修复只需统一为 mimangfei-order-export,但已经积累的翻译文件命名也要一并改名。
  7. JS 里的字符串漏翻。块编辑器或前台脚本里的文案要用 wp_set_script_translations() 注册,否则界面一半中文一半英文。
<?php
wp_enqueue_script(
    'mfoe-block-editor',
    plugins_url( 'build/index.js', __FILE__ ),
    array( 'wp-i18n', 'wp-blocks' ),
    '1.0.0',
    true
);
wp_set_script_translations( 'mfoe-block-editor', 'mimangfei-order-export', plugin_dir_path( __FILE__ ) . 'languages' );

总结

判断一个插件国际化是否合格,只看三点:插件头部的 Text Domain 是否与目录 slug 完全一致;代码里是否所有用户可见字符串都走了翻译函数且带了正确的转义;languages 目录里是否有随源码同步更新的 .pot 以及编译好的 .mo。做到这三点,中文包可以由官方社区翻译,也可以自己用 Poedit 与 Loco Translate 汉化 维护,插件就具备了面向多语言站点的基础能力。

如果你只是想快速把别人的插件翻成中文,可以接着看 用 Loco Translate 汉化未翻译的插件;而插件要真正适配多语言站点,还需要配合 WordPress 多语言站点搭建方案 的整体规划。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。