WordPress Plugin Development Tutorial Manual - Internationalization and Localization
internalization
What is internationalization?
Internationalization is a process of plugin development, after internationalization, the plugin can be easily translated into other languages, internationalization is usually abbreviated as i18n (Internationalization in the first letter i and the last letter n in the middle of 18 letters).
Why Internationalization
WordPress is used all over the world, and in many countries where the main language is not English, the characters in WordPress plugins need to be encoded in a special way so that they can be easily translated into other languages. As developers, it is impossible for us to provide localization services for all users. After the plugin is internationalized, translators using various languages can provide localized translations for the plugin or theme without modifying the code.
For further information on internationalization you can read "How to internationalize our plugin articles".
Resources
- Video: i18n: Preparing Your WordPress Theme for the World
- Video: On Internationalization: Plugins and themes for the whole worldSlides
- Video: Big in Japan: A Guide for Themes and Internationalization
- Video: Lost in Translation-i18n and WordPress
- Internationalizing And Localizing Your WordPress Theme
- Internationalization: You're probably doing it wrong
- More Internationalization Fun
- Use wp_localize_script, It Is Awesome
- Understanding _n_noop()
- Language Packs 101 - Prepwork
- Translating WordPress Plugins and Themes: Don't Get Clever
- How to load theme and plugin translations
localization
What is localization?
Localization is the process of translating a plugin into various local languages after it has been internationalized. The abbreviation for localization is l10n (there are 10 letters between the first letter l and the last letter n in localization).
local exchange of documents
POT (Portable Object Template) file
This text contains the original strings from the plugin (in English), below is a snippet from a POT file.
#: plugin-name.php:123
msgid Page Title
msgid
PO (Portable Object) Documentation
Each translator will use the POT file to translate the msgstr part into their own language. The result of the translation is a PO file, which has the same format as the POT file, but with translated strings and some specific headers, one for each language.
MO (Machine Object) file
Each translated PO file is eventually compiled by the msgfmt tool into an MO file, which is a machine-readable binary file used by the gettext function (the machine doesn't care about PO and POT files). Depending on the needs of the application, different modules can use their own MO files (e.g. the WordPress kernel, plugins, themes all have their own MO files), with the modules being distinguished from each other by a text domain.
Generate POT file
A POT file is a file that we need to give to our translators so that they can translate based on this file, POT and PO files can be easily converted by simply changing the file name. It is often a good practice to include the POT file in the plugin so that translators can use it directly for translation. There are several ways to generate a POT file for our plugin.
Plugin Catalog Management Tool
If our plugin is hosted in the WordPress.org plugin directoryOpen the "Admin" page of the plugin, then in the "Generate POT file" area, click the 'Continue' button.
Then click the Get POT button to download the POT file.
WordPress i18n Tool
If our plugin is not in the WordPress plugin directory, we can check out SVN's WordPress Trunk directory (see Using Subversion (Article). We need to check out the entire backbone because wordpress-i18n tool Using WordPress core code to generate POT files. Before running the following commands, we need to install the gettext (GNU internationalization utility) tool and PHP.
Open the command line and switch to the language folder of the plugin.
cd plugin-name/languages
Then, use the following command to generate the POT file
php path/to/makepot.php wp-plugin path/to/your-plugin-directory
Using Poedit Software
When translating plugins, we can use Poedit software, which is an open-soft translation tool that supports all major operating systems. The free version supports manual scanning of all source code using the Gettext function. Professional version can also help us scan WordPress plug-ins and generate POT files, when saving, Poedit will automatically help us compile MO files, the file is not necessary, we can delete this file. If you don't like to buy the Pro version, you can download a blank POT file, put the blank POT file into the language folder, and then click the "Update" button in Poedit to scan the plugin files to update the POT file.The interface of Poedit is as follows.
Grunt mission
If we've used Grunt before, there are a few Grunt tasks that can help us create a POT file. grunt-wp-i18n respond in singing grunt-potTo use these tools we need to install node.js. Then install the Grunt tool in the plugins directory. Installation is done from the command line. Here's aGrunt.js and package.json template, we can put it in the root directory of the plugin, and then we can generate the POT file using a simple command.
Translation of PO documents
There are many ways to translate PO files.
We can translate it directly in a text editor. For example, the untranslated PO file fragment is as follows:
#: plugin-name.php:123
msgid Page Title
msgid
When translating, we can just type the translation directly into the center of the quotes in msgstr, as follows:
#: plugin-name.php:123
msgid Page Title
msgid Page Title
We can also use Poedit software to open PO files for direct translation.
In addition to translating PO files locally, we can also use some online translation services. The whole idea is to upload the POT file and then authorize users or translators to translate our plugin. This method keeps our translations always up-to-date and avoids duplicate translations.
François-Xavier Bénard is running. WP-TranslationsIt's a community where "translators meet developers". It's in Transifex Running on it, you can submit translation projects as a developer for translators to translate, or translate existing plugins or themes into your own language.
Below are other tools that can be used to translate POT documents online.
We can even use a WordPress plugin to do the translation.
The translated file will be saved as my-plugin-{locale}.mo
file, locale is the language code/country code we defined in wp-config.php, for example, the locale for Simplified Chinese is zh_CN, in the above example, the text field is my-plugin, then the PO and MO files for Simplified Chinese should be my-plugin-zh_CN.po and my-plugin-zh- CN.mo. For more information about the locale and country code, please refer to the following link. For more information about languages and country codes, please refer to theInstall WordPress in your languageThe
Generation of MO files
via the command line
We use msgfmt to generate MO files. msgfmt is part of the Gettext toolkit. Otherwise, a typical use of the msgfmt command is as follows:
msgfmt -o filename.mo filename.po
If we have PO files to generate for many languages, we can use the bash command to do it in bulk.
# Find PO files, process each with msgfmt and rename the result to MO
for file in `find . -name *.po` ; do msgfmt -o ${file/.po/.mo} $file ; done
Using Poedit
Poedit software integrates msgfmt, which allows us to generate MO files, and we can enable or disable this feature in the software settings.
Grunt commands
There is a grunt-po2mo It can help us to convert all PO files.
Tips for Good Translation
Don't translate directly, translate meaningfully
Every language has different language habits and structures. When translating to Chinese, we should not do the translation according to the English language structure, we should extract the meaning expressed in English and then express it in natural Chinese. Don't use direct copy of the translation result of machine translation tools, machine translation is not yet developed to the extent that it can take care of our language habits.
Try to keep the same translation style
Every statement can be informal or formal, too formal a tone can distance the user, too informal a tone can make the user feel undisciplined. WordPress is used to tend to keep a polite informal tone, when translating, we can use a tone that feels the same.
Don't use slang or dialect words
Some slang or dialect terms are understood by fewer people when they leave a certain place. In WordPress, try not to use such terms for translation, because WordPress is an internationalized platform and is used by people all over the world. If there are terms that are difficult to translate, leaving them as they are will make them easier for users to understand. For example, pingback, trackback or feed in WordPress.
Reference to localized translations of other themes or plugins
If we are new to WordPress localization, or if we are not sure about the localized terms, we can refer to the localized translations of WordPress core, or other good themes and plugins to see how they translate some fields.
Using Localization Files
We can put the localization files into the plugin directory, and since WordPress 3.7, we can also extract the translation files and put them into the wp-content/languanges directory of WordPress, the latter prevents the custom translation files from being overwritten when the plugin is updated.
Starting from WordPress 4.0, we can change the language in "General Settings", if you don't see your desired language in the options, it means the language file is not downloaded. For example, if we want to switch to French, we can do it through the following steps.
- Define the language code constant WPLANG to be used in the wp-config.php file, e.g., if we need to switch to French, add define ('WPLANG', 'fr_FR ').
- Then open wp-admin/options-general.php or "Settings" -> "General"
- In the "Site Language" drop-down option, select the language we want.
- Development wp-admin/update-core.php
- Click "Update Translation"
- WordPress will download the language file for us.
Resources
- Creating a POT file for a theme or plugin
- How to Internationalize WordPress Plugins
- Translate WordPress Themes
- Blank WordPress POT file
- Enhanced WordPress i18n Tools
- How to quickly update translations
- GitHub/Transifex Collaborative Workflow
- Gist: Complete localization of Grunt tasks
- WordPress.tv Tags. i18n, internationalization respond in singing translation
How to internationalize the plug-in
In order to make the characters in our program translatable to other languages, we need to put the original characters into a special function.
Gettext function
WordPress uses i18n's gettext library and tools for internationalization, and if we see the _() function in our code, that function is using the native PHP Gettext compliant translation functionality. In WordPress, we should use the __() function defined by WordPress, and if we want to get more extensive and deeper Gettext functionality, we recommend reading the Gettext manual.
text field
A text may appear in more than one theme or plugin, and we distinguish the origin of these texts using a text field, which is a unique identifier used to ensure that WordPress can distinguish between all translations loaded. This mechanism enhances portability and allows for better integration with existing WordPress tools. The plugin's text field must be the same as the plugin's slug. If our plugin's main file is the my-plugin.php file, or if it is contained in a folder named my-plugin.php, the text field should be my-plugin. If our plugin is hosted at wordpress.org, the text field must be the plugin URL ( wordpress.org/plugins/), and the text field must use English dashes instead of underscores.
String example:
__( 'String (text to be internationalized)', 'text-domain' ).
In the actual code, we need to change "text-domain" to our plugin slug.
The text field also needs to be added to the plugin header, so that WordPress will use it to internationalize metadata in case the plugin is disabled. The text field should be used in conjunction with the Load Text Field is the same as the one used in Examples are shown below:
/*
* Plugin Name: My Plugin
* Author: Plugin Author
* Text Domain: my-plugin
*/
Text Field Path
We need to set a Text Domain Path so that WordPress knows where to find translations when the plugin is deactivated, the Text Domain Path is only useful if the plugin uses a separate languages folder. For example, if the .mo file is located in the plugin's languages folder, the Domain Path should be written as "/languages", and the first character of the path must be a /, which represents the plugin's root directory. An example is shown below:
/*
* Plugin Name: My Plugin
* Author: Plugin Author
* Text Domain: my-plugin
* Domain Path: /languages
*/
base string
When internationalizing WordPress, we use most of the __() function to return the translation of the argument:
__( 'Blog Options', 'my-plugin' ).
Another one that is more commonly used is _e()
function, used to directly input the translation of the argument, is equivalent to a shortcut to the code below.
echo __( 'WordPress is the best!', 'my-plugin' );
It can be written directly:
_e( 'WordPress is the best!', 'my-plugin' );
variant
If we need to use variables in strings, we need to use placeholders instead of variables. As in the following code:
echo 'Your city is $city.'
When internationalizing, we need to use the printf
respond in singing sprintf
to enter strings with variables. as follows:
printf(
/* translators: %s: Name of a city */
__( 'Your city is %s.', 'my-plugin' ),
$city
);
Note that the above strings can be translated only if they do not contain placeholders. The placeholder %s remains unchanged when translated, and the string it represents remains unchanged when displayed on the page.
If we have more than one placeholder in our string, we can use theparameter exchangeIn this case, single quotes must be used; double quotes would treat $s as an s variable, as exemplified below.
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
).
In the above code, the number in the middle of % and $ represents the position of the following variables, %1$s corresponds to $city, %2$s corresponds to $zipcode, sometimes we need to display the following variable $zipcode in the front, in this case, we can just interact with the position of %1$s and %2$s in the translation template, as follows in the translation template, as follows:
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
);
Caution! The code below is incorrect.
// This is incorrect do not use.
_e( Your city is $city., 'my-plugin' );
The translation string is extracted from the source code, so the translator will see the source string Your city is $city. when translating.
In the application, if the translation function _e() can't find the corresponding translation of the source string, it will use the source string directly when displaying. For example, if "Your city is London" is not translated by the translator, the gettext function will return this string directly.
complex number (math.)
basic plural
If our string in the number is plural is changed. For example, in English the One comment
respond in singing Two comments
In other languages, there may be more plural forms. When internationalizing and localizing WordPress, we can use the_n() function.
printf(
_n(
'%s comment',
'%s comments',
get_comments_number(),
'my-plugin'
),
number_format_i18n( get_comments_number() )
).
The _n() function takes 4 arguments:
- singular - the singular form of a string (note that in some languages it is possible to represent a character as a number, so '%s item' should be written as 'One item')
- plural - plural form of a string
- count - the number of objects, this determines whether to return the singular or plural form (in some languages, there are more than two forms)
- text domain - the text domain of the plugin
The function determines whether the return value is in singular or plural form, depending on the number specified by the function.
plural form of postponement
First, we need to use the _n_noop()
maybe _nx_noop()
Sets the plural string.
$comments_plural = _n_noop(
'%s comments.',
'%s comments.'
);
Later in the code, we can use the translate_nooped_plural()
functions are all in strings.
printf(
translate_nooped_plural(
$comments_plural,
get_comments_number(),
'my-plugin'
),
number_format_i18n( get_comments_number() )
).
Contextual disambiguation
Sometimes a string may be used in more than one context, and even though the string is the same, they express different meanings and the corresponding translations in other languages are different. For example, the word Post can be used either as a verb "Click here to post comment" or as a noun "Edit the post", in which case we should Use _x()
maybe _ex()
functions, which are similar to the __()
respond in singing _e()
, but with an additional context parameter. As follows:
_x( 'Post', 'noun', 'my-plugin' ); _x( 'Post', 'noun', 'my-plugin' )
_x( 'Post', 'verb', 'my-plugin' ).
This context parameter will be displayed as an annotation to the translator, and when the translator translates, he/she will choose the appropriate translation of this paper according to this annotation.
respond in singing __()
, _x()
Likewise, echo _x can be written directly as _ex(), and the above example can be written directly as when outputting directly:
_ex( 'Post', 'noun', 'my-plugin' );
_ex( 'Post', 'verb', 'my-plugin' ).
Translation notes
Sometimes, translators will not understand the meaning of certain strings, we need to add a description in the code to help translators understand, the way to add a translation description is to add a PHP line comment in front of the translation string, the comment starts with the character "translators:", the example is as follows:
/* translators: draft saved date format, see http://php.net/date */
$saved_date_format = __( 'g:i:s a' );
Translation comments can also be used to interpret placeholders in strings, as follows:
/* 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> addressed %2$s bugs.' ).
line break
When a string needs to be marked on a separate line, use the newline character \n, not \r, which is problematic for line breaks in Gettext.
empty string
Empty strings are internally reserved characters in Gettext, so don't try to internationalize an empty string, and you don't need to, because even if you translate it, the user will see the same effect. If we have a valid use case for internationalizing an empty string, then context can be added at the same time to help the translator and to maintain harmony with the Gettext system.
Processing JavaScript files
JavaScript runs on the front-end, so we don't need to use Gettext to internationalize JavaScript files; if you do, use the wp_localize_script()
to add translation strings to the frontend, and then internationalize the JavaScript file using JavaScript's internationalization methods.
escape character
It's good to escape all strings so that translators don't have a chance to run malicious code.WordPress provides a number of escaping functions that work in conjunction with internationalization.
basic function
Translated and escaped functions
Strings used for HTML tag attributes must be escaped for internationalization, use the following function to ensure that these characters are escaped for internationalization.
- esc_html__()
- esc_html_e()
- esc_html_x()
- esc_attr__()
- esc_attr_e()
- esc_attr_x()
Date and number functions
Best Practices for Writing Strings
Here are a few best practices for writing internationalized strings
- Use formal British style - try not to use slang and abbreviations.
- Use whole sentences - In most languages, word order is different than in English.
- Merge related sentences, but don't include the text of the entire page in one string.
-
Do not leave leading or trailing gaps in translatable phrases.
- Assuming that the length of the string is doubled when translated
- Avoid infrequent tags and infrequent control characters - Don't include tags around text
-
Don't put unnecessary HTML tags into translated strings!
- Do not include URLs in characters unless versions in other languages are available.
- Add variables as placeholders to a string, just as placeholders change position in some languages.
- Use format strings instead of string concatenation - Translate phrases instead of words
- Strings that are the same in different locations need to be translated only one, using the same words and symbols, which can reduce the number of strings and reduce the translator's workload for example. E.g.:
__( 'Posts:', 'my-plugin' );
respond in singing__( 'Posts', 'my-plugin' ).
Load text field with string
We must add the text field to the __()
, _e()
respond in singing __n()
etc. in the internationalization function, otherwise our translation won't work. E.g.:__( 'Post' )
owe it to __( 'Post', 'my-theme' )
If the strings in the plugin are also used in WordPress core (e.g. "Settings", we need to add our own text fields to the internationalization function as well), otherwise our strings will become untranslated if the WordPress core strings change.
Constantly adding text fields while writing code can be burdensome, and we can automate that operation:
- If our plugin is located in the WordPress.org plugin directory, go to the "Administration" page and scroll to "Add Domain to Gettext Calls".
- Upload the file to which the text field needs to be added.
- Then click on "Get domainified file".
Alternatively, we can add it locally by using the following method.
- downloading
add-textdomain.php
file to the folder where we want to add the text field. - Run the following command php add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php to generate a new file with a text domain added to it
- If we want to put add-textdomain.php in a different folder, you just need to define the location in the command. php \path\to\add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php
-
If you do not want to output a new file, use this command. php add-textdomain.php -i my-plugin my-plugin.php
-
To change multiple files in a directory, pass the directory to the script. php add-textdomain.php -i my-plugin my-plugin-directory
When finished, the text field will be added to the end of all gettext functions. If there is an existing text field, the above command will not replace it.
Load Text Field
Finally, we need to pass theload_plugin_textdomain() To load the translated MO file, this function loads {text-domain}-{locale}.mo from our specified plugin folder. locale is the country code of the WordPress setting, for more information on languages and country codes, seeInstall WordPress in your languageThe
In the above code example, the text field is my-plugin, so the MO and PO files in Simplified Chinese should be named my-plugin-zh_CN.mo and my-plugin-zh_CN.po. The code to load the translated files is as follows:
function my_plugin_load_plugin_textdomain() {
load_plugin_textdomain( 'my-plugin', FALSE, basename( dirname( __FILE__ ) ) . '/languages/' );
}
add_action( 'plugins_loaded', 'my_plugin_load_plugin_textdomain' ); }
language pack
If you're interested in language packs and how to import them into translate.wordpress.org, please read the Meta Handbook page on translationThe
Internationalization of security issues
Security is often overlooked when internationalizing WordPress, and there are some important things to mention here.
Check for spam and other malicious strings
When translators submit localized information to us, check to make sure that their translations don't contain spammy ad messages and other malicious strings, which we can translate into our native language using Google Translate, and then compare the original strings to the translated ones.
Escaping Internationalized Strings
We can't trust that all developers will just add normal translated text, and if some developers have bad intentions, they can add destructive JavaScript or other code. To prevent this, we need to treat internationalized strings like any other user input.
If we need to enter a string, we should escape it as we type.
It's not safe:
<?php _e( 'The REST API content endpoints were added in WordPress 4.7.', 'your-text-domain' ); ?>
Safe:
<?php esc_html_e( 'The REST API content endpoints were added in WordPress 4.7.', 'your-text-domain' ); ?>
Alternatively, some developers choose to rely on translation validation mechanisms rather than escaping to avoid this problem, an example of which is giving the WordPress Polyglotes team the ability to use the translate.wordpress.org of the editor role. This will ensure that any translations submitted by untrusted contributors have been verified by trusted translators before being accepted.
Using placeholders for URLs
Do not include URLs in internationalized strings, as unsuspecting translators may modify them to point to other URLs. The correct approach is to use the printf() or sprintf() placeholders.
Unsafe practices:
<?php _e(
'Please <a href=https://wordpress.org/support/register.php>
register for a WordPress.org account</a>.',
'your-text-domain'
); ?>
Safe practices:
<?php printf(
__(
'Please <a href=%s>register for a WordPress.org account</a>.',
'your-text-domain'
),
'https://wordpress.org/support/register.php'
); ?>
Compile your own MO files
Translators usually send us the translated MO file along with the PO file, and we should discard their MO file and compile one ourselves. Because MO files are binary, we can't see if the MO file they sent was compiled from the same PO file or from a PO file containing spam and other malicious strings.
Binary files generated with Poedit will overwrite the headers in the .po file, so it is best to compile using the command line.
msgfmt -cv -o /path/to/output.mo /path/to/input.po