WordPress 插件国际化:load_plugin_textdomain()
函数源码剖析(讲座模式)
各位听众,欢迎来到今天的“WordPress 插件国际化:load_plugin_textdomain()
函数源码剖析”讲座。我是今天的讲师,代号“码农李”,很高兴能和大家一起探索 WordPress 插件国际化的奥秘。今天,我们重点聚焦 load_plugin_textdomain()
这个神奇的函数,看看它如何让你的插件说“世界语”,支持各种语言,走向国际。
在开始之前,先给大家讲个笑话:一个程序员去面试,面试官问:“你擅长什么?”程序员自信地说:“我擅长复制粘贴!”面试官:“那好,请你把简历复制一遍。” 程序员:“……” 这告诉我们,光会复制粘贴是不行的,要理解原理,才能真正掌握技术。今天,我们就一起深入 load_plugin_textdomain()
的源码,看看它背后的原理。
一、什么是国际化 (i18n) 和本地化 (l10n)?
在深入 load_plugin_textdomain()
之前,我们需要先搞清楚两个概念:国际化 (i18n) 和本地化 (l10n)。
- 国际化 (i18n, Internationalization): 指的是设计和开发产品,使其能够适应不同的语言、区域和文化的技术。简单来说,就是让你的代码具有“国际范儿”,为支持多种语言做好准备。
- 本地化 (l10n, Localization): 指的是将产品改编成特定语言、区域或文化的过程。比如,将英文版的 WordPress 插件翻译成中文,就是本地化的过程。
WordPress 已经为我们提供了强大的国际化和本地化支持,而 load_plugin_textdomain()
就是其中的关键一环。
二、load_plugin_textdomain()
函数:插件国际化的核心
load_plugin_textdomain()
函数是 WordPress 中用于加载插件的文本域(text domain)的函数。文本域就像一个命名空间,用于区分不同插件的翻译文件,避免冲突。
函数签名:
<?php
function load_plugin_textdomain(
string $domain,
bool $deprecated = false,
string $plugin_rel_path = null
): bool;
参数说明:
参数 | 类型 | 描述 |
---|---|---|
$domain |
string |
插件的文本域。这是一个唯一的标识符,用于区分不同插件的翻译文件。建议使用插件的 slug 作为文本域。 |
$deprecated |
bool |
(已弃用) 始终应为 false 。用于向后兼容。 |
$plugin_rel_path |
string |
插件的相对路径。通常是翻译文件存放的目录相对于插件主文件的路径。例如,如果你的翻译文件存放在 languages 目录下,那么 $plugin_rel_path 就应该是 languages 。如果为 null ,则 WordPress 会自动尝试查找默认位置 (插件根目录下的 languages 目录)。 |
返回值:
true
: 如果成功加载了翻译文件。false
: 如果加载失败。
使用示例:
<?php
/**
* Plugin Name: My Awesome Plugin
* Description: An example plugin that demonstrates internationalization.
*/
add_action( 'plugins_loaded', 'my_awesome_plugin_load_textdomain' );
function my_awesome_plugin_load_textdomain() {
load_plugin_textdomain( 'my-awesome-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
}
这段代码做了什么呢?
add_action( 'plugins_loaded', 'my_awesome_plugin_load_textdomain' );
: 这行代码告诉 WordPress,在plugins_loaded
动作被触发时,执行my_awesome_plugin_load_textdomain
函数。plugins_loaded
是一个钩子,在所有插件都加载完毕后触发,是加载翻译文件的最佳时机。load_plugin_textdomain( 'my-awesome-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
: 这行代码调用了load_plugin_textdomain
函数,加载了my-awesome-plugin
文本域的翻译文件。'my-awesome-plugin'
是文本域,必须唯一。false
是为了兼容旧版本,现在已经没用了,始终设置为false
即可。dirname( plugin_basename( __FILE__ ) ) . '/languages/'
是翻译文件存放的目录。plugin_basename( __FILE__ )
获取当前插件主文件的路径,dirname()
获取该路径的目录名,然后拼接上/languages/
,就得到了翻译文件的完整路径。
三、load_plugin_textdomain()
源码剖析:深入内部
现在,让我们深入 load_plugin_textdomain()
的源码,看看它是如何工作的。
(以下源码基于 WordPress 6.4.2)
<?php
function load_plugin_textdomain( string $domain, bool $deprecated = false, string $plugin_rel_path = null ): bool {
global $l10n, $wp_filesystem;
// Deprecated.
if ( ! is_bool( $deprecated ) ) {
_deprecated_argument( __FUNCTION__, '2.7' );
}
// Initialize the locale.
$locale = determine_locale();
$locale = apply_filters( 'plugin_locale', $locale, $domain );
// Bail if no locale is defined.
if ( empty( $locale ) ) {
return false;
}
$mofile_global = WP_LANG_DIR . '/plugins/' . $domain . '-' . $locale . '.mo';
$mofile_local = plugin_dir_path( __FILE__ ) . $plugin_rel_path . '/' . $domain . '-' . $locale . '.mo';
$mofile_default = trailingslashit( WP_PLUGIN_DIR ) . dirname( plugin_basename( __FILE__ ) ) . '/' . $plugin_rel_path . '/' . $domain . '-' . $locale . '.mo';
if ( file_exists( $mofile_global ) ) {
return load_textdomain( $domain, $mofile_global );
}
if ( file_exists( $mofile_local ) ) {
return load_textdomain( $domain, $mofile_local );
}
if ( file_exists( $mofile_default ) ) {
return load_textdomain( $domain, $mofile_default );
}
return false;
}
我们来逐行分析这段代码:
global $l10n, $wp_filesystem;
: 声明全局变量$l10n
和$wp_filesystem
。$l10n
是一个数组,用于存储已加载的文本域和对应的翻译数据。$wp_filesystem
是 WordPress 文件系统抽象层,用于访问文件。if ( ! is_bool( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '2.7' ); }
: 检查$deprecated
参数是否为布尔值,如果不是,则触发一个弃用警告。这个参数在 WordPress 2.7 之后已经不再使用。$locale = determine_locale();
: 调用determine_locale()
函数获取当前的语言环境 (locale)。语言环境是一个字符串,用于表示特定的语言和区域,例如zh_CN
(简体中文)。$locale = apply_filters( 'plugin_locale', $locale, $domain );
: 使用plugin_locale
过滤器修改语言环境。这允许开发者根据自己的需要,自定义插件的语言环境。if ( empty( $locale ) ) { return false; }
: 如果语言环境为空,则直接返回false
,表示加载失败。$mofile_global = WP_LANG_DIR . '/plugins/' . $domain . '-' . $locale . '.mo';
$mofile_local = plugin_dir_path( __FILE__ ) . $plugin_rel_path . '/' . $domain . '-' . $locale . '.mo';
$mofile_default = trailingslashit( WP_PLUGIN_DIR ) . dirname( plugin_basename( __FILE__ ) ) . '/' . $plugin_rel_path . '/' . $domain . '-' . $locale . '.mo';
:
这三行代码分别构建了三个可能的.mo
文件的路径。.mo
文件是 Machine Object 的缩写,是编译后的二进制翻译文件,包含了翻译后的字符串。$mofile_global
: 全局.mo
文件路径,位于WP_LANG_DIR/plugins/
目录下。WP_LANG_DIR
是 WordPress 语言文件的目录,通常是wp-content/languages
。$mofile_local
: 本地.mo
文件路径,位于插件目录下的$plugin_rel_path
目录下。$mofile_default
: 默认.mo
文件路径,也位于插件目录下的$plugin_rel_path
目录下。
if ( file_exists( $mofile_global ) ) { return load_textdomain( $domain, $mofile_global ); }
if ( file_exists( $mofile_local ) ) { return load_textdomain( $domain, $mofile_local ); }
if ( file_exists( $mofile_default ) ) { return load_textdomain( $domain, $mofile_default ); }
:
这三行代码依次检查上述三个.mo
文件是否存在。如果存在,则调用load_textdomain()
函数加载该文件,并返回true
。return false;
: 如果所有.mo
文件都不存在,则返回false
,表示加载失败。
总结:load_plugin_textdomain()
的工作流程
load_plugin_textdomain()
函数的工作流程可以总结为以下几个步骤:
- 获取当前的语言环境 (locale)。
- 构建可能的
.mo
文件路径 (全局、本地、默认)。 - 依次检查这些
.mo
文件是否存在。 - 如果存在,则调用
load_textdomain()
函数加载该文件。 - 如果所有
.mo
文件都不存在,则返回false
。
四、load_textdomain()
函数:加载翻译文件的核心
load_plugin_textdomain()
函数实际上是调用 load_textdomain()
函数来加载翻译文件的。load_textdomain()
函数才是真正加载翻译数据的核心。
函数签名:
<?php
function load_textdomain( string $domain, string $mofile ): bool;
参数说明:
参数 | 类型 | 描述 |
---|---|---|
$domain |
string |
文本域。 |
$mofile |
string |
.mo 文件的完整路径。 |
返回值:
true
: 如果成功加载了翻译文件。false
: 如果加载失败。
load_textdomain()
的源码比较复杂,这里我们只分析关键部分:
<?php
function load_textdomain( string $domain, string $mofile ): bool {
global $l10n, $wp_filesystem;
/**
* Filters whether to load the text domain.
*
* @since 3.0.0
*
* @param bool $load Whether to load the text domain. Default true.
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
* @param string $mofile Path to the MO file.
*/
$load = apply_filters( 'load_textdomain', true, $domain, $mofile );
if ( ! $load ) {
return false;
}
if ( ! is_readable( $mofile ) ) {
return false;
}
$l10n[ $domain ] = new MO();
$loaded = $l10n[ $domain ]->import_from_file( $mofile );
if ( ! $loaded ) {
unset( $l10n[ $domain ] );
return false;
}
$l10n[ $domain ]->domain = $domain;
/**
* Fires after the text domain is loaded.
*
* @since 2.9.0
*
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
* @param string $mofile Path to the MO file.
*/
do_action( 'textdomain_loaded', $domain, $mofile );
return true;
}
$load = apply_filters( 'load_textdomain', true, $domain, $mofile );
: 使用load_textdomain
过滤器,允许开发者控制是否加载指定的文本域。if ( ! is_readable( $mofile ) ) { return false; }
: 检查.mo
文件是否可读。如果不可读,则返回false
。$l10n[ $domain ] = new MO();
: 创建一个MO
类的实例。MO
类是 WordPress 中用于处理.mo
文件的类。$loaded = $l10n[ $domain ]->import_from_file( $mofile );
: 调用MO
类的import_from_file()
方法,从.mo
文件中导入翻译数据。if ( ! $loaded ) { unset( $l10n[ $domain ] ); return false; }
: 如果导入失败,则从$l10n
数组中移除该文本域,并返回false
。$l10n[ $domain ]->domain = $domain;
: 设置MO
实例的domain
属性。do_action( 'textdomain_loaded', $domain, $mofile );
: 触发textdomain_loaded
动作,允许开发者在文本域加载完成后执行一些操作。return true;
: 返回true
,表示加载成功。
五、如何使用翻译函数?
加载了翻译文件之后,我们就可以使用 WordPress 提供的翻译函数来显示翻译后的字符串了。常用的翻译函数有:
__()
: 返回翻译后的字符串。_e()
: 输出翻译后的字符串。_x()
: 返回带上下文的翻译后的字符串。_ex()
: 输出带上下文的翻译后的字符串。_n()
: 返回单复数形式的翻译后的字符串。_nx()
: 返回带上下文的单复数形式的翻译后的字符串。
示例:
<?php
// 返回翻译后的字符串
$translated_string = __( 'Hello, world!', 'my-awesome-plugin' );
echo $translated_string;
// 输出翻译后的字符串
_e( 'Hello, world!', 'my-awesome-plugin' );
// 返回带上下文的翻译后的字符串
$translated_string = _x( 'Post', 'noun', 'my-awesome-plugin' );
echo $translated_string;
// 返回单复数形式的翻译后的字符串
$translated_string = _n( '%s comment', '%s comments', $comment_count, 'my-awesome-plugin' );
echo $translated_string;
注意:
- 所有翻译函数都需要指定文本域,即你在
load_plugin_textdomain()
函数中使用的$domain
参数。 - 翻译函数中的第一个参数是要翻译的原始字符串。这个字符串应该尽量使用英文,并且保持简洁明了。
六、创建翻译文件:.po
和 .mo
要进行本地化,我们需要创建翻译文件。WordPress 使用两种类型的翻译文件:
- .po (Portable Object): 是人类可读的文本文件,包含了原始字符串和翻译后的字符串。
- .mo (Machine Object): 是编译后的二进制文件,用于 WordPress 加载翻译数据。
创建 .po
文件:
- 使用 Poedit 或其他类似的翻译工具创建一个新的
.po
文件。 - 在
.po
文件中,你需要添加所有需要翻译的字符串。 - 对于每个字符串,你需要提供翻译后的字符串。
.po
文件示例:
msgid "Hello, world!"
msgstr "你好,世界!"
msgid "Welcome to my awesome plugin!"
msgstr "欢迎使用我的超棒插件!"
将 .po
文件编译成 .mo
文件:
- 使用 Poedit 或其他类似的翻译工具打开
.po
文件。 - 保存
.po
文件。Poedit 会自动创建一个与.po
文件同名的.mo
文件。
七、最佳实践
- 使用唯一的文本域: 确保你的插件使用唯一的文本域,避免与其他插件冲突。建议使用插件的 slug 作为文本域。
- 将翻译文件放在
languages
目录下: 建议将翻译文件放在插件根目录下的languages
目录下,这是一个标准的做法。 - 使用合适的翻译函数: 根据需要选择合适的翻译函数,例如
__()
、_e()
、_x()
、_ex()
、_n()
和_nx()
。 - 保持原始字符串的简洁明了: 原始字符串应该尽量使用英文,并且保持简洁明了,方便翻译人员理解。
- 定期更新翻译文件: 随着插件的更新,可能会添加新的字符串或修改现有的字符串。因此,需要定期更新翻译文件,确保翻译的准确性。
- 利用在线翻译平台: 可以利用一些在线翻译平台,例如 Transifex 或 GlotPress,来管理和协作翻译工作。
八、总结
load_plugin_textdomain()
函数是 WordPress 插件国际化的核心,它负责加载插件的翻译文件,让你的插件能够支持多种语言。通过深入了解 load_plugin_textdomain()
函数的源码,我们可以更好地理解 WordPress 的国际化机制,为我们的插件提供更好的本地化支持。
希望今天的讲座对大家有所帮助。记住,让你的插件说“世界语”,走向国际,从 load_plugin_textdomain()
开始!
感谢大家的聆听!