探究 WordPress `load_plugin_textdomain()` 函数的源码:如何为插件加载翻译文件。

大家好,欢迎来到今天的“WordPress翻译探秘之旅”。我是你们今天的导游,名叫“代码侦探”,专职挖掘那些隐藏在WordPress源码深处的秘密。今天,我们要一起扒一扒 load_plugin_textdomain() 这个神秘函数,看看它到底是怎么为我们的插件加载翻译文件的。

准备好了吗?Let’s go!

第一站:什么是 Text Domain?

在深入 load_plugin_textdomain() 之前,我们先要搞清楚一个核心概念:Text Domain。你可以把它想象成一个“翻译身份证”,每个插件都有一个独一无二的身份证号,用来区分不同的翻译文件。

举个例子,如果你的插件叫做 "My Awesome Plugin",你的 Text Domain 可能是 "my-awesome-plugin"。这个名字很重要,因为它会出现在你的插件代码中,也出现在翻译文件的命名中。

Text Domain 的作用:

  • 区分翻译文件: 避免不同插件的翻译文件冲突。
  • 绑定翻译与代码: 将特定翻译文件与特定插件的代码关联起来。

第二站:load_plugin_textdomain() 的基本用法

好了,现在我们来看看 load_plugin_textdomain() 函数的基本用法。这个函数通常放在插件的主文件中,在插件激活的时候调用。

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Description: A plugin that does awesome things.
 * Version: 1.0.0
 * Author: Code Detective
 */

add_action( 'plugins_loaded', 'my_awesome_plugin_load_textdomain' );

function my_awesome_plugin_load_textdomain() {
    load_plugin_textdomain(
        'my-awesome-plugin', // Text Domain
        false,                // Deprecated argument (always false)
        dirname( plugin_basename( __FILE__ ) ) . '/languages/' // Path to the languages folder
    );
}

代码解释:

  1. add_action( 'plugins_loaded', 'my_awesome_plugin_load_textdomain' ) 这行代码的意思是,当 WordPress 加载完所有插件后,就执行 my_awesome_plugin_load_textdomain() 函数。
  2. load_plugin_textdomain() 这是我们今天的主角,它负责加载翻译文件。
  3. 'my-awesome-plugin' 这是 Text Domain,必须和你的插件代码中使用的 Text Domain 保持一致。
  4. false 这个参数已经过时了,始终设置为 false
  5. dirname( plugin_basename( __FILE__ ) ) . '/languages/' 这是翻译文件存放的路径。plugin_basename( __FILE__ ) 获取插件主文件的相对路径,dirname() 获取插件主文件的目录,然后拼接上 /languages/,表示翻译文件存放在插件目录下的 languages 文件夹中。

翻译文件的命名规则:

翻译文件必须遵循一定的命名规则,才能被 WordPress 正确加载。规则如下:

{textdomain}-{locale}.mo

  • {textdomain} 插件的 Text Domain,例如 my-awesome-plugin
  • {locale} WordPress 的本地化代码,例如 zh_CN (简体中文), en_US (美国英语), fr_FR (法语) 等。
  • .mo: 这是编译后的二进制翻译文件,需要使用工具从 .po 文件生成。

所以,如果你的插件 Text Domain 是 my-awesome-plugin,你想提供简体中文翻译,你的翻译文件就应该命名为 my-awesome-plugin-zh_CN.mo

第三站:深入 load_plugin_textdomain() 源码

现在,我们来深入 load_plugin_textdomain() 的源码,看看它背后到底做了什么。

为了方便查看,我们简化一下代码(省略一些不常用的参数和错误处理):

// wp-includes/l10n.php

function load_plugin_textdomain( $domain, $deprecated, $plugin_rel_path ) {
    return load_textdomain( $domain, WP_PLUGIN_DIR . '/' . $plugin_rel_path . '/' . $domain . '-' . get_locale() . '.mo' );
}

function load_textdomain( $domain, $mofile ) {
    global $l10n;

    if ( isset( $l10n[ $domain ] ) ) {
        return true; // Already loaded
    }

    if ( ! is_readable( $mofile ) ) {
        return false; // File not found
    }

    $l10n[ $domain ] = new MO();
    $loaded = $l10n[ $domain ]->import_from_file( $mofile );

    return $loaded;
}

代码解释:

  1. load_plugin_textdomain()
    • 它接收 Text Domain ($domain) 和 翻译文件相对路径 ($plugin_rel_path) 作为参数。
    • 它拼接出完整的翻译文件路径,然后调用 load_textdomain() 函数。
  2. load_textdomain()
    • 它接收 Text Domain ($domain) 和 翻译文件完整路径 ($mofile) 作为参数。
    • 检查是否已经加载: 首先检查全局变量 $l10n 中是否已经存在该 Text Domain 的翻译,如果存在,则直接返回 true,避免重复加载。
    • 检查文件是否存在: 然后检查翻译文件是否存在且可读,如果不存在,则返回 false
    • 加载翻译文件: 如果文件存在,则创建一个 MO 对象(MO 类是 WordPress 用来处理 .mo 文件的类),并调用 import_from_file() 方法加载翻译文件。
    • 存储翻译数据: 将加载的翻译数据存储到全局变量 $l10n 中,以 Text Domain 作为键。
    • 返回结果: 返回 import_from_file() 的结果,表示是否成功加载翻译文件。

MO 类:

MO 类负责解析 .mo 文件,并将翻译数据存储在内存中。它的 import_from_file() 方法会读取 .mo 文件的内容,并将其转换为一个关联数组,其中键是原文,值是译文。

总结:

load_plugin_textdomain() 函数实际上只是一个简单的封装,它主要负责拼接翻译文件的完整路径,然后调用 load_textdomain() 函数来真正加载翻译文件。load_textdomain() 函数则负责检查文件是否存在、是否已经加载,以及使用 MO 类加载翻译数据。

第四站:实战演练

光说不练假把式,我们来做一个简单的实战演练。

  1. 创建插件: 创建一个名为 my-awesome-plugin 的插件,包含一个主文件 my-awesome-plugin.php 和一个 languages 文件夹。
  2. 添加代码: 将上面的代码示例添加到 my-awesome-plugin.php 文件中。
  3. 添加翻译:my-awesome-plugin.php 文件中添加一些需要翻译的文本。
<?php
/**
 * Plugin Name: My Awesome Plugin
 * Description: A plugin that does awesome things.
 * Version: 1.0.0
 * Author: Code Detective
 */

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() {
    echo '<p>' . __( 'Hello, world!', 'my-awesome-plugin' ) . '</p>';
    echo '<p>' . __( 'This is an awesome plugin.', 'my-awesome-plugin' ) . '</p>';
}

add_action( 'wp_footer', 'my_awesome_plugin_output' );
  1. 创建 .po 文件: 使用 Poedit 或其他翻译工具,创建一个名为 my-awesome-plugin-zh_CN.po 的文件,并添加以下翻译:
msgid "Hello, world!"
msgstr "你好,世界!"

msgid "This is an awesome plugin."
msgstr "这是一个很棒的插件。"
  1. 编译 .mo 文件: 使用 Poedit 或其他翻译工具,将 .po 文件编译成 .mo 文件。
  2. 上传文件:my-awesome-plugin-zh_CN.mo 文件上传到 my-awesome-plugin/languages/ 文件夹中。
  3. 设置 WordPress 语言: 在 WordPress 后台,将语言设置为简体中文。
  4. 激活插件: 激活 My Awesome Plugin 插件。

现在,刷新你的网站,你应该可以看到 "Hello, world!" 和 "This is an awesome plugin." 已经变成了中文。

第五站:高级用法与技巧

  • 使用不同的翻译文件路径: 你可以通过修改 load_plugin_textdomain() 的第三个参数来指定不同的翻译文件路径。例如,你可以将翻译文件放在主题的 languages 文件夹中。
  • 使用 load_muplugin_textdomain()load_child_theme_textdomain() WordPress 还提供了 load_muplugin_textdomain()load_child_theme_textdomain() 函数,分别用于加载 Must-Use 插件和子主题的翻译文件。
  • 使用 __(), _e(), _x(), _ex() 等翻译函数: WordPress 提供了多个翻译函数,用于处理不同类型的翻译需求。 __() 返回翻译后的字符串,_e() 直接输出翻译后的字符串,_x() 允许你提供上下文信息,_ex() 结合了 _x()_e() 的功能。
  • 使用 wp i18n make-pot 命令: WordPress CLI 提供了 wp i18n make-pot 命令,可以自动从你的插件或主题代码中提取需要翻译的文本,并生成一个 .po 文件。

总结:

函数/命令 作用
load_plugin_textdomain() 加载插件的翻译文件。
load_muplugin_textdomain() 加载 Must-Use 插件的翻译文件。
load_child_theme_textdomain() 加载子主题的翻译文件。
__() 返回翻译后的字符串。
_e() 输出翻译后的字符串。
_x() 返回翻译后的字符串,允许提供上下文信息。
_ex() 输出翻译后的字符串,允许提供上下文信息。
wp i18n make-pot 自动从代码中提取需要翻译的文本,并生成 .po 文件。

第六站:常见问题与解决方法

  • 翻译文件没有生效:
    • 检查 Text Domain 是否正确。
    • 检查翻译文件命名是否符合规则。
    • 检查翻译文件路径是否正确。
    • 检查 WordPress 语言设置是否正确。
    • 清除 WordPress 缓存。
  • 翻译文本显示不完整:
    • 检查 .po 文件中是否存在错误的翻译。
    • 检查 .mo 文件是否已正确编译。
  • 使用 wp i18n make-pot 命令时出错:
    • 检查 WordPress CLI 是否已正确安装和配置。
    • 检查你的插件或主题代码中是否存在语法错误。

第七站:旅程结束

恭喜你,完成了今天的“WordPress翻译探秘之旅”!希望你对 load_plugin_textdomain() 函数有了更深入的了解。现在,你可以自信地为你的插件添加翻译功能,让你的插件走向世界!

记住,代码的世界充满了乐趣和挑战,Keep exploring! 下次再见! (挥手)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注