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

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

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.

Do not use variable names as the text domain for gettext functions. For example, don't do this:__('Translate me.', $text_domain);

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 commentsIn 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.

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

After WordPress-4.6, we can translate our plugins directly at translate.wordpress.org, plugins translated via translate.wordpress.org don't need load_plugin_textdomain(). If we don't want to add load_plugin_textdomain() to our plugin, we have to specify Requires least: to 4.6 in readme.txt.

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

We offer WordPress Themes and Plugins Custom Development Services

This site has long undertaken WordPress themes, plugins, WooCommerce-based store mall development business. We have 10 years of experience in WordPress development, if you want to Developing Websites with WordPress, please contact WeChat: iwillhappy1314 or email: amos@wpcio.com for inquiries.

发表回复

Your email address will not be published. 必填项已用 * 标注

*