WordPress源码深度解析之:`WordPress`的`i18n`:`_e()`和`__()`的底层工作原理。

各位观众,大家好!我是今天的主讲人,很高兴能和大家一起聊聊WordPress的i18n,尤其是_e()__()这两个函数的底层实现。别担心,我们不会像啃骨头一样分析源码,而是会像剥洋葱一样,一层一层地揭开它们的神秘面纱。准备好了吗?Let’s dive in!

开场白:为什么要关注_e()__()

在WordPress的世界里,国际化(i18n)和本地化(L10n)是至关重要的。它们让你的主题和插件能够被世界各地的用户使用,而不用修改任何代码。而_e()__(),就像是i18n/L10n的基石,几乎每个WordPress项目都会用到它们。

_e()用于输出翻译后的字符串,而__()用于返回翻译后的字符串。虽然它们看起来很简单,但背后却隐藏着一套复杂的机制。理解这些机制,能帮助你更好地编写可翻译的代码,并解决一些潜在的i18n问题。

第一层:_e()__()的基本用法

首先,让我们快速回顾一下_e()__()的基本用法。

  • _e( string $text, string $domain = 'default' ):输出翻译后的字符串。

    • $text:需要翻译的字符串。
    • $domain:文本域,用于区分不同的翻译文件。通常是你的主题或插件的slug。

    例如:

    _e( 'Hello, world!', 'my-theme' );
  • __( string $text, string $domain = 'default' ):返回翻译后的字符串。

    • $text:需要翻译的字符串。
    • $domain:文本域,用于区分不同的翻译文件。

    例如:

    $translated_text = __( 'Goodbye, world!', 'my-theme' );
    echo $translated_text;

简单来说,_e()相当于 echo __( ... )

第二层:load_textdomain()函数:翻译文件的载入

要让_e()__()正常工作,首先需要加载相应的翻译文件(.mo 文件)。这个任务通常由 load_textdomain() 函数完成。

load_textdomain( string $domain, string $mofile ):加载一个文本域的翻译文件。

  • $domain:文本域,与_e()__()$domain参数一致。
  • $mofile:翻译文件的完整路径。

WordPress在启动时会尝试加载一些默认的翻译文件,比如WordPress核心的翻译文件。而你的主题或插件,通常需要在自己的代码中调用load_textdomain()来加载自己的翻译文件。

例如,在你的主题的functions.php文件中:

function my_theme_load_textdomain() {
    load_theme_textdomain( 'my-theme', get_template_directory() . '/languages' );
}
add_action( 'after_setup_theme', 'my_theme_load_textdomain' );

这里的 load_theme_textdomain() 函数实际上是对 load_textdomain() 的一个封装,它会自动帮你拼装翻译文件的路径。

重要的是,load_textdomain() 会将翻译文件加载到全局变量 $l10n 中。 $l10n 是一个关联数组,它的键是文本域,值是 Translation_Entry 对象的一个集合,每一个对象代表一个翻译条目。

第三层:$l10n:翻译的“大本营”

$l10n 是一个全局变量,它存储了所有已加载的翻译文件的数据。 它的结构大致如下:

global $l10n;

$l10n = array(
    'my-theme' => array( // 文本域
        'Hello, world!' => '你好,世界!', // 原始字符串 => 翻译后的字符串
        'Goodbye, world!' => '再见,世界!',
        // ... 更多翻译条目
    ),
    'default' => array( // WordPress核心的文本域
        'Read more' => '阅读更多',
        // ...
    ),
    // ... 更多文本域
);

当调用 _e()__() 时,WordPress会首先检查 $l10n 中是否存在对应文本域的翻译。如果存在,则返回翻译后的字符串;否则,返回原始字符串。

第四层:translate() 函数:真正的翻译引擎

_e()__() 最终都会调用 translate() 函数来进行翻译。

translate( string $text, string $domain = 'default' ):根据指定的文本域翻译字符串。

  • $text:需要翻译的字符串。
  • $domain:文本域。

translate() 函数的逻辑大致如下:

  1. 检查 $l10n 中是否存在该文本域的翻译数据。 如果不存在,则直接返回原始字符串。
  2. 如果存在,则在 $l10n[$domain] 中查找与 $text 匹配的翻译条目。
  3. 如果找到匹配的翻译条目,则返回翻译后的字符串。
  4. 如果没有找到匹配的翻译条目,则返回原始字符串。

可以用伪代码描述一下:

function translate(text, domain):
  if domain not in l10n:
    return text
  if text in l10n[domain]:
    return l10n[domain][text]
  else:
    return text

第五层:Translation_Entry 类:翻译条目的数据结构

Translation_Entry 类是用来表示单个翻译条目的。它包含原始字符串、翻译后的字符串、以及一些其他的元数据。

虽然你通常不需要直接操作 Translation_Entry 对象,但了解它的结构有助于你更好地理解翻译文件的格式和 WordPress 的翻译机制。

Translation_Entry 类的属性大致如下:

  • singular:原始字符串(单数形式)。
  • plural:原始字符串(复数形式,仅用于复数形式的翻译)。
  • translations:翻译后的字符串数组。
  • context:上下文信息,用于区分相同的字符串在不同语境下的翻译。

第六层:复数形式的翻译:_n()_nx()

除了单数形式的翻译,WordPress还支持复数形式的翻译。 这需要用到 _n()_nx() 函数。

  • _n( string $singular, string $plural, int $number, string $domain = 'default' ):根据数量选择合适的翻译字符串。

    • $singular:单数形式的字符串。
    • $plural:复数形式的字符串。
    • $number:数量。
    • $domain:文本域。
  • _nx( string $singular, string $plural, int $number, string $context, string $domain = 'default' ):根据数量和上下文选择合适的翻译字符串。

    • $singular:单数形式的字符串。
    • $plural:复数形式的字符串。
    • $number:数量。
    • $context:上下文信息。
    • $domain:文本域。

复数形式的翻译比较复杂,因为它涉及到不同语言的复数规则。不同的语言可能有不同的复数形式,例如英语只有单数和复数两种形式,而一些斯拉夫语言则有多种复数形式。

WordPress 使用 gettext 的复数形式规则来处理复数形式的翻译。 gettext 的复数形式规则是一个表达式,用于根据数量选择合适的翻译字符串。

第七层:sprintf():字符串格式化

在翻译过程中,经常需要将一些变量插入到翻译后的字符串中。 这时就需要用到 sprintf() 函数。

sprintf( string $format, mixed ...$args ):根据格式化字符串和参数生成字符串。

例如:

$count = 10;
$message = sprintf( __( 'You have %d new messages.', 'my-theme' ), $count );
echo $message; // 输出:You have 10 new messages.

在使用 sprintf() 时,需要注意格式化字符串中的占位符类型,例如 %d 表示整数,%s 表示字符串,%f 表示浮点数。

第八层:esc_html__()esc_attr__():安全输出

在输出翻译后的字符串时,为了防止 XSS 攻击,需要对字符串进行转义。 WordPress 提供了 esc_html__()esc_attr__() 函数来完成这个任务。

  • esc_html__( string $text, string $domain = 'default' ):对翻译后的字符串进行 HTML 转义。

  • esc_attr__( string $text, string $domain = 'default' ):对翻译后的字符串进行 HTML 属性转义。

例如:

echo esc_html__( 'Hello, world!', 'my-theme' ); // 输出:Hello, world! (经过 HTML 转义)

echo '<input type="text" value="' . esc_attr__( 'Enter your name', 'my-theme' ) . '">'; // 输出:<input type="text" value="Enter your name" (经过 HTML 属性转义)>

第九层:文本域的命名规范

选择一个好的文本域名称非常重要,因为它关系到翻译文件的组织和加载。 通常,文本域应该与你的主题或插件的 slug 相同。

例如,如果你的主题的 slug 是 my-theme,那么你的文本域也应该命名为 my-theme

第十层:翻译文件的组织

翻译文件通常保存在 languages 目录下,该目录位于你的主题或插件的根目录下。

翻译文件的命名规范是 [textdomain]-[locale].mo[textdomain]-[locale].po

  • [textdomain]:文本域。
  • [locale]:语言区域代码,例如 zh_CN 表示简体中文,en_US 表示美式英语。

例如,如果你的主题的文本域是 my-theme,并且你想提供简体中文翻译,那么你的翻译文件应该命名为 my-theme-zh_CN.momy-theme-zh_CN.po

总结:_e()__() 的工作流程

现在,让我们来总结一下 _e()__() 的工作流程:

  1. 调用 _e()__() 函数,传入要翻译的字符串和文本域。
  2. _e()__() 函数调用 translate() 函数。
  3. translate() 函数检查 $l10n 中是否存在该文本域的翻译数据。
  4. 如果存在,则在 $l10n[$domain] 中查找与 $text 匹配的翻译条目。
  5. 如果找到匹配的翻译条目,则返回翻译后的字符串。
  6. 如果 _e() 函数被调用,则输出翻译后的字符串。
  7. 如果 __() 函数被调用,则返回翻译后的字符串。

可以用一个表格总结一下各个关键函数的作用:

函数名 作用
_e() 输出翻译后的字符串。
__() 返回翻译后的字符串。
load_textdomain() 加载一个文本域的翻译文件。
translate() 根据指定的文本域翻译字符串。
_n() 根据数量选择合适的翻译字符串 (单复数)。
_nx() 根据数量和上下文选择合适的翻译字符串(单复数)。
sprintf() 根据格式化字符串和参数生成字符串(用于在翻译字符串中插入变量)。
esc_html__() 对翻译后的字符串进行 HTML 转义,用于在 HTML 中安全输出。
esc_attr__() 对翻译后的字符串进行 HTML 属性转义,用于在 HTML 属性中安全输出。
$l10n 全局变量,存储所有已加载的翻译文件的数据。

一些建议和最佳实践

  • 始终使用 _e()__() 函数来输出和返回可翻译的字符串。
  • 选择一个好的文本域名称,并保持一致。
  • 将翻译文件保存在 languages 目录下。
  • 使用 sprintf() 函数来格式化字符串,并在翻译文件中提供相应的翻译。
  • 使用 esc_html__()esc_attr__() 函数来安全输出翻译后的字符串。
  • 使用合适的翻译工具来创建和编辑翻译文件。 (例如 Poedit)
  • 测试你的主题和插件的翻译功能,确保一切正常工作。

总结

希望通过这次讲座,你对 WordPress 的 i18n 机制有了更深入的了解。 虽然看起来有点复杂,但是只要掌握了基本概念和函数,你就能轻松地编写出可翻译的 WordPress 代码。记住,让你的代码走向世界,让更多的人使用你的作品!

感谢大家的收听! 如果有什么问题,欢迎提问!

发表回复

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