各位观众,晚上好!我是今天的主讲人,很高兴能和大家一起探讨 WordPress 翻译机制中的一颗小星星,但能量巨大的 _nx()
函数。
今天的主题是“探究 WordPress _nx()
函数的源码:如何处理带有上下文的单复数翻译”。咱们的目标是:不仅要知其然,还要知其所以然,最后还能用得上。准备好了吗? Let’s dive in!
第一部分:_nx()
函数的身世之谜
首先,让我们先来认识一下 _nx()
函数。它位于 WordPress 的国际化(I18n)和本地化(L10n)体系的核心位置,负责处理带有上下文信息的单复数翻译。
为什么要用 _nx()
?
你可能会问,已经有 __()
,_e()
,_n()
这些函数了,为啥还要搞个 _nx()
出来? 原因是:
- 上下文的重要性: 很多词语在不同的语境下含义不同。比如 "Post" 可以是 "文章" (名词) 也可以是 "发布" (动词)。
- 单复数的复杂性: 不同语言的单复数规则千差万别,英语里 1 是单数,大于 1 都是复数。但有些语言(例如波兰语)区分 1、2-4 和 5+ 三种情况。
_nx()
函数就是为了解决这些问题而生的。它允许开发者提供上下文信息,以便翻译者能够根据语境进行准确翻译,并且能够根据不同数量选择正确的单复数形式。
_nx()
函数的签名
_nx()
函数的签名如下:
_nx( string $singular, string $plural, int $number, string $context, string $domain = 'default' ): string
参数解释:
参数 | 类型 | 描述 |
---|---|---|
$singular |
string |
单数形式的字符串。 |
$plural |
string |
复数形式的字符串。 |
$number |
int |
用于决定使用单数还是复数形式的数字。 |
$context |
string |
上下文信息,用于区分相同字符串在不同语境下的含义。 |
$domain |
string |
文本域(Text Domain),用于指定翻译文件。默认为 default 。可以理解为翻译文件的命名空间。 |
一个简单的例子
$comment_count = 5;
$message = _nx(
'%s comment',
'%s comments',
$comment_count,
'comments title', // 上下文
'my-theme' // 文本域
);
printf( $message, number_format_i18n( $comment_count ) ); // 输出:5 comments
在这个例子中:
'%s comment'
是单数形式。'%s comments'
是复数形式。$comment_count
是评论的数量。'comments title'
是上下文,表明这是评论标题相关的翻译。'my-theme'
是文本域,指定从my-theme
的翻译文件中查找翻译。
第二部分:源码剖析:_nx()
的内部运作
现在,让我们深入 _nx()
函数的源码,看看它是如何工作的。
首先,我们要找到 _nx()
函数的定义。 它通常位于 wp-includes/l10n.php
文件中。 但实际上 _nx()
函数只是一个门面 (Facade),它最终会调用 translate_nooped_plural()
函数。
function _nx( $singular, $plural, $number, $context, $domain = 'default' ) {
return translate_nooped_plural( new WP_I18n_Translator_Entry( $singular, $plural, $context, $domain ), $number, $domain );
}
我们可以看到 _nx()
函数创建了一个 WP_I18n_Translator_Entry
对象,然后调用了 translate_nooped_plural()
函数。
WP_I18n_Translator_Entry
类
这个类只是一个简单的数据结构,用于存储单数形式、复数形式、上下文和文本域。
translate_nooped_plural()
函数
这个函数才是真正的核心。 它的主要职责是:
- 构建唯一 ID: 基于单数形式、上下文和文本域,生成一个唯一的 ID,用于在翻译文件中查找翻译。
- 查找翻译: 在全局
$l10n
数组(存储已加载的翻译数据)中查找对应的翻译。 - 应用复数规则: 根据
$number
和当前语言的复数规则,选择正确的单数或复数形式。 - 返回翻译后的字符串: 如果找到翻译,则返回翻译后的字符串;否则,返回原始的单数或复数形式。
让我们逐步分析 translate_nooped_plural()
函数的代码片段 (简化版):
function translate_nooped_plural( $nooped_plural, $count, $domain ) {
global $l10n;
$singular = $nooped_plural->singular;
$plural = $nooped_plural->plural;
$context = $nooped_plural->context;
$key = $singular . "x04" . $context; // 构建唯一 ID
if ( isset( $l10n[ $domain ]->entries[ $key ] ) ) {
$entry = $l10n[ $domain ]->entries[ $key ];
// 获取复数形式的索引
$index = $l10n[ $domain ]->get_plural_form( $count );
// 返回翻译后的字符串
if ( isset( $entry->translations[ $index ] ) ) {
return $entry->translations[ $index ];
}
}
// 如果没有找到翻译,则返回原始的单数或复数形式
if ( 1 == $count ) {
return $singular;
} else {
return $plural;
}
}
代码解读:
- 构建唯一 ID:
$key = $singular . "x04" . $context;
这行代码使用单数形式和上下文信息,用一个特殊的字符x04
(EOT, End of Transmission) 连接起来,作为唯一的 ID。 这个字符很少在普通文本中使用,可以有效地分隔单数形式和上下文。 - 查找翻译:
if ( isset( $l10n[ $domain ]->entries[ $key ] ) )
这行代码检查$l10n
数组中是否存在指定文本域和唯一 ID 的翻译条目。$l10n
数组是一个全局变量,存储了已加载的所有翻译数据。 - 获取复数形式的索引:
$index = $l10n[ $domain ]->get_plural_form( $count );
这行代码调用$l10n[ $domain ]
对象的get_plural_form()
方法,根据$count
和当前语言的复数规则,计算出应该使用哪个复数形式的索引。 不同语言的复数规则存储在$l10n[ $domain ]->plural_forms
属性中。 - 返回翻译后的字符串:
if ( isset( $entry->translations[ $index ] ) ) { return $entry->translations[ $index ]; }
这行代码检查翻译条目中是否存在指定索引的翻译,如果存在,则返回翻译后的字符串。 - 如果没有找到翻译: 如果
$l10n
数组中没有找到对应的翻译,则根据$count
的值,返回原始的单数或复数形式。
get_plural_form()
方法
get_plural_form()
方法是根据当前语言的复数形式规则,来确定应该使用哪个索引的翻译。 每个语言都有自己的复数形式规则,这些规则定义了如何根据数量选择正确的单复数形式。 这些规则通常存储在 .po
文件头部的 Plural-Forms
字段中。
例如,英语的复数规则是:
Plural-Forms: nplurals=2; plural=(n != 1);
这意味着英语有两种复数形式 (nplurals=2),当 n 不等于 1 时使用复数形式 (plural=(n != 1))。
而波兰语的复数规则是:
Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
这意味着波兰语有三种复数形式 (nplurals=3),并且使用一个复杂的公式来确定应该使用哪种形式。
get_plural_form()
方法会解析这些规则,并根据 $count
的值计算出正确的索引。
第三部分:实战演练:如何在你的主题或插件中使用 _nx()
理论讲了这么多,现在让我们看看如何在实际项目中使用 _nx()
函数。
步骤 1:标记需要翻译的字符串
在你的主题或插件代码中,找到需要翻译的字符串,并使用 _nx()
函数进行标记。 确保提供正确的上下文信息和文本域。
例如:
$book_count = 10;
$message = _nx(
'%s book',
'%s books',
$book_count,
'book count', // 上下文
'my-plugin' // 文本域
);
printf( $message, number_format_i18n( $book_count ) );
步骤 2:生成 .pot
文件
使用 WordPress 提供的工具(例如 wp-cli
或 Poedit)从你的代码中提取所有被标记的字符串,并生成一个 .pot
(Portable Object Template) 文件。 .pot
文件是一个模板文件,包含了所有需要翻译的字符串。
使用 wp-cli
的命令如下:
wp i18n make-pot . languages/my-plugin.pot --slug=my-plugin
步骤 3:创建 .po
和 .mo
文件
将 .pot
文件复制一份,并将其重命名为 .po
(Portable Object) 文件,例如 my-plugin-zh_CN.po
。 .po
文件是实际的翻译文件,包含了原始字符串和翻译后的字符串。
使用 Poedit 打开 .po
文件,并进行翻译。 翻译完成后,保存 .po
文件。 Poedit 会自动生成一个对应的 .mo
(Machine Object) 文件。 .mo
文件是机器可读的二进制文件,WordPress 在运行时会加载 .mo
文件,并使用其中的翻译数据。
步骤 4:加载文本域
在你的主题或插件的主文件中,使用 load_plugin_textdomain()
或 load_theme_textdomain()
函数加载文本域。
例如:
function my_plugin_load_textdomain() {
load_plugin_textdomain( 'my-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
}
add_action( 'plugins_loaded', 'my_plugin_load_textdomain' );
注意事项:
- 上下文的选择: 选择有意义的上下文信息,以便翻译者能够理解字符串的含义。
- 文本域的命名: 使用唯一的文本域名,避免与其他主题或插件冲突。
- 翻译文件的位置: 将
.po
和.mo
文件放在正确的目录下,通常是languages
目录。 - 代码注释: 在代码中添加注释,解释字符串的含义和用途,方便翻译者理解。
第四部分:高级技巧:_nx()
的进阶用法
除了基本的用法之外,_nx()
函数还有一些高级技巧,可以帮助你更好地处理复杂的翻译场景。
使用占位符
_nx()
函数支持使用占位符,例如 %s
、%d
、%f
等,以便在翻译后的字符串中插入变量。
例如:
$item_count = 3;
$message = _nx(
'You have %d item in your cart.',
'You have %d items in your cart.',
$item_count,
'cart items',
'my-theme'
);
printf( $message, $item_count ); // 输出:You have 3 items in your cart.
使用 HTML 标签
如果需要在翻译后的字符串中使用 HTML 标签,可以使用 wp_kses_post()
函数对字符串进行过滤,以防止 XSS 攻击。
例如:
$username = 'John Doe';
$message = _nx(
'Welcome, <strong>%s</strong>!',
'Welcome, <strong>%s</strong>!',
1,
'welcome message',
'my-theme'
);
printf( wp_kses_post( $message ), esc_html( $username ) ); // 输出:Welcome, <strong>John Doe</strong>!
处理复杂的复数规则
如果你的语言有复杂的复数规则,可以自定义 get_plural_form()
方法,以满足需求。 但是,这需要对 WordPress 的翻译机制有深入的了解。
第五部分:常见问题与解答
Q:为什么我的翻译没有生效?
A:可能的原因包括:
- 文本域没有正确加载。
.mo
文件没有放在正确的目录下。.po
文件中的翻译错误。- 缓存问题。
Q:如何调试翻译问题?
A:可以使用 WP_DEBUG
模式,并启用 SCRIPT_DEBUG
和 WP_DEBUG_LOG
,以查看错误信息。 还可以使用 Poedit 的调试功能,检查 .po
文件中的翻译是否正确。
Q:如何贡献翻译?
A:可以加入 WordPress 的翻译团队,参与 WordPress 核心、主题和插件的翻译工作。
第六部分:总结
今天我们深入探讨了 WordPress 的 _nx()
函数,从它的身世之谜到源码剖析,再到实战演练和高级技巧。 希望通过今天的讲座,你能够更好地理解和使用 _nx()
函数,为你的主题和插件提供更好的国际化支持。
记住,良好的国际化和本地化是构建优秀 WordPress 作品的关键。 感谢大家的收听! 如果有任何问题,欢迎提问。下次再见!