各位观众,大家好!我是今天的主讲人,很高兴能和大家一起聊聊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() 函数的逻辑大致如下:
- 检查
$l10n中是否存在该文本域的翻译数据。 如果不存在,则直接返回原始字符串。 - 如果存在,则在
$l10n[$domain]中查找与$text匹配的翻译条目。 - 如果找到匹配的翻译条目,则返回翻译后的字符串。
- 如果没有找到匹配的翻译条目,则返回原始字符串。
可以用伪代码描述一下:
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.mo 和 my-theme-zh_CN.po。
总结:_e() 和 __() 的工作流程
现在,让我们来总结一下 _e() 和 __() 的工作流程:
- 调用
_e()或__()函数,传入要翻译的字符串和文本域。 _e()和__()函数调用translate()函数。translate()函数检查$l10n中是否存在该文本域的翻译数据。- 如果存在,则在
$l10n[$domain]中查找与$text匹配的翻译条目。 - 如果找到匹配的翻译条目,则返回翻译后的字符串。
- 如果
_e()函数被调用,则输出翻译后的字符串。 - 如果
__()函数被调用,则返回翻译后的字符串。
可以用一个表格总结一下各个关键函数的作用:
| 函数名 | 作用 |
|---|---|
_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 代码。记住,让你的代码走向世界,让更多的人使用你的作品!
感谢大家的收听! 如果有什么问题,欢迎提问!