首页 / 插件开发手册 / 国际化 / 如何国际化你的插件

如何国际化你的插件

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

 

Gettext简介

WordPress为i18n使用gettext库和工具,但不是直接使用:有一组专门为实现字符串翻译而创建的特殊函数。下面列出了这些函数,这些是您应该在插件中使用的函数。

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

 

文本域(Text Domain)

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

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

文本域名必须使用破折号而不是下划线、小写、并且没有空格。

文本域也需要添加到插件header中。WordPress使用它来国际化插件元数据,即使插件被禁用。文本域应与加载文本域时使用的域相同。

Header 示例:

/* 
 * Plugin Name: My Plugin
 * Author: Plugin Author
 * Text Domain: my-plugin
 */

Note:再次,将“my-plugin”更改为插件的slug。

Note:自从WordPress 4.6开始,Text Domain header是可选的,因为它必须与插件slug相同。包含它没有害处,但不是必需的。

 

域路径

域路径定义插件翻译的位置。这有一些用途,尤其是当插件被禁用时,WordPress知道在哪里可以找到翻译。这默认为插件所在的文件夹。

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

Header 示例:

/*
 * Plugin Name: My Plugin
 * Author: Plugin Author
 * Text Domain: my-plugin
 * Domain Path: /languages
 */

Note:如果插件位于官方WordPress插件目录中,Domain Path header可以省略。

 

基本字符串

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

__( 'Blog Options', 'my-plugin' );

Warning:不要对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函数族。特别有用的是printfsprintf。以下是正确的解决方案:

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' );

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

注意,与__()类似,_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上的插件

Note:自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文件

查看通用API手册国际化javascript部分,了解如何正确加载翻译文件。还有古腾堡插件文档页面

 

语言包

如果您对语言包以及如何导入translate.wordpress.org的工作方式感兴趣,请阅读关于翻译的Meta手册页面

另请参阅Polyglots手册中的插件/主题作者指南,以获取项目翻译。