为了使字符串在你的应用程序中可翻译,你必须将原始字符串包装在一组特殊函数之一的调用中。这些函数统称为“gettext。”

Gettext 简介

WordPress 使用 gettext 库和工具进行 i18n,但不是直接使用:有一组专门为此目的创建的函数用于启用字符串翻译。这些函数列在下面。这些是你应该在插件中使用的函数。

若要深入了解 gettext,请阅读 gettext 在线手册

文本域

使用 文本域 来标识属于你插件的所有文本。文本域是确保 WordPress 能够区分所有加载翻译的唯一标识符。这提高了可移植性,并且与现有的 WordPress 工具配合得更好。

文本域必须与插件的 slug 匹配。如果你的插件是一个名为 my-plugin.php 的单文件,或者它包含在一个名为 my-plugin 的文件夹中,则域名必须是 my-plugin。如果你的插件托管在 wordpress.org 上,它必须是你的插件 URL 的 slug 部分(wordpress.org/plugins/<slug>)。

文本域名称必须使用连字符而不是下划线,必须为小写,且不能有空格。

文本域还需要添加到插件头部。WordPress 使用它来国际化你的插件元数据,即使插件被禁用也是如此。文本域应与 加载文本域 时使用的相同。

头部示例:

/* 
 * Plugin Name: My Plugin
 * Author: Plugin Author
 * Text Domain: my-plugin
 */
再次,将“my-plugin”更改为你的插件的 slug。由于 WordPress 4.6,Text Domain 头部是可选的,因为它必须与插件 slug 相同。包含它没有害处,但不是必需的。

域路径

域路径定义了插件翻译的位置。这有几个用途,特别是这样 WordPress 即使在插件被禁用时也知道在哪里找到翻译。这默认为你的插件所在的文件夹。

例如,如果翻译位于你插件中名为 languages 的文件夹内,则域路径为 /languages,并且必须用第一个斜杠书写:

头部示例:

/*
 * Plugin Name: My Plugin
 * Author: Plugin Author
 * Text Domain: my-plugin
 * Domain Path: /languages
 */
如果插件在官方 WordPress 插件目录中,则可以省略 Domain Path 头部。

基本字符串

对于基本字符串(意味着没有占位符或复数的字符串),请使用 __()。它返回其参数的翻译:

__( 'Blog Options', 'my-plugin' );
不要使用变量名或常量作为 gettext 函数的文本域部分。例如:不要这样做作为捷径:
__( 'Translate me.' , $text_domain );

要回显检索到的翻译,请使用 _e()。因此,而不是编写:

echo __( 'WordPress is the best!', 'my-plugin' );

你可以使用:

_e( 'WordPress is the best!', 'my-plugin' );

变量

如果你有一个如下所示的字符串怎么办:

echo 'Your city is $city.'

在这种情况下,$city 是一个变量,不应是翻译的一部分。解决方案是为变量使用占位符,以及 printf 函数族。特别有帮助的是 printf 和 sprintf。以下是正确解决方案的样子:

printf(
	/* translators: %s: Name of a city */
	__( 'Your city is %s.', 'my-plugin' ),
	$city
);

注意,在这里翻译的字符串只是模板 "Your city is %s.",它在源和运行时是相同的。

还要注意有一个提示给翻译人员,以便他们知道占位符的上下文。

如果字符串中有多个占位符,建议使用 参数交换。在这种情况下,字符串周围的单引号 (') 是强制性的,因为双引号 (") 会告诉 php 将 $s 解释为 s 变量,这不是我们想要的。

printf(
	/* translators: 1: Name of a city 2: ZIP code */
	__( 'Your city is %1$s, and your zip code is %2$s.', 'my-plugin' ),
	$city,
	$zipcode
);

在这里,邮政编码显示在城市名称之后。在某些语言中,以相反的顺序显示邮政编码和城市可能更合适。使用上面的示例中的 %s 前缀,允许出现这种情况。因此可以编写翻译:

printf(
	/* translators: 1: Name of a city 2: ZIP code */
	__( 'Your zip code is %2$s, and your city is %1$s.', 'my-plugin' ),
	$city,
	$zipcode
);

重要! 以下代码是不正确的:

// This is incorrect do not use.
_e( "Your city is $city.", 'my-plugin' );

翻译的字符串是从源代码中提取的,因此翻译人员将获得此短语进行翻译:"Your city is $city."。

然而,在应用程序中 _e 将以类似 "Your city is London." 的参数调用,并且 gettext 找不到此内容的合适翻译,并将返回其参数:"Your city is London."。不幸的是,它没有正确翻译。

复数

基本复数化

如果你有一个当项目数量变化时变化的字符串,你需要一种方法在你的翻译中反映这一点。例如,在英语中你有 "One comment" 和 "Two comments"。在其他语言中,你可以有多个复数形式。要在 WordPress 中处理此问题,请使用 _n() 函数。

printf(
	_n(
		'%s comment',
		'%s comments',
		get_comments_number(),
		'my-plugin'
	),
	number_format_i18n( get_comments_number() )
);

_n() 接受 4 个参数:

  • singular – 字符串的单数形式(注意,在某些语言中它可以用于除一以外的数字,因此应使用 '%s item' 而不是 'One item')
  • plural – 字符串的复数形式
  • count – 对象的数量,这将决定应返回单数还是复数形式(有些语言有超过 2 种形式)
  • text domain – 插件的文本域

函数的返回值是正确翻译的形式,对应于给定的计数。

注意,某些语言对其他数字使用单数形式(例如 21、31 等,就像英语中的 '21st'、'31st')。如果你想特别处理单数,请专门检查:

if ( 1 === $count ) {
	printf( esc_html__( 'Last thing!', 'my-text-domain' ), $count );
} else {
	printf( esc_html( _n( '%d thing.', '%d things.', $count, 'my-text-domain' ) ), $count );
}

还要注意 $count 参数通常使用两次。首先将 $count 传递给 _n() 以确定使用哪个翻译字符串,然后将 $count 传递给 printf() 以将数字替换到翻译字符串中。

稍后复数化

你首先使用 _n_noop() 或 _nx_noop() 设置复数字符串。

$comments_plural = _n_noop(
	'%s comment.',
	'%s comments.'
);

然后在代码的稍后一点,你可以使用 translate_nooped_plural() 加载字符串。

printf(
	translate_nooped_plural(
		$comments_plural,
		get_comments_number(),
		'my-plugin'
	),
	number_format_i18n( get_comments_number() )
);

通过上下文消歧义

有时一个术语会在几种不同的语境中使用,虽然在英语中它是同一个词,但在其他语言中必须翻译成不同的形式。例如,单词 Post 既可以用作动词 "Click here to post your comment",也可以用作名词 "Edit this post"。在这种情况下,应使用 _x() 或 _ex() 函数。它与 __() 和 _e() 类似,但多了一个参数——语境:

_x( 'Post', 'noun', 'my-plugin' );
_x( 'Post', 'verb', 'my-plugin' );

在这两种情况下使用此方法,我们将得到原始版本的字符串 Comment,但翻译人员将看到两个 Comment 字符串用于翻译,每个处于不同的语境中。

请注意,与 __() 类似,_x() 也有一个 echo 版本:_ex()。前面的示例可以写成:

_ex( 'Post', 'noun', 'my-plugin' );
_ex( 'Post', 'verb', 'my-plugin' );

使用您觉得能提高可读性和编码便捷性的那个版本。

描述

为了让翻译人员知道如何翻译像 __( 'g:i:s a' ) 这样的字符串,您可以在源代码中添加说明性注释。它必须以单词 translators: 开头,并且必须是 gettext 调用之前的最后一个 PHP 注释。这里有一个示例:

/* translators: draft saved date format, see http://php.net/date */
$saved_date_format = __( 'g:i:s a' );

它也用于解释字符串中的占位符,例如 _n_noop( '<strong>Version %1$s</strong> addressed %2$s bug.','<strong>Version %1$s</strong> addressed %2$s bugs.' )。

/* translators: 1: WordPress version number, 2: plural number of bugs. */
_n_noop( '<strong>Version %1$s</strong> addressed %2$s bug.','<strong>Version %1$s</strong>strong> addressed %2$s bugs.' );

换行符

Gettext 不喜欢可翻译字符串中的 r(ASCII 代码:13),因此请避免使用它,而改用 n。

空字符串

空字符串保留供 Gettext 内部使用,您必须不要尝试对空字符串进行国际化。这也毫无意义,因为翻译人员将看不到任何语境。

如果您有有效的使用案例需要对空字符串进行国际化,请添加语境以帮助翻译人员并与 Gettext 系统和平共处。

转义字符串

转义所有字符串是很好的做法,这样翻译人员就无法运行恶意代码。有几个与国际化函数集成的转义函数。

本地化函数

基本函数

转译与转义函数

需要翻译并在 HTML 标签属性中使用的字符串必须被转义。

日期和数字函数

编写字符串的最佳实践

以下是编写字符串的最佳实践:

  • 使用得体的英语风格——尽量减少俚语和缩写。
  • 使用完整的句子——在大多数语言中,单词顺序与英语不同。
  • 按段落拆分——合并相关句子,但不要将一整页文本包含在一个字符串中。
  • 不要在可翻译短语中留下前导或尾随空白。
  • 假设翻译后的字符串长度会翻倍
  • 避免不寻常的标记和不寻常的控制字符——不要包含包围您文本的标签
  • 不要将不必要的 HTML 标记放入翻译后的字符串中
  • 除非它们可能有其他语言版本,否则不要留下 URL 供翻译。
  • 将变量作为占位符添加到字符串中,因为在某些语言中占位符的位置会改变。
printf(
	__( 'Search results for: %s', 'my-plugin' ),
	get_search_query()
);
  • 使用格式字符串而不是字符串连接——翻译短语而不是单词——printf( __( 'Your city is %1$s, and your zip code is %2$s.', 'my-plugin' ), $city, $zipcode ); 总是优于: __( 'Your city is ', 'my-plugin' ) . $city . __( ', and your zip code is ', 'my-plugin' ) . $zipcode;
  • 尽量使用相同的单词和相同的符号,以便不需要翻译多个字符串,例如 __( 'Posts:', 'my-plugin' ); 和 __( 'Posts', 'my-plugin' );

为字符串添加文本域

您必须将您的文本域作为参数添加到每个 __()、_e() 和 __n() gettext 调用中,否则翻译将无法工作。

示例:

  • __( 'Post' ) 应改为 __( 'Post', 'my-theme' )
  • _e( 'Post' ) 应改为 _e( 'Post', 'my-theme' )
  • _n( '%s post', '%s posts', $count ) 应改为 _n( '%s post', '%s posts', $count, 'my-theme' )

如果您的插件中有与 WordPress 核心也使用的字符串(例如“Settings”),您仍然应该为它们添加自己的文本域,否则如果核心字符串更改(这确实会发生),它们将变为未翻译。

手动添加文本域如果在编写代码时不持续进行可能会成为一种负担,因此您可以自动完成:

  • 下载 add-textdomain.php 脚本到您想要添加文本域的文件所在的文件夹
  • 在命令行中移动到文件所在的目录
  • 运行此命令以创建带有已添加文本域的新文件:
php add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php

如果您希望将 add-textdomain.php 放在不同的文件夹中,只需在命令中定义位置。

php /path/to/add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php

如果您不想输出新文件,请使用此命令:

php add-textdomain.php -i my-plugin my-plugin.php

如果您想更改目录中的多个文件,也可以将目录传递给脚本:

php add-textdomain.php -i my-plugin my-plugin-directory

完成后,文本域将被添加到文件中所有 gettext 调用的末尾。如果已存在文本域,则不会替换它。

加载文本域

可以使用 load_plugin_textdomain 加载翻译,例如:

add_action( 'init', 'wpdocs_load_textdomain' );

function wpdocs_load_textdomain() {
	load_plugin_textdomain( 'wpdocs_textdomain', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); 
}

WordPress.org 上的插件

自 WordPress 4.6 起,翻译现在将 translate.wordpress.org 作为首选,因此通过 translate.wordpress.org 进行翻译的插件不再需要 load_plugin_textdomain()。如果您不想在插件中添加 load_plugin_textdomain() 调用,则必须将 readme.txt 中的 Requires at least: 字段设置为 4.6 或更高版本。

如果您仍然想加载自己的翻译而不是来自 translate 的翻译,您将需要使用名为 load_textdomain_mofile 的钩子过滤器。
示例:在插件的 /languages/ 目录中有一个 .mo 文件,将此代码插入主插件文件中:

function my_plugin_load_my_own_textdomain( $mofile, $domain ) {
	if ( 'my-domain' === $domain && false !== strpos( $mofile, WP_LANG_DIR . '/plugins/' ) ) {
		$locale = apply_filters( 'plugin_locale', determine_locale(), $domain );
		$mofile = WP_PLUGIN_DIR . '/' . dirname( plugin_basename( __FILE__ ) ) . '/languages/' . $domain . '-' . $locale . '.mo';
	}
	return $mofile;
}
add_filter( 'load_textdomain_mofile', 'my_plugin_load_my_own_textdomain', 10, 2 );

处理 JavaScript 文件

请查看 Common APIs Handbook 中的 Internationalizing javascript 部分,以了解如何正确加载翻译文件。还有 Gutenburg 插件文档页面。

语言包

如果您对语言包以及导入到 translate.wordpress.org 的工作原理感兴趣,请阅读 Meta Handbook 关于翻译的页面。

此外,请参考 Polyglots Handbooks 中的插件/主题作者指南 来获取您的项目的翻译。