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

哈喽,各位观众老爷们,我是你们的码农老司机。今天咱们聊聊 WordPress 插件国际化的幕后英雄—— load_plugin_textdomain() 函数。 这玩意儿,看着挺唬人,其实就是个帮你的插件说外语的翻译官。

开场白:为什么需要国际化?

想象一下,你辛辛苦苦写了个超级牛逼的插件,结果只有说中文的人能用,那多可惜啊! 为了让你的插件走向世界,服务全球人民,国际化 (i18n) 就显得尤为重要了。 而 load_plugin_textdomain() 就是 i18n 流程中,加载翻译文件,让你的插件显示不同语言的关键步骤。

主角登场: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 base path instead.
 * @param string|bool $plugin_rel_path (optional) Relative path to .mo file from plugin directory.
 *                                      Default false.
 * @return bool True when textdomain is successfully loaded, false on failure.
 */
function load_plugin_textdomain( $domain, $deprecated = false, $plugin_rel_path = false ) {
    global $l10n, $wp_plugin_paths;

    // First, check if the textdomain is loaded already.
    if ( isset( $l10n[ $domain ] ) ) {
        return true;
    }

    $locale = determine_locale();
    $locale = apply_filters( 'plugin_locale', $locale, $domain );

    // If no textdomain is supplied, exit gracefully.
    if ( empty( $domain ) ) {
        return false;
    }

    // If we don't have a plugin_rel_path, derive it from the constant.
    if ( false === $plugin_rel_path ) {
        $plugin_rel_path = dirname( plugin_basename( __FILE__ ) ); // Assuming this is called from the plugin's main file
    }

    // Sanitize the plugin path.
    $plugin_path = untrailingslashit( $plugin_rel_path );

    $mofile = $domain . '-' . $locale . '.mo';

    // Prepend constants if available.
    if ( defined( 'WP_LANG_DIR' ) ) {
        $mofile_global = WP_LANG_DIR . '/plugins/' . $mofile;
    }

    $mofile_local = WP_PLUGIN_DIR . '/' . $plugin_path . '/' . $mofile;

    // Look in global /wp-content/languages/plugins/ folder first.
    if ( ! empty( $mofile_global ) && file_exists( $mofile_global ) ) {
        return load_textdomain( $domain, $mofile_global );
    }

    // Then look in /wp-content/plugins/<plugin_path>/ folder.
    if ( file_exists( $mofile_local ) ) {
        return load_textdomain( $domain, $mofile_local );
    }

    // Finally, look in WP_LANG_DIR subfolder if defined.
    if ( defined( 'WPMU_PLUGIN_DIR' ) && file_exists( WPMU_PLUGIN_DIR . '/' . $plugin_path . '/' . $mofile ) ) {
        return load_textdomain( $domain, WPMU_PLUGIN_DIR . '/' . $plugin_path . '/' . $mofile );
    }

    if ( ( defined( 'WP_LANG' ) && WP_LANG ) || ( defined( 'WPLANG' ) && WPLANG ) ) {
        $mofile = $domain . '-' . substr( get_locale(), 0, 2 ) . '.mo';
        // Prepend constants if available.
        if ( defined( 'WP_LANG_DIR' ) ) {
            $mofile_global = WP_LANG_DIR . '/plugins/' . $mofile;
        }
        if ( ! empty( $mofile_global ) && file_exists( $mofile_global ) ) {
            return load_textdomain( $domain, $mofile_global );
        }
    }

    return false;
}

别被这么长的代码吓到,咱们把它拆开揉碎了细细分析。

参数详解

  • $domain: 你的插件的文本域 (text domain)。 这是一个唯一的字符串,用来区分不同插件的翻译文本。 就像你的插件的身份证号一样,一定要独一无二!通常使用插件的slug,比如 my-awesome-plugin
  • $deprecated: 这个参数已经过时了,不用管它。 属于时代的眼泪,留着只是为了兼容旧版本。
  • $plugin_rel_path: 你的翻译文件 (.mo 文件) 相对于插件主目录的路径。如果你的 .mo 文件直接放在插件主目录下,就设为 false。 如果放在比如 languages 目录下,就设为 languages

工作原理:翻译官的寻宝之旅

load_plugin_textdomain() 的主要任务是找到并加载正确的 .mo 翻译文件。 它会按以下顺序搜索:

  1. 检查是否已经加载: 首先,它会检查 $l10n 全局变量,看看你的文本域是否已经加载过了。 如果加载过了,就直接返回 true,省事儿!

  2. 确定当前语言环境: 调用 determine_locale() 获取当前的语言环境 (locale),比如 zh_CN (简体中文), en_US (美国英语) 等。 还会应用 plugin_locale 过滤器,允许开发者自定义语言环境。

  3. 处理空的文本域: 如果 $domain 为空,啥也不做,直接返回 false

  4. 计算相对路径: 如果 $plugin_rel_pathfalse,则尝试自动获取插件主目录的相对路径。 这里假设你是在插件的主文件中调用 load_plugin_textdomain(),如果不是,就需要手动指定 $plugin_rel_path

  5. 构建 .mo 文件名: 根据文本域和语言环境构建 .mo 文件的文件名,例如 my-awesome-plugin-zh_CN.mo

  6. 寻宝开始! 依次在以下目录中寻找 .mo 文件:

    • /wp-content/languages/plugins/: 这是 WordPress 存放全局插件翻译文件的地方。
    • /wp-content/plugins/<plugin_path>/: 这是插件自己的翻译文件存放的地方。
    • WPMU_PLUGIN_DIR/<plugin_path>/: 如果是多站点 (Multisite),并且插件是网络激活的,则会在这里寻找。
  7. 备用方案: 如果上面的寻宝都失败了,它还会尝试使用简化的语言代码 (比如 zh 而不是 zh_CN) 再次查找。

  8. 加载 .mo 文件: 如果找到了 .mo 文件,就调用 load_textdomain() 函数来真正加载翻译文件。

  9. 宣告结果: 如果成功加载了 .mo 文件,就返回 true,否则返回 false

代码实战:让你的插件说中文

假设你的插件叫做 my-awesome-plugin,并且你的翻译文件放在 languages 目录下。 那么,在你的插件主文件中,你可以这样调用 load_plugin_textdomain()

<?php
/**
 * Plugin Name: My Awesome Plugin
 * Plugin URI:  https://example.com/my-awesome-plugin
 * Description: A super awesome plugin.
 * Version:     1.0.0
 * Author:      Your Name
 * Author URI:  https://example.com
 * License:     GPL2
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: my-awesome-plugin
 * Domain Path: /languages
 */

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' );
}

这段代码做了以下几件事:

  1. 插件头部信息: 在插件头部信息中,定义了 Text DomainDomain PathText Domain 必须和 load_plugin_textdomain() 的第一个参数一致。 Domain Path 指定了翻译文件存放的目录。
  2. plugins_loaded 钩子: 使用 add_action()my_awesome_plugin_load_textdomain() 函数挂载到 plugins_loaded 钩子上。 这意味着在所有插件加载完毕后,才会执行这个函数。 确保 WordPress 已经准备好加载翻译文件。
  3. my_awesome_plugin_load_textdomain() 函数: 这个函数调用 load_plugin_textdomain(),传入了正确的参数。 dirname( plugin_basename( __FILE__ ) ) 获取插件主目录的路径。

准备翻译文件:.po.mo 的爱恨情仇

光有 load_plugin_textdomain() 还不够,你还需要准备翻译文件。 翻译文件分为两种:

  • .po 文件: 人类可读的文本文件,包含了原始字符串和对应的翻译。 你需要编辑这个文件,填写翻译内容。
  • .mo 文件: 机器可读的二进制文件,是 .po 文件的编译版本。 WordPress 实际上加载的是 .mo 文件。

你可以使用 Poedit 等工具来创建和编辑 .po 文件,然后将其编译成 .mo 文件。

翻译函数的妙用:__(), _e(), _x(), _ex()

有了翻译文件,你还需要在你的插件代码中使用翻译函数来标记需要翻译的字符串。 常用的翻译函数有:

  • __(): 返回翻译后的字符串。
  • _e(): 直接输出翻译后的字符串。
  • _x(): 带有上下文的翻译,用于区分相同字符串在不同语境下的翻译。
  • _ex(): 带有上下文的翻译,并直接输出翻译后的字符串。

例子:

<?php

// 使用 __() 函数
$translated_string = __( 'Hello, world!', 'my-awesome-plugin' );
echo $translated_string;

// 使用 _e() 函数
_e( 'Welcome to my plugin!', 'my-awesome-plugin' );

// 使用 _x() 函数
$translated_string = _x( 'Post', 'noun', 'my-awesome-plugin' ); // 作为名词的 Post
echo $translated_string;

$translated_string = _x( 'Post', 'verb', 'my-awesome-plugin' ); // 作为动词的 Post
echo $translated_string;

// 使用 _ex() 函数
_ex( 'Add New', 'button label', 'my-awesome-plugin' );

?>

表格总结:load_plugin_textdomain() 的关键要素

要素 说明
文本域 (Domain) 插件的唯一标识符,用于区分不同插件的翻译文本。 必须和插件头部信息中的 Text Domain 一致。
翻译文件 (.mo) 包含了翻译后的字符串的二进制文件。
语言环境 (Locale) 表示特定语言和地区的代码,例如 zh_CN (简体中文), en_US (美国英语)。
翻译函数 __(), _e(), _x(), _ex() 等,用于标记需要翻译的字符串。
加载时机 建议在 plugins_loaded 钩子中调用 load_plugin_textdomain(),确保 WordPress 已经准备好加载翻译文件。
文件存放位置 WordPress 会按顺序在以下目录中查找 .mo 文件: /wp-content/languages/plugins/, /wp-content/plugins/<plugin_path>/, WPMU_PLUGIN_DIR/<plugin_path>/。 建议将翻译文件放在插件的 languages 目录下。

常见问题解答 (FAQ)

  • Q: 为什么我的翻译不起作用?

    • A: 检查你的文本域是否正确。 确保它和插件头部信息中的 Text Domain 以及 load_plugin_textdomain() 的第一个参数一致。
    • A: 检查你的 .mo 文件是否存在,并且文件名是否正确。 文件名必须是 textdomain-locale.mo 的格式,例如 my-awesome-plugin-zh_CN.mo
    • A: 检查你的翻译文件是否放在正确的位置。
    • A: 清除 WordPress 的缓存。
  • Q: 我可以使用在线翻译服务来翻译我的插件吗?

    • A: 当然可以! 但是,最好人工校对一下,确保翻译的质量。
  • Q: 我需要为每种语言都创建一个 .mo 文件吗?

    • A: 是的。 每种语言都需要一个单独的 .mo 文件。

高级技巧:动态文本域

有时候,你可能需要在运行时动态地改变文本域。 例如,你可能想根据用户的设置加载不同的翻译文件。 你可以使用 plugin_locale 过滤器来实现这个目标:

<?php

add_filter( 'plugin_locale', 'my_awesome_plugin_dynamic_locale', 10, 2 );

function my_awesome_plugin_dynamic_locale( $locale, $domain ) {
    if ( 'my-awesome-plugin' === $domain ) {
        // 根据用户设置获取语言代码
        $user_language = get_user_meta( get_current_user_id(), 'my_plugin_language', true );

        if ( ! empty( $user_language ) ) {
            return $user_language;
        }
    }

    return $locale;
}

这段代码会根据用户的 my_plugin_language 元数据来动态地改变语言环境。

总结:让你的插件说遍全世界

load_plugin_textdomain() 是 WordPress 插件国际化的基石。 掌握了它的用法,你就能轻松地让你的插件支持多种语言,走向世界,服务全球人民。 记住,国际化不仅仅是翻译字符串,更是一种对不同文化和语言的尊重。 希望今天的讲座能帮助你更好地理解和使用 load_plugin_textdomain() 函数。 下次再见!

发表回复

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