EP129. “插件国际化:文本域、翻译函数与 Loco Translate”
🔒 登录后可标记已读学习怎么让插件方便被翻译成别的语言。分两步:① 在代码层面「挖空」写死的英文字符串——给插件头信息加 Text Domain/Domain Path,用 __()/esc_html__() 等翻译函数包住需要翻译的文字,并在 init 钩子里加载对应语言的翻译文件;② 用免费插件 Loco Translate 扫描代码自动生成翻译模板(.pot 文件),再基于模板为具体语言(这里演示西班牙语)逐条填写翻译,生成 .po/.mo 文件。最后提醒一个安全细节:翻译内容本质上也是「外部输入」,输出到页面时同样需要转义,防止翻译文件被篡改后注入恶意 HTML/JS。
涉及文件
wp-content/plugins/our-first-unique-plugin/our-first-unique-plugin.php(修改)wp-content/plugins/our-first-unique-plugin/languages/(新建目录,存放翻译文件;.pot/.po/.mo由 Loco Translate 插件生成,不是手写代码)
代码实现
插件头信息新增 Text Domain / Domain Path:
/*
Plugin Name: Our Test Plugin
Description: A truly amazing plugin.
Version: 1.0
Author: Brad
Author URI: https://www.udemy.com/user/bradschiff/
Text Domain: wcpdomain
Domain Path: /languages
*/
__construct() 新增加载语言文件的钩子:
add_action('init', array($this, 'languages'));
languages():加载对应语言的翻译文件:
function languages() {
load_plugin_textdomain('wcpdomain', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
用翻译函数「挖空」两处示例文字(其余文字这一讲未处理,只做两个示范):
// 后台 Settings 菜单里的链接文字
function adminPage() {
add_options_page('Word Count Settings', __('Word Count', 'wcpdomain'), 'manage_options', 'word-count-settings-page', array($this, 'ourHTML'));
}
// 正文统计信息里 "This post has ... words." 这句
if (get_option('wcp_wordcount', '1')) {
$html .= esc_html__('This post has', 'wcpdomain') . ' ' . $wordCount . ' ' . __('words', 'wcpdomain') . '.<br>';
}
关键改动点:
Text Domain是这个插件所有可翻译字符串的统一标识符(自己取名,只要唯一),Domain Path指向存放翻译文件的目录——两者都是标准的插件头信息属性,Loco Translate 等翻译工具靠这两个值定位该扫描/加载哪些文件load_plugin_textdomain($domain, $deprecated, $path):第二个参数官方文档标注为已弃用,固定传false;第三个参数用dirname(plugin_basename(__FILE__)) . '/languages'拼出「相对于插件目录的语言文件夹路径」——plugin_basename(__FILE__)拿到当前文件相对wp-content/plugins/的路径,dirname()再去掉文件名只留目录部分__($text, $domain)是最基础的翻译函数:如果当前系统语言有对应的翻译,返回翻译后的文字;没有的话原样返回英文原文(所以即使还没做任何翻译,代码本身也完全正常工作,不会报错或显示空白)esc_html__($text, $domain)是esc_html(__($text, $domain))的简写组合函数——同时做翻译和 HTML 转义,专门用在「翻译结果会直接输出到页面」的场景,比分开写两个函数调用更简洁- 「words」这个单词被单独抠出来翻译(而不是整句话一起翻译成一个字符串),是因为句子中间还嵌着动态的数字(
$wordCount),只能把「不变的文字片段」分别处理——转录里特别提到要把原本硬编码在字符串里的空格挪到拼接的地方,这样翻译人员翻译「words」这一个词时不需要自己再操心前后要不要带空格 - 这一讲只做了两处「挖空」作为示范(后台菜单链接文字、正文统计句的一部分),插件里其他硬编码的英文字符串(比如字符统计、阅读时间那两句)没有做国际化处理,只是教学演示用,不是插件已经全部完成翻译准备
Loco Translate 操作流程(后台插件,非代码):
- 安装启用 Loco Translate 插件(作者推荐它优于 PoEdit,理由是对 WordPress 场景优化更到位,且后续章节还能用它处理 JavaScript 里的翻译字符串)
- 后台 Loco Translate → Plugins → 找到目标插件 → Create Template(自动扫描代码里所有
__()/esc_html__()等函数调用,生成.pot模板文件到languages/目录) - New Language → 选择目标语言(示例选西班牙语)→ 保存位置选 Author(存进插件自己的
languages/目录)→ Start Translating - 对模板里列出的每条原文,在下方翻译框填入对应语言的译文 → Save,生成
.po(可读文本)和.mo(编译后供程序读取)两个文件 - 后台 Settings → General → Site Language 切换成对应语言,即可看到已翻译的部分自动生效,没翻译的部分依然显示英文原文
[截图:wp-admin Loco Translate 插件的翻译编辑界面,左侧原文列表、右侧填写西班牙语译文]
[截图:切换 Site Language 为西班牙语后,前台已翻译部分(如 Settings 菜单里的 Word Count 链接文字)实际显示效果]
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
Text Domain / Domain Path | 插件头信息属性 | 标识可翻译字符串所属的域名和翻译文件存放路径 |
init | WP hook | WordPress 初始化时触发,是加载文本域的标准挂载点 |
load_plugin_textdomain($domain, $deprecated, $path) | WP 内建 function | 加载指定文本域对应当前系统语言的翻译文件 |
plugin_basename(__FILE__) | WP 内建 function | 获取当前文件相对于 wp-content/plugins/ 的路径 |
__($text, $domain) | WP 内建 function | 翻译函数,没有对应翻译时原样返回原文 |
esc_html__($text, $domain) | WP 内建 function | __() + esc_html() 的组合简写,翻译并转义 |
常见坑
- 把包含动态数字/变量的整句话当成一个整体去翻译——不同语言的语序、单复数规则不同,硬把整句锁死会让翻译没法适配语言差异,应该只抠出真正需要翻译的固定文字片段
- 翻译结果直接输出、不转义——翻译文件本质上也是一种「外部输入」(哪怕看似只有开发者/受信任的翻译人员会编辑),转录演示了往翻译文本里塞入
<strong>标签就能让页面把它当成真实 HTML 渲染,如果换成<script>就是真实的 XSS 风险,所以只要是会直接输出到页面的翻译文字,就该用esc_html__()而不是普通的__() - 忘记在
Text Domain属性和load_plugin_textdomain()/__()各处调用里保持完全一致的字符串——任何一处拼错都会导致翻译系统找不到对应的文本域,所有翻译都不生效
延伸 / 后续讲座会用到
第一个插件(字数统计)彻底完工。下一讲开始做本章最后一个 PHP 插件——敏感词过滤器,会用到跟这次不一样的做法:完全手写后台设置页(不用 Settings API 自动生成表单),并且第一次学怎么给后台菜单项配自定义 SVG 图标、建独立的顶级菜单。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 23, EP129