各位代码爱好者,大家好!今天咱们来聊聊 WordPress 插件国际化中一个非常关键的函数:load_plugin_textdomain()
。这货听起来很高大上,但其实就是负责加载你插件的翻译文件,让你的插件能说各国语言,变得更国际范儿。
咱们今天就深入它的源码,看看它到底是怎么运作的,以及在使用过程中需要注意哪些坑。准备好了吗? Let’s dive in!
1. 什么是 Text Domain? 为什么需要它?
在深入 load_plugin_textdomain()
之前,先得搞清楚什么是 "Text Domain"。 简单来说,Text Domain 就是你插件的身份标识,一个唯一的字符串,用来区分不同插件的翻译文件。 想象一下,如果没有 Text Domain,所有插件的翻译文件都叫 default.mo
,那还不乱套了? WordPress 就不知道该用哪个翻译文件来显示你的插件文本了。
Text Domain 就像是你的插件的身份证号码,确保翻译文件能正确地对应到你的插件。
为什么需要 Text Domain?
- 避免冲突: 不同的插件可以使用相同的字符串,但他们的翻译可能不同。Text Domain 确保翻译能正确对应插件。
- 组织翻译: Text Domain 将插件的翻译文件组织在一起,方便管理和维护。
- WordPress 识别: WordPress 使用 Text Domain 来识别和加载正确的翻译文件。
2. load_plugin_textdomain()
函数的庐山真面目
load_plugin_textdomain()
函数的原型如下:
/**
* Loads a plugin's translated strings.
*
* @since 2.7.0
*
* @param string $domain Unique identifier for retrieving translated strings.
* @param string $deprecated Deprecated since 2.7.0. Use the plugin_rel_path parameter instead.
* @param string|false $plugin_rel_path Path to the plugin folder, relative to the plugins folder.
* Default false.
* @return bool True when textdomain is successfully loaded, false otherwise.
*/
function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) {
global $l10n, $wp_version;
// First, check if the function has been called without the optional arguments. If so, we may need to process
// the arguments.
if ( false !== $deprecated ) {
_deprecated_argument( __FUNCTION__, '2.7', sprintf(
/* translators: 1: Function name, 2: Argument name. */
__( 'The %1$s function's %2$s argument is deprecated.' ),
'<code>load_plugin_textdomain()</code>',
'<code>$path</code>'
) );
}
// If no plugin_rel_path path is given, define it.
if ( false === $plugin_rel_path ) {
$plugin_rel_path = dirname( plugin_basename( __FILE__ ) );
}
// Sanitize the domain.
$domain = sanitize_key( $domain );
// Load languages for backwards compatibility.
if ( isset( $l10n[ $domain ] ) && ( ! is_a( $l10n[ $domain ], 'NOOP_Translations' ) ) ) {
return true;
}
// Determine plugin locale location.
$locale = apply_filters( 'plugin_locale', get_locale(), $domain );
$mofile = $domain . '-' . $locale . '.mo';
$plugin_path = trailingslashit( WP_PLUGIN_DIR ) . trailingslashit( dirname( $plugin_rel_path ) );
$mofile_global = WP_LANG_DIR . '/plugins/' . $mofile;
$mofile_local = $plugin_path . $mofile;
if ( is_readable( $mofile_global ) ) {
return load_textdomain( $domain, $mofile_global );
} elseif ( is_readable( $mofile_local ) ) {
return load_textdomain( $domain, $mofile_local );
}
return false;
}
别被这坨代码吓到,其实它做的事情很简单,咱们把它拆解一下:
参数说明:
参数 | 类型 | 描述 |
---|---|---|
$domain |
string | 你的 Text Domain,也就是插件的唯一标识。 |
$deprecated |
string | 已经废弃的参数,不用管它。 |
$plugin_rel_path |
string | 插件目录相对于 plugins 目录的路径。 例如,如果你的插件目录是 wp-content/plugins/my-awesome-plugin/ ,那么这个参数就是 my-awesome-plugin/ 。 如果设置为 false (默认值),函数会自动计算。 推荐设置为 false ,让 WordPress 自己算,省事。 |
函数做了什么:
- 参数处理: 检查参数,处理废弃参数,如果
$plugin_rel_path
是false
,则自动计算。 - 清理 Text Domain: 使用
sanitize_key()
函数清理$domain
,确保其符合 WordPress 的命名规范。 - 检查是否已加载: 检查全局变量
$l10n
,看看这个 Text Domain 的翻译文件是否已经加载过了。如果已经加载,就直接返回true
,避免重复加载。 - 确定语言区域 (Locale): 使用
apply_filters( 'plugin_locale', get_locale(), $domain )
获取当前站点的语言区域。get_locale()
获取 WordPress 设置的语言,apply_filters
允许其他插件修改这个语言区域。 - 构建翻译文件路径: 根据 Text Domain 和语言区域,构建翻译文件的名称 (
.mo
文件)。例如,如果 Text Domain 是my-awesome-plugin
,语言区域是zh_CN
,那么翻译文件的名称就是my-awesome-plugin-zh_CN.mo
。 - 查找翻译文件: 按照以下顺序查找翻译文件:
- 全局翻译目录:
WP_LANG_DIR . '/plugins/' . $mofile
。WP_LANG_DIR
通常是wp-content/languages/
。 所以全局路径可能是wp-content/languages/plugins/my-awesome-plugin-zh_CN.mo
。 - 插件目录:
$plugin_path . $mofile
。$plugin_path
是插件的完整路径。 所以插件目录路径可能是wp-content/plugins/my-awesome-plugin/my-awesome-plugin-zh_CN.mo
。
- 全局翻译目录:
- 加载翻译文件: 如果找到翻译文件,就使用
load_textdomain()
函数加载它。 - 返回结果: 如果成功加载翻译文件,返回
true
,否则返回false
。
3. 源码剖析 (重点)
咱们来重点看看几个关键部分:
-
自动计算
$plugin_rel_path
:if ( false === $plugin_rel_path ) { $plugin_rel_path = dirname( plugin_basename( __FILE__ ) ); }
这段代码负责在
$plugin_rel_path
为false
时,自动计算插件目录相对于plugins
目录的路径。它使用了两个函数:plugin_basename( __FILE__ )
: 返回当前文件的插件主文件相对于plugins
目录的路径。 例如,如果你的插件主文件是wp-content/plugins/my-awesome-plugin/my-awesome-plugin.php
,那么plugin_basename( __FILE__ )
会返回my-awesome-plugin/my-awesome-plugin.php
。dirname()
: 返回路径的目录部分。 例如,dirname( 'my-awesome-plugin/my-awesome-plugin.php' )
会返回my-awesome-plugin
。
所以,这段代码实际上是获取了插件主文件所在的目录,作为
$plugin_rel_path
。 这也是为什么我们通常建议把load_plugin_textdomain()
放在插件主文件中,这样 WordPress 才能正确地计算路径。 -
构建翻译文件路径:
$locale = apply_filters( 'plugin_locale', get_locale(), $domain ); $mofile = $domain . '-' . $locale . '.mo'; $plugin_path = trailingslashit( WP_PLUGIN_DIR ) . trailingslashit( dirname( $plugin_rel_path ) ); $mofile_global = WP_LANG_DIR . '/plugins/' . $mofile; $mofile_local = $plugin_path . $mofile;
这段代码构建了全局和本地两种翻译文件的路径。 注意
trailingslashit()
函数,它可以确保路径以斜杠结尾,避免路径错误。 -
查找和加载翻译文件:
if ( is_readable( $mofile_global ) ) { return load_textdomain( $domain, $mofile_global ); } elseif ( is_readable( $mofile_local ) ) { return load_textdomain( $domain, $mofile_local ); }
这段代码首先检查全局翻译目录是否存在翻译文件,如果存在,就加载它。 否则,检查插件目录是否存在翻译文件,如果存在,就加载它。 注意
is_readable()
函数,它会检查文件是否存在并且可读。load_textdomain()
函数是真正加载翻译文件的函数,它会将翻译文件中的字符串加载到全局变量$l10n
中。
4. 如何使用 load_plugin_textdomain()
说了这么多,咱们来看看怎么在实际代码中使用 load_plugin_textdomain()
:
<?php
/**
* Plugin Name: My Awesome Plugin
* Description: A simple plugin to demonstrate internationalization.
* Version: 1.0.0
*/
// 在插件激活时加载翻译文件 (推荐)
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' );
}
// 使用翻译函数
function my_awesome_plugin_output_text() {
echo __( 'Hello, world!', 'my-awesome-plugin' );
}
add_action( 'wp_footer', 'my_awesome_plugin_output_text' );
代码解释:
- 插件头部: 定义插件的名称、描述和版本等信息。
plugins_loaded
钩子: 使用add_action( 'plugins_loaded', 'my_awesome_plugin_load_textdomain' )
将my_awesome_plugin_load_textdomain()
函数绑定到plugins_loaded
钩子。plugins_loaded
钩子在 WordPress 加载所有插件后触发,是加载翻译文件的推荐时机。my_awesome_plugin_load_textdomain()
函数: 调用load_plugin_textdomain()
函数加载翻译文件。'my-awesome-plugin'
是 Text Domain。false
表示使用默认的翻译文件路径。dirname( plugin_basename( __FILE__ ) ) . '/languages'
指定翻译文件存放的目录。 这里假设你的翻译文件放在插件目录下的languages
目录中。 例如,wp-content/plugins/my-awesome-plugin/languages/my-awesome-plugin-zh_CN.mo
。
my_awesome_plugin_output_text()
函数: 使用__()
函数输出翻译后的文本。__()
函数是 WordPress 提供的翻译函数,它会根据当前的语言区域,查找 Text Domain 对应的翻译,并返回翻译后的文本。wp_footer
钩子: 使用add_action( 'wp_footer', 'my_awesome_plugin_output_text' )
将my_awesome_plugin_output_text()
函数绑定到wp_footer
钩子,在页面底部输出翻译后的文本。
翻译文件的目录结构:
你的插件目录结构应该类似这样:
my-awesome-plugin/
├── my-awesome-plugin.php (插件主文件)
├── languages/
│ ├── my-awesome-plugin-zh_CN.mo
│ ├── my-awesome-plugin-zh_CN.po
│ └── ...
└── ...
5. 注意事项 (踩坑指南)
- Text Domain 必须唯一: 确保你的 Text Domain 在所有插件中是唯一的。 通常建议使用插件的 slug 作为 Text Domain。
- 正确指定翻译文件路径: 确保
load_plugin_textdomain()
函数的第三个参数指定了正确的翻译文件路径。 如果路径不正确,WordPress 无法找到翻译文件。 - 使用正确的翻译函数: 使用
__()
、_e()
、_x()
、_ex()
等 WordPress 提供的翻译函数来输出文本。 不要直接输出文本,否则无法进行翻译。 - 生成
.mo
文件:.mo
文件是二进制的翻译文件,WordPress 才能读取。 你需要使用工具 (例如 Poedit) 将.po
文件转换为.mo
文件。 - 清理缓存: 如果你修改了翻译文件,需要清理 WordPress 的缓存,才能看到更新后的翻译。
- 加载时机:
plugins_loaded
钩子是加载翻译文件的推荐时机。 不要在init
钩子之前加载翻译文件,否则可能无法获取到正确的语言区域。 - 更新翻译文件: 如果你的插件更新了,并且添加了新的文本,需要更新翻译文件,并重新生成
.mo
文件。
6. load_textdomain()
函数 (幕后英雄)
load_plugin_textdomain()
函数最终会调用 load_textdomain()
函数来加载翻译文件。 咱们也简单了解一下 load_textdomain()
:
/**
* Loads a .mo file into the text domain.
*
* If the text domain already exists, the translations will be merged. If both
* sets of translations have the same string, the translation from the original
* text domain will be used.
*
* @since 1.5.0
*
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
* @param string $mofile Path to the MO file.
* @return bool True on success, false on failure.
*/
function load_textdomain( $domain, $mofile ) {
global $l10n;
$domain = sanitize_key( $domain );
// Has the text domain already been loaded?
if ( isset( $l10n[ $domain ] ) && ( ! is_a( $l10n[ $domain ], 'NOOP_Translations' ) ) ) {
return true;
}
/**
* Fires before the MO file 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( 'load_textdomain', $domain, $mofile );
$mo = new MO();
if ( ! $mo->import_from_file( $mofile ) ) {
return false;
}
$l10n[ $domain ] = &$mo;
unset( $mo );
/**
* 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_textdomain()
函数主要做了以下事情:
- 检查是否已加载: 与
load_plugin_textdomain()
类似,先检查是否已经加载过该 Text Domain 的翻译文件。 - 触发钩子: 触发
load_textdomain
钩子,允许其他插件在加载翻译文件之前执行一些操作。 - 加载
.mo
文件: 使用MO
类 (一个 WordPress 内部类,用于处理.mo
文件) 加载.mo
文件。 - 存储翻译数据: 将加载的翻译数据存储到全局变量
$l10n
中。$l10n
是一个关联数组,以 Text Domain 为键,以MO
对象为值。 - 触发钩子: 触发
textdomain_loaded
钩子,允许其他插件在加载翻译文件之后执行一些操作。
7. 总结
load_plugin_textdomain()
函数是 WordPress 插件国际化的关键函数。 理解它的源码,可以帮助你更好地管理插件的翻译文件,避免踩坑,让你的插件能够更好地服务全球用户。
记住以下几点:
- Text Domain 是插件的唯一标识。
- 使用
load_plugin_textdomain()
函数加载翻译文件。 - 确保翻译文件路径正确。
- 使用 WordPress 提供的翻译函数。
- 生成
.mo
文件。 - 在
plugins_loaded
钩子加载翻译文件。
希望今天的讲座对你有所帮助! 以后开发插件的时候,记得让你的插件也说几句外语,走向世界! 咱们下次再见!