各位观众,大家好!我是今天的主讲人,很高兴能和大家一起聊聊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 代码。记住,让你的代码走向世界,让更多的人使用你的作品!
感谢大家的收听! 如果有什么问题,欢迎提问!