# 如何国际化你的插件
为了使字符串在应用程序中可翻译,您必须将原始字符串包装在对一组特殊函数之一的调用中。这些函数统称为“gettext”。
#### Gettext简介
WordPress 使用i18n 的[gettext](http://www.gnu.org/software/gettext/)库和工具,但不是直接使用:有一组专门为了启用字符串翻译而创建的特殊函数。下面列出了这些功能。这些是您应该在插件中使用的函数。
笔记:要更深入地了解 gettext,请阅读[gettext 在线手册](http://www.gnu.org/software/gettext/manual/html_node/)
#### 文本域
使用*文本域*来表示属于您的插件的所有文本。文本域是一个唯一标识符,以确保 WordPress 可以区分所有加载的翻译。这提高了可移植性,并且可以更好地与现有的 WordPress 工具配合使用。
文本域必须与`slug`插件的文本域匹配。如果您的插件是一个名为的单个文件`my-plugin.php`或它包含在一个名为的文件夹中,则`my-plugin`域名必须是`my-plugin`. 如果您的插件托管在 wordpress.org 上,则它必须是插件 URL 的 slug 部分 ( `wordpress.org/plugins/`)。
文本域名必须使用破折号而不是下划线、小写且不含空格。
文本域还需要添加到插件标头中。即使插件被禁用,WordPress也会使用它来国际化您的插件元数据。[文本域应与加载文本域](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#loading-text-domain)时使用的文本域相同。
**标头示例:**
```php
/*
* Plugin Name: My Plugin
* Author: Plugin Author
* Text Domain: my-plugin
*/
```
笔记:再次将“my-plugin”更改为插件的名称。
笔记:从 WordPress 4.6 开始,`Text Domain`标头是可选的,因为它必须与插件 slug 相同。包含它没有什么坏处,但不是必需的。
#### 域路径
域路径定义插件翻译的位置。这有一些用途,特别是即使插件被禁用,WordPress 也知道在哪里可以找到翻译。默认为您的插件所在的文件夹。
例如,如果翻译位于`languages`插件内调用的文件夹中,则域路径`/languages`必须用第一个斜杠编写:
**标头示例:**
```php
/*
* Plugin Name: My Plugin
* Author: Plugin Author
* Text Domain: my-plugin
* Domain Path: /languages
*/
```
笔记:`Domain Path`如果插件位于官方 WordPress 插件目录中,则可以省略标头 。
#### 基本字符串
对于基本字符串(即没有占位符或复数的字符串),请使用`__()`. 它返回其参数的翻译:
```php
__( 'Blog Options', 'my-plugin' );
```
警告:不要对 gettext 函数的文本域部分使用变量名或常量。例如: 不要将此作为快捷方式:
```
__( '翻译我。' , $text_domain );
```
要回显检索到的翻译,请使用`_e()`. 所以不要写:
```php
echo __( 'WordPress is the best!', 'my-plugin' );
```
您可以使用:
```php
_e( 'WordPress is the best!', 'my-plugin' );
```
#### 变量
如果你有一个像下面这样的字符串怎么办:
```php
echo 'Your city is $city.'
```
在这种情况下,`$city`是一个变量,不应该是翻译的一部分。解决方案是使用变量的占位符以及`printf`函数族。尤其有帮助的是`printf`和`sprintf`。正确的解决方案如下所示:
```php
printf(
/* translators: %s: Name of a city */
__( 'Your city is %s.', 'my-plugin' ),
$city
);
```
请注意,这里用于翻译的字符串只是 template `"Your city is %s."`,它在源代码和运行时都是相同的。
另请注意,翻译人员会收到提示,以便他们了解占位符的上下文。
如果字符串中有多个占位符,建议您使用[参数交换](http://www.php.net/manual/en/function.sprintf.php#example-4829)。在这种情况下,`(')`字符串周围的单引号是强制性的,因为双引号`(")`会告诉 php 将 the 解释`$s`为`s`变量,这不是我们想要的。
```php
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前缀可以实现这种情况。因此可以写出一个翻译:
```php
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
);
```
**重要的!**下面的代码是不正确的:
```php
// 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()`函数。
```php
printf(
_n(
'%s comment',
'%s comments',
get_comments_number(),
'my-plugin'
),
number_format_i18n( get_comments_number() )
);
```
`_n()`接受 4 个参数:
- 单数 – 字符串的单数形式(请注意,在某些语言中,它可以用于除 1 之外的数字,因此`'%s item'`应该使用 代替`'One item'`)
- plural——字符串的复数形式
- count – 对象的数量,这将决定是否应返回单数或复数形式(有些语言具有远远超过 2 种形式)
- 文本域 – 插件文本域
函数的返回值是正确的翻译形式,对应于给定的计数。
请注意,某些语言对其他数字使用单数形式(例如 21、31 等,很像英语中的“21st”、“31st”)。如果您想对单数进行特殊处理,请特别检查:
```php
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()`。
```php
$comments_plural = _n_noop(
'%s comment.',
'%s comments.'
);
```
然后,在代码的稍后位置,您可以使用它`translate_nooped_plural()`来加载字符串。
```php
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()`or函数。`_ex()`它类似于`__()`and `_e()`,但它有一个附加参数 - 上下文:
```php
_x( 'Post', 'noun', 'my-plugin' );
_x( 'Post', 'verb', 'my-plugin' );
```
在这两种情况下使用此方法,我们将获得原始版本的注释字符串,但译者将看到两个翻译注释字符串,每个字符串都在不同的上下文中。
请注意,与 类似`__()`,`_x()`也有一个`echo`版本:`_ex()`。前面的例子可以写成:
```php
_ex( 'Post', 'noun', 'my-plugin' );
_ex( 'Post', 'verb', 'my-plugin' );
```
使用您认为可以增强可读性和易于编码的任何一种。
#### 描述
这样翻译人员就知道如何翻译字符串,就像`__( 'g:i:s a' )`您可以在源代码中添加澄清注释一样。它必须以单词开头`translators:`,并且是 gettext 调用之前的最后一个 PHP 注释。这是一个例子:
```php
/* translators: draft saved date format, see http://php.net/date */
$saved_date_format = __( 'g:i:s a' );
```
它还用于解释字符串中的占位符,例如`_n_noop( 'Version %1$s addressed %2$s bug.','Version %1$s addressed %2$s bugs.' )`.
```php
/* translators: 1: WordPress version number, 2: plural number of bugs. */
_n_noop( 'Version %1$s addressed %2$s bug.','Version %1$sstrong> addressed %2$s bugs.' );
```
#### 换行符
Gettext 不喜欢`r`可翻译字符串中的 (ASCII 代码:13),因此请避免使用它并改用它`n`。
#### 空字符串
空字符串保留供内部 Gettext 使用,您不得尝试国际化空字符串。它也没有任何意义,因为翻译者看不到任何上下文。
如果您有一个有效的用例来国际化空字符串,请添加上下文以帮助翻译人员并与 Gettext 系统保持一致。
#### 转义字符串
最好转义所有字符串,这样翻译器就无法运行恶意代码。有一些转义函数与国际化函数集成在一起。
#### 本地化功能
#### 基本功能
- [\_\_()](https://developer.wordpress.org/reference/functions/__/)
- [\_e()](https://developer.wordpress.org/reference/functions/_e/)
- [\_X()](https://developer.wordpress.org/reference/functions/_x/)
- [\_前任()](https://developer.wordpress.org/reference/functions/_ex/)
- [\_n()](https://developer.wordpress.org/reference/functions/_n/)
- [\_nx()](https://developer.wordpress.org/reference/functions/_nx/)
- [\_n\_noop()](https://developer.wordpress.org/reference/functions/_n_noop/)
- [\_nx\_noop()](https://developer.wordpress.org/reference/functions/_nx_noop/)
- [翻译\_nooped\_plural()](https://developer.wordpress.org/reference/functions/translate_nooped_plural/)
#### 翻译和转义功能
需要翻译并在 html 标记的属性中使用的字符串必须进行转义。
- [esc\_html\_\_()](https://developer.wordpress.org/reference/functions/esc_html__/)
- [esc\_html\_e()](https://developer.wordpress.org/reference/functions/esc_html_e/)
- [esc\_html\_x()](https://developer.wordpress.org/reference/functions/esc_html_x/)
- [esc\_attr\_\_()](https://developer.wordpress.org/reference/functions/esc_attr__/)
- [esc\_attr\_e()](https://developer.wordpress.org/reference/functions/esc_attr_e/)
- [esc\_attr\_x()](https://developer.wordpress.org/reference/functions/esc_attr_x/)
#### 日期和数字函数
- [number\_format\_i18n()](https://developer.wordpress.org/reference/functions/number_format_i18n/)
- [日期\_i18n()](https://developer.wordpress.org/reference/functions/date_i18n/)
#### 编写字符串的最佳实践
以下是编写字符串的最佳实践
- 使用得体的英语风格——尽量减少俚语和缩写。
- 使用整个句子——大多数语言的词序与英语不同。
- 在段落中拆分 - 合并相关句子,但不要在一个字符串中包含整页文本。
- 不要在可翻译短语中留下前导或尾随空格。
- 假设翻译后字符串的长度可以加倍
- 避免不寻常的标记和不寻常的控制字符 - 不要包含文本周围的标签
- 不要将不必要的 HTML 标记放入翻译后的字符串中
- 不要留下 URL 进行翻译,除非它们有其他语言的版本。
- 将变量作为占位符添加到字符串中,就像在某些语言中占位符会更改位置一样。
```php
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' );`
#### 将文本域添加到字符串
您必须将 Text 域作为参数添加到每个`__()`,`_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 核心中使用(例如“设置”),您仍然应该向它们添加您自己的文本域,否则如果核心字符串发生更改(这种情况发生),它们将变为未翻译。
如果在编写代码时不连续添加文本域,则手动添加文本域可能会成为一种负担,这就是为什么您可以自动执行此操作:
- 将脚本下载`add-textdomain.php`到您要添加文本域的文件所在的文件夹
- 在命令行中移动到文件所在的目录
- 运行以下命令创建一个添加了文本域的新文件:
```bash
php add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php
```
如果您希望将其放在`add-textdomain.php`不同的文件夹中,则只需在命令中定义位置即可。
```bash
php /path/to/add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php
```
如果您不想输出新文件,请使用此命令:
```php
php add-textdomain.php -i my-plugin my-plugin.php
```
如果要更改目录中的多个文件,还可以将目录传递给脚本:
```php
php add-textdomain.php -i my-plugin my-plugin-directory
```
完成后,文本域将被添加到文件中所有 gettext 调用的末尾。如果存在现有文本域,则不会被替换。
#### 加载文本域
可以使用 加载翻译`load_plugin_textdomain`,例如:
```php
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](https://translate.wordpress.org/)为优先级,因此通过translate.wordpress.org翻译的插件不再需要`load_plugin_textdomain()`。如果您不想添加`load_plugin_textdomain()`对插件的调用,则必须将`Requires at least:`readme.txt 中的字段设置为 4.6 或更高。
如果您仍然想加载自己的翻译而不是来自 translate 的翻译,则必须使用名为 的钩子过滤器`load_textdomain_mofile`。插件目录中的 .mo 文件
**示例,并将此代码插入到主插件文件中:**`/languages/`
```php
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 手册](https://developer.wordpress.org/apis/)的[国际化 javascript](https://developer.wordpress.org/apis/handbook/internationalization/#internationalizing-javascript)部分,了解如何正确加载翻译文件。还有[古腾堡插件文档页面](https://github.com/WordPress/gutenberg/blob/trunk/docs/how-to-guides/internationalization.md)。
#### 语言包
如果您对语言包以及如何导入[translate.wordpress.org](http://translate.wordpress.org/)感兴趣,请阅读[有关翻译的元手册页面](https://make.wordpress.org/meta/handbook/documentation/translations/)。
另请参阅[多语言手册中的插件/主题作者指南](https://make.wordpress.org/polyglots/handbook/plugin-theme-authors-guide/)以翻译您的项目。