Home >CMS Tutorial >WordPress >WordPress i18n: Make Your Plugin Translation Ready
In a previous post, I covered the fundamentals of WordPress internationalization (abbreviated as i18n); how to install a localized version of WordPress and how an existing WordPress site can be easily converted to a localized version.
In this article, I’m going to walk you through the process of Internationalizing WordPress plugins. The process isn’t difficult and once the knowledge is acquired, you can have your WordPress plugins easily translated to other languages.
Over the years, developers tend to misconstrue the meaning of these terms – Internationalization and Localization.
It is worthy of note that Internationalization is often abbreviated as i18n (because there are 18 letters between the ‘i’ and the ‘n’) and Localization is abbreviated as l10n (because there are 10 letters between the ‘l’ and the ‘n’.)
The answer is simple; WordPress is used all over the world in many different languages. When plugins are internationalized, they attract a larger audience from other parts of the world who would obviously benefit by using the plugin in their own language.
As a developer, you may not have time to provide localized versions of your plugin because you do not speak other languages. However, when you internationalize your plugin, you leave the door open for others to create localization without necessarily modifying the source code.
Now that we are familiar with the concept of plugin internationalization and localization, let’s dive into the process of making plugins ready for translation.
The first step to take in making a plugin translatable is to include the translation headers among the plugin headers.
The translation headers are the Text Domain and the Domain Path.
The Text Domain is used to denote all text belonging to a plugin. It is a unique identifier which ensures WordPress can distinguish between all loaded translations. This increases portability and plays better with already existing WordPress tools.
The text domain must match the slug of the plugin. For example, if your plugin is a single file called sample-plugin.php or it is contained in a folder called sample-plugin the text domain should be sample-plugin.
The text domain name must use dashes and not underscores.
Remember I said the text domain must match with the plugin slug? That may not be true after all. I carried out a quick experiment with one of my plugins, instead of the plugin slug, I used a unique text and it worked without any problems.
Moral: ensure the Text Domain is unique so it doesn’t clash with that of other plugins.
The Domain Path is the folder WordPress will search for the .mo translation files.
By default, WordPress searches the plugin directory for the translation files to use. Having the translation file at the root folder of your plugin could disorganize your plugin structure.
If you wish to keep the translation files in a folder, for example; /languages, you need to inform WordPress about it using the Domain Path header.
Below is a typical header of a WordPress plugin including that of the translation.
<span><span><?php </span></span><span><span>/* </span></span><span><span> Plugin Name: Enable Shortcode and PHP in Text widget </span></span><span><span> Plugin URI: http://w3guy.com/shortcode-php-support-wordpress-text-widget/ </span></span><span><span> Description: Enable shortcode support and execute PHP in WordPress's Text Widget </span></span><span><span> Author: Agbonghama Collins </span></span><span><span> Version: 1.2 </span></span><span><span> Author URI: http://w3guy.com </span></span><span><span> Text Domain: espw-plugin </span></span><span><span> Domain Path: /languages/ </span></span><span><span> */</span></span>
Now, we would use the load_plugin_textdomain() function to tell WordPress to load a translation file if it exists for the user’s language.
Below is the function synopsis.
<span><span><?php load_plugin_textdomain( $domain, $abs_rel_path, $plugin_rel_path ) ?></span></span>
The first parameter $domain should be the text domain; the $abs_rel_path has been deprecated and should be set to false; finally, $plugin_rel_path is the relative path to translation files.
If translation MO files is in the plugin’s own directory, use as follows:
<span>load_plugin_textdomain( 'espw-plugin', false, dirname( plugin_basename( __FILE__ ) ) );</span>
If translation MO files is in the plugin’s languages subdirectory. Use as follows:
<span>load_plugin_textdomain( 'espw-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );</span>
You don’t just call the load_plugin_textdomain function, it should be called in your plugin as early as the plugins_loaded action like this:
<span>function load_plugin_textdomain() { </span> <span>load_plugin_textdomain( 'espw-plugin', FALSE, basename( dirname( __FILE__ ) ) . '/languages/' ); </span><span>} </span> <span>add_action( 'plugins_loaded', 'load_plugin_textdomain' );</span>
Now that the Text Domain and the Domain Path header is set, it’s time to learn how to internationalize a plugin.
This segment of the tutorial will be divided into the following:
Please note: The string espw-plugin will be use as text domain in this tutorial.
To make a string translatable in your plugin, wrap the original string in a __() function call as follows:
<span><span><?php </span></span><span><span>/* </span></span><span><span> Plugin Name: Enable Shortcode and PHP in Text widget </span></span><span><span> Plugin URI: http://w3guy.com/shortcode-php-support-wordpress-text-widget/ </span></span><span><span> Description: Enable shortcode support and execute PHP in WordPress's Text Widget </span></span><span><span> Author: Agbonghama Collins </span></span><span><span> Version: 1.2 </span></span><span><span> Author URI: http://w3guy.com </span></span><span><span> Text Domain: espw-plugin </span></span><span><span> Domain Path: /languages/ </span></span><span><span> */</span></span>
If you want to echo the string to the browser, instead of the echo language construct, use the _e function:
<span><span><?php load_plugin_textdomain( $domain, $abs_rel_path, $plugin_rel_path ) ?></span></span>
As PHP and WordPress developers, I assume you know what placeholders are. You can quickly skim through the sprintf and printf() PHP documentation for further information.
If you are using variables in strings like the example below, you should use placeholders.
<span>load_plugin_textdomain( 'espw-plugin', false, dirname( plugin_basename( __FILE__ ) ) );</span>
The right way is to use the printf()function as follows:
<span>load_plugin_textdomain( 'espw-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );</span>
Going through the code of some plugins hosted in the WordPress plugin repository, I do see things like this:
<span>function load_plugin_textdomain() { </span> <span>load_plugin_textdomain( 'espw-plugin', FALSE, basename( dirname( __FILE__ ) ) . '/languages/' ); </span><span>} </span> <span>add_action( 'plugins_loaded', 'load_plugin_textdomain' );</span>
<span>$text = __( 'Hello, SitePoint Readers!', 'espw-plugin' );</span>
Although the strings are now translatable, the PHP variable $city also becomes translatable.
This is a bad practice because a translator could mistakenly alter the variable or inject malicious code into the plugin code base, and this will ultimately make the plugin malfunction.
The sprintf function is similar to the printf in that they format string using placeholders while printf outputs a formatted string, the sprintf return the string.
Example: the following code assign a formatted string to the variable $text.
<span>_e( 'Hello, SitePoint Readers!', 'espw-plugin' );</span>
Including HTML in translatable strings depends on the context.
An example is a link (separated from text surrounding it):
<span>echo 'Your city is $city.'</span>Another example is a link in a paragraph (not separated from text surrounding it):
<span>printf( </span> <span>__( 'Your city is %s.', 'espw-plugin' ), </span> <span>$city </span><span>);</span>
A string that changes when the number of items changes can be internationalized using the _n() function.
This function accepts 4 arguments, namely:
Let’s see some examples to comprehend how the _n() function works.
In English you have "One comment" and "Two comments". In other languages you can have multiple plural forms.
The code below demonstrates how to handle such scenario using the _n() function.
<span>echo __('Your city is $city', 'espw-plugin');</span>
Code Explanation the code above consist of these three functions – printf, _n and number_format_i18n.
For easy assimilation, the function code will be dissected with each function component explained.
<span><span><?php </span></span><span><span>/* </span></span><span><span> Plugin Name: Enable Shortcode and PHP in Text widget </span></span><span><span> Plugin URI: http://w3guy.com/shortcode-php-support-wordpress-text-widget/ </span></span><span><span> Description: Enable shortcode support and execute PHP in WordPress's Text Widget </span></span><span><span> Author: Agbonghama Collins </span></span><span><span> Version: 1.2 </span></span><span><span> Author URI: http://w3guy.com </span></span><span><span> Text Domain: espw-plugin </span></span><span><span> Domain Path: /languages/ </span></span><span><span> */</span></span>
The first argument passed to the _n function is the text to be displayed when the number of comments is singular.
The second is the text displayed when the number of comments is greater than one.
The placeholder %s will contain the value of number_format_i18n( get_comments_number() ) which will be explained shortly.
The third argument get_comments_number() is assumed to be a function that returns the comment count.
If it returns 1, the first argument One comment get outputted by printf otherwise the second argument %s comments is returned if it is greater than 1.
Please note: The placeholder %s gets replaced by the integer returned by number_format_i18n( get_comments_number() ) which is the second argument passed to the printf function.
Finally, the fourth argument is the translation text domain.
The function number_format_i18n() converts the comment count to format based on the locale. See the documentation for more information.
Similar to the number_format_i18n() is the date_i18n that retrieves the date in localized format, based on timestamp.
Still on the _n() function, below is another demonstration of how the function works.
<span><span><?php load_plugin_textdomain( $domain, $abs_rel_path, $plugin_rel_path ) ?></span></span>
If the variable $count returns 1, the text We deleted one spam message will be displayed; but if it is greater than 1, We deleted %d spam messages will be displayed with the placeholder %d replaced by the integer value of $count.
Sometimes one term is used in several contexts, although it is one and the same word in English it has to be translated differently in other languages.
For example the word Post can be used both as a verb as in "Click here to post your comment" and as a noun "Edit this post".
In such cases the _x or _ex function should be used.
It is similar to __() and _e(), but it has an extra argument — $context.
<span>load_plugin_textdomain( 'espw-plugin', false, dirname( plugin_basename( __FILE__ ) ) );</span>
Using this method in both cases we will get the string Comment for the original version, but the translators will see two Comment strings for translation, each in the different context.
When the strings made translatable by the function _x() get parsed by a translation tool such as Poedit, the context argument provides a hint to the translator on the context the string/text was used.
In German, the Post as a noun is Beitrag while as a verb is verbuchen.
Below is a screenshot of Poedit translating the string Post to German with the context squared-bracketed.
While _x() retrieve translated string, _ex() displays it.
WordPress has a number of functions for validating and sanitizing data.
Among the list are functions for escaping translation texts – esc_html(), esc_html_e(), esc_html_x(), esc_attr(), esc_attr_e() and esc_attr_x(). You can get more information on each of these functions at WordPress Codex.
I don’t need to explain every one of these, but what they do is basically escape translatable texts.
One of the goals of WordPress is to make it easy for users across the world to publish content. As a plugin developer, you can help to further ease the publishing process for users when you internationalize your plugins.
The first part of this tutorial was essentially about everything you need to know about plugin i18n.
The concluding part will be a walk-through on how to make a plugin translation ready as well as learning how to localize a plugin to a new language.
I do hope you have learned something new from this tutorial.
Happy coding!
Making a WordPress plugin translation-ready is crucial for reaching a global audience. Not all WordPress users are English speakers. By internationalizing your plugin, you make it accessible to users who speak different languages, thereby increasing your user base. It also enhances user experience as users can interact with your plugin in their native language, making it more user-friendly.
WordPress i18n, or internationalization, works by making your plugin’s text strings translatable into other languages. This is achieved by wrapping these text strings in a special function that allows them to be translated. WordPress uses the GNU gettext localization framework for this purpose, which provides a set of tools for translating the text.
Making a WordPress plugin translation-ready involves several steps. First, you need to internationalize your plugin by wrapping all text strings in the __() or _e() function. Next, you need to create a .pot (Portable Object Template) file which contains all the translatable strings from your plugin. This file is used as a template for creating .po (Portable Object) and .mo (Machine Object) files, which contain the translated strings.
The __() and _e() functions are both used in WordPress i18n to make text strings translatable. The main difference between them is that __() returns the translated string, while _e() echoes or outputs the translated string directly. Therefore, you would use __() when you want to store the translated string in a variable, and _e() when you want to display the translated string to the user.
To translate your WordPress plugin into different languages, you need to create .po and .mo files for each language. These files contain the translated strings and are named according to the ISO-639 language code (e.g., en_US for English, fr_FR for French). You can use a tool like Poedit or Loco Translate to create and manage these translation files.
There are several tools available for WordPress i18n. Poedit and Loco Translate are popular tools for creating and managing translation files. GlotPress, a web-based translation tool, is also used by the WordPress community to translate WordPress core, plugins, and themes.
You can test your WordPress plugin for translation readiness by changing the language in your WordPress settings and checking if the plugin’s text strings are correctly translated. You can also use a tool like the WordPress i18n checker plugin, which checks your plugin for common i18n errors.
You can contribute to WordPress plugin translations through the WordPress Polyglots team. They are responsible for localizing WordPress into different languages. You can join the team and start translating plugins and themes into your native language.
.pot, .po, and .mo files play a crucial role in WordPress i18n. The .pot file is a template that contains all the translatable strings from your plugin. The .po file is a human-readable file that contains the translated strings, and the .mo file is a machine-readable file that is used by WordPress to display the translated strings.
Making a WordPress theme translation-ready involves a similar process to plugins. You need to internationalize your theme by wrapping all text strings in the __() or _e() function. Then, you need to create a .pot file and use it as a template for creating .po and .mo files for each language.
The above is the detailed content of WordPress i18n: Make Your Plugin Translation Ready. For more information, please follow other related articles on the PHP Chinese website!