咳咳,各位同学,欢迎来到今天的“WordPress 源码解剖系列”讲座。今天我们要拆解的是 WordPress 中一个非常实用,但也容易被忽略的函数:_n()
。 别看它名字短小精悍,它可是负责处理单复数形式翻译的大功臣。准备好,我们这就深入它的“骨髓”一探究竟!
一、 什么是 _n()
? 为什么要用它?
在很多语言中,名词的单复数形式会影响到句子结构,尤其是在涉及到数量的时候。 比如:
- 英文: "1 comment", "2 comments"
- 法文: "1 commentaire", "2 commentaires"
- 俄文: "1 комментарий", "2 комментария", "5 комментариев" (俄语更复杂,还有变格)
直接硬编码这些变化既不优雅,也不方便维护。 WordPress 为了解决这个问题,引入了 _n()
函数。 它的作用就是:根据给定的数字,选择合适的单数或复数形式的翻译文本。
_n()
函数的基本语法如下:
_n( $single, $plural, $number, $domain = 'default' );
$single
: 单数形式的文本。$plural
: 复数形式的文本。$number
: 用于判断单复数的数字。$domain
: 翻译文本所属的文本域(text domain)。 默认是 ‘default’。
举个栗子:
printf( _n( '%s comment', '%s comments', $comment_count ), number_format_i18n( $comment_count ) );
这段代码会根据 $comment_count
的值,输出 "1 comment" 或 "2 comments" 等。 number_format_i18n()
函数是为了格式化数字,让其符合当前语言环境的数字格式。
二、 _n()
函数的源码剖析
好了,废话不多说,让我们直接进入 _n()
的源码。 _n()
函数实际上是对 translate_plural()
函数的一个简单封装。 在 wp-includes/l10n.php
文件中,你可以找到它的定义:
function _n( $single, $plural, $number, $domain = 'default' ) {
return translate_plural( $single, $plural, $number, $domain );
}
可以看到,_n()
函数只是简单地调用了 translate_plural()
函数。 所以,真正的逻辑都在 translate_plural()
函数中。
三、 translate_plural()
函数的源码解析
translate_plural()
函数才是真正干活的家伙。 它的源码稍微复杂一些,我们一步一步来分析。
function translate_plural( $singular, $plural, $number, $domain = 'default' ) {
global $l10n;
$domain = sanitize_key( $domain );
if ( isset( $l10n[ $domain ] ) ) {
$translations = $l10n[ $domain ];
} else {
$translations = null;
}
if ( isset( $translations->headers['Plural-Forms'] ) ) {
$plural_forms = $translations->headers['Plural-Forms'];
} else {
$plural_forms = false;
}
if ( ! is_null( $translations ) && false !== $plural_forms ) {
$index = _n_noop_plural( $singular, $plural, $number, $domain );
$index = $translations->select_plural_form( $number );
$translation = $translations->translate_plural( $singular, $plural, $number, $index );
if ( false !== $translation ) {
return $translation;
}
}
if ( 1 == $number ) {
return $singular;
} else {
return $plural;
}
}
我们来逐行解释:
-
global $l10n;
: 声明全局变量$l10n
。$l10n
是一个全局数组,存储了所有已加载的翻译数据。 它的键是文本域(text domain),值是一个Translation_Entry
对象,包含了翻译文本和元数据。 -
$domain = sanitize_key( $domain );
: 对文本域进行安全过滤。 确保文本域的格式是合法的。 -
if ( isset( $l10n[ $domain ] ) ) { ... }
: 检查指定的文本域是否已经加载了翻译数据。 如果已经加载,就从$l10n
数组中获取Translation_Entry
对象。 -
if ( isset( $translations->headers['Plural-Forms'] ) ) { ... }
: 检查Translation_Entry
对象中是否包含了 "Plural-Forms" 头信息。 "Plural-Forms" 头信息定义了如何根据数字选择单复数形式。 这是最关键的部分。 -
if ( ! is_null( $translations ) && false !== $plural_forms ) { ... }
: 如果翻译数据和 "Plural-Forms" 头信息都存在,就执行翻译逻辑。 -
$index = _n_noop_plural( $singular, $plural, $number, $domain );
: 这行代码看起来有点奇怪,但实际上什么也没做。_n_noop_plural()
函数只是为了让 POT 文件提取工具能够识别出需要翻译的单复数形式。 它返回的值并没有被真正使用。 -
$index = $translations->select_plural_form( $number );
: 这才是真正选择单复数形式索引的关键代码! 调用Translation_Entry
对象的select_plural_form()
方法,根据数字$number
和 "Plural-Forms" 头信息,计算出应该使用哪个翻译文本。 -
$translation = $translations->translate_plural( $singular, $plural, $number, $index );
: 调用Translation_Entry
对象的translate_plural()
方法,根据索引$index
获取翻译文本。 -
if ( false !== $translation ) { return $translation; }
: 如果找到了翻译文本,就直接返回。 -
if ( 1 == $number ) { return $singular; } else { return $plural; }
: 如果没有找到翻译文本,就根据$number
的值,返回原始的单数或复数形式。 这是一个默认的 fallback 机制。
四、 "Plural-Forms" 头信息: 单复数形式选择的秘密
"Plural-Forms" 头信息是决定如何选择单复数形式的关键。 它定义了一个规则,用于根据数字计算出一个索引值。 这个索引值对应于翻译文本数组中的一个元素。
"Plural-Forms" 头信息的格式如下:
Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;
nplurals
: 指定了该语言有多少种复数形式。plural
: 定义了一个表达式,用于计算索引值。 表达式可以使用数字$n
作为变量。
举几个例子:
-
英文:
Plural-Forms: nplurals=2; plural=(n != 1);
nplurals=2
: 英文有两种复数形式(单数和复数)。plural=(n != 1)
: 如果$n
不等于 1,则索引值为 1(复数),否则索引值为 0(单数)。
-
法文:
Plural-Forms: nplurals=2; plural=(n > 1);
nplurals=2
: 法文有两种复数形式。plural=(n > 1)
: 如果$n
大于 1,则索引值为 1(复数),否则索引值为 0(单数)。
-
俄文:
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
: 俄文有三种复数形式。plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)
: 这个表达式比较复杂,但它定义了俄语的复数规则。
五、 Translation_Entry
类的 select_plural_form()
方法
Translation_Entry
类的 select_plural_form()
方法负责解析 "Plural-Forms" 头信息,并根据数字计算出索引值。
这个方法的源码比较复杂,涉及到编译和执行 PHP 代码,这里就不深入讲解了。 简单来说,它会:
- 从 "Plural-Forms" 头信息中提取
nplurals
和plural
表达式。 - 将
plural
表达式编译成 PHP 代码。 - 使用给定的数字
$number
执行 PHP 代码,得到索引值。
六、 总结: _n()
函数的工作流程
现在,我们来总结一下 _n()
函数的工作流程:
_n()
函数接收单数形式、复数形式、数字和文本域作为参数。_n()
函数调用translate_plural()
函数。translate_plural()
函数从全局变量$l10n
中获取指定文本域的翻译数据。translate_plural()
函数检查翻译数据中是否包含 "Plural-Forms" 头信息。- 如果包含 "Plural-Forms" 头信息,
translate_plural()
函数调用Translation_Entry
对象的select_plural_form()
方法,根据数字和 "Plural-Forms" 头信息计算出索引值。 translate_plural()
函数根据索引值获取翻译文本。translate_plural()
函数返回翻译文本,或者原始的单数或复数形式。
可以用下面的表格来概括这个流程:
步骤 | 函数/方法 | 描述 |
---|---|---|
1 | _n() |
接收参数,调用 translate_plural() |
2 | translate_plural() |
获取翻译数据,检查 "Plural-Forms" |
3 | Translation_Entry->select_plural_form() |
根据 "Plural-Forms" 和数字计算索引 |
4 | Translation_Entry->translate_plural() |
根据索引获取翻译文本 |
5 | translate_plural() |
返回翻译文本或默认的单复数形式 |
七、 如何使用 _n()
函数
明白了 _n()
函数的原理,我们来看看如何正确使用它。
-
准备好单数和复数形式的文本。 确保单数和复数形式的文本都正确无误。
-
使用
_n()
函数进行翻译。 将单数形式、复数形式和数字传递给_n()
函数。 -
使用
printf()
函数格式化输出。_n()
函数返回的是翻译文本,你需要使用printf()
函数将其格式化输出。
例如:
$post_count = get_the_modified_date( 'Ymd' ); //假设你取到的是一个文章数量
printf( _n( 'One post modified today', '%s posts modified today', $post_count ), number_format_i18n( $post_count ) );
八、 总结与思考
_n()
函数是 WordPress 国际化和本地化 (i18n/l10n) 的一个重要组成部分。 它巧妙地利用 "Plural-Forms" 头信息,实现了对各种语言的单复数形式的灵活处理。
通过深入分析 _n()
函数的源码,我们不仅了解了它的工作原理,也学习了 WordPress 如何处理翻译数据和国际化逻辑。 这对于我们更好地理解 WordPress 源码,以及开发高质量的 WordPress 插件和主题都非常有帮助。
当然,_n()
函数只是 WordPress i18n/l10n 系统中的冰山一角。 还有很多其他的函数和机制,比如 __()
, _e()
, _x()
, load_textdomain()
等,都值得我们深入学习和研究。
好了,今天的讲座就到这里。 希望大家有所收获! 如果有什么问题,欢迎提问。 下课!