
This is the third part of the series covering the essential elements to include in a professional plugin. Although it is more focused on how to translate a plugin in WordPress, this information is completely valid for translating a WordPress theme as well. Let us soak up a little knowledge.
Much of the content in this article has been extracted and translated from one place or another in the WordPress documentation, but not all of it. And frankly, I myself learned a great deal preparing this article.
Internationalisation
Internationalisation of a theme or plugin is the process by which the means are provided for it to be easily translated into another language. This process is also commonly called i18n as an abbreviation of that long name.
Localisation
We must be clear that internationalisation is not the same as localisation, since the latter is the adaptation of the theme or plugin to the linguistic needs of a specific market. We can read this article from w3.org which discusses the differences between both terms.
Localisation is usually abbreviated as l10n.
If you want to learn more about the characteristics of a successful website, we recommend reviewing our complete guide on the topic. There you will find valuable information to optimise your site and improve the experience of your users.
GNU gettext
For the translation of plugins in WordPress, a group of free internationalisation tools called GNU gettext is used — the same applies to themes. This system allows, among other things, that language adjustment is transparent to the user, and it is also one of the most widely used standards.
The gettext framework uses a per-message translation level. This means that each message (or string) is translated individually.
Differences between .po, .pot and .mo, the localisation files

The .POT file
The file we could call essential for the translation of any theme and/or plugin is the one with the .pot extension. This file contains a list of the text strings used. Normally this is the file we should send to translators.
This file is usually found in the languages directory of the theme or plugin. For example, a very common structure would be: wp-content/plugins/my-plugin/languages/my-plugin.pot. However, we should keep in mind that many plugins today do not use this structure but instead use the translations_api, as explained in the repository translation section below.
The .po or Portable Object file
These files contain both the original text strings and the translation specific to that file. This is the file that is typically received from translators.
If we open this file with a text editor such as Visual Studio Code, we can find information about the translation in the header, such as:
- The last revision
- The MIME version
- The language
- Whether it is the latest stable version
- Who the translator or last translator was
The .mo or Machine Object file
The .mo file has the same content as the .po file, only in a format adapted for machine reading. That is, they are binary files that use the gettext functions — a compiled version of the .po files. This conversion is done using the msgfmt tool; we can visit this link if we want to learn more.
If we want to see examples of .po files we can access the WordPress translations repository where we can see all the details of each translation, such as authors, dates, and stable versions. Upon entering a translation, and when we see the two columns (original and translation), at the bottom right we can select the format (including .po and .mo) and click export to download the file.
How to translate a WordPress plugin
Before we start translating any plugin in WordPress, we should be aware that each text string must be configured in a specific way so that it can subsequently be identified automatically.

The Text Domain
Whether we want to internationalise a plugin or a theme, we must pay close attention to the text-domain. This is a small piece of text included in the plugin's main file or in the theme's style.css.
It is very important that we do not use variables when representing our Text Domain.
The Domain Path
In the case of plugins (meaning this would not be used in a theme) it is also necessary to specify the Domain Path, and like the Text Domain, it is a piece of text added to the header of the plugin's main file. This text specifies where the translations are located.
Basic strings
Basic strings return the translation of the argument and do not contain placeholders or plurals (explained below). They are presented as follows:
__( 'Text in original language', 'text-domain' );If we want to echo this string in our code we could do it as simply as:
_e( 'Text in original language', 'text-domain' );
Variables
If we want to represent text in a variable way we cannot use basic strings. For example, if we want to do something like:
echo 'Your city is $city.'The solution lies in using placeholders together with the printf function. This is the correct way to use them:
printf(
/* translators: %s: City name */
__( 'Your city is %s.', 'text-domain' ),
$city
);
As we can see, there is a small comment that makes the translators' work easier.
If there is more than one placeholder, it is recommended to format it using sprintf. The correct usage would be:
sprintf(
/* translators: 1: City name 2: ZIP code */
__( 'Your city is %1$s, and your ZIP code is %2$s.', 'text-domain' ),
$city,
$zipcode
);
Plurals
If we have text that varies depending on other variable values and whether it is singular or plural depends on the case, then we should use the _n() function. This function accepts 4 arguments:
- The singular form of the text string
- The plural form of the same
- The number of objects that will determine whether it is plural or singular
- And our well-known text-domain
A good example is the one presented in the documentation. The heading of the comments field would vary depending on the number of comments that exist. So we could do the following:
printf(
_n(
'%s comment',
'%s comments',
get_comments_number(),
'text-domain'
),
number_format_i18n( get_comments_number() )
);
Context-dependent translation
Sometimes the same term may or may not be used depending on context. That is when we should use the _x() and _ex() functions. The latter is used when we want to echo. Unlike __() and _e(), a second argument is included — the context. Here is the example from the English documentation, which uses noun or verb as the context:
_ex( 'Post', 'noun', 'my-plugin' );
_ex( 'Post', 'verb', 'my-plugin' );Creating the translation files for a WordPress plugin
To begin we should start with the .pot file, which as explained above is the one we will deliver to the translators. But before this step it is very important to configure our plugin or theme as explained above. To create the files we have the following methods:
Via WP-CLI
WP-CLI is the command-line interface for WordPress. If we have it installed we can create a .pot file with a simple command:
$ wp i18n make-pot . languages/myplugin.potHere we can read more about the topic.
Grunt
Grunt is a JavaScript task runner that can be installed via NPM. It has a WordPress localisation package called grunt-wp-i18n that will scan your plugin or theme, find the strings, and compile a .pot file.

Poedit
Poedit is a Gettext text-list editor that can be installed on Windows, Linux, or Mac. It is free, surprisingly intuitive, and the free version can meet your needs.
Although I have never tried this method for creating .pot files, the WordPress documentation confirms it is possible.
Loco Translate plugin
To create the .pot file through this plugin, simply install it from the WordPress repository like any other plugin and activate it. You can then access the plugin by clicking on the right-side menu in the control panel — usually found near the very bottom.
To create the .pot file you will need to select the plugins or themes section (depending on what you want to translate), select the specific item, and click create template. Voilà, your .pot file has been created automatically.
Translating the WordPress theme or plugin
To translate a WordPress theme or plugin we need the .po file. There are several options:
Manual translation
All things considered, some people prefer the interface of their text editor. It may be a bit more laborious but it works 100%. So yes, you can open your .po file and edit it manually with your text editor. Each text string to be translated would look something like this:
#: path/to/my/file/myplugin.php:256
msgid "The crazy man on the hill"
msgstr "The hill's fool"
As we can see, the file location is shown first, along with the line number where the string is found. This is followed by the string in the original language and its translation.
The Poedit application
Once we have the .pot file we can open Poedit and follow these steps:
- Click on File
- Click "New from POT/PO file..."
- Select the .pot file we want to translate and convert to .po
- Select the language we want to translate into
- And voilà, we have an interface showing the strings to translate, the text in the original language, and the field where we can enter the translated string

The Loco Translate plugin
Yes, with the Loco Translate plugin we can also translate our WordPress plugin or theme:
- Once we have created the .pot file as explained above, access the template we created
- We will then find an interface very similar to Poedit
Translation from the WordPress repository
If our plugin is in the WordPress repository we can publish the translation files in the repository without having to bundle them inside the plugin itself. This results in a lighter plugin in terms of files, but we would also need to consider the call to an external server — each person must weigh what suits them best.
Today it is very likely that translations are obtained via the translations_api() function, which makes a call to wordpress.org to get the files needed for translation.
WordPress Translations API
A good example to illustrate the above is Contact Form 7, which previously included localisation files under the contact-form-7/languages folder, but if you look now you will find a readme.txt specifying that translations have been moved to the specific page on wordpress.org. And if we go a step further we will find the API call under contact-form-7/includes/l10n.php as follows:
$api = translations_api( 'plugins', array(
'slug' => 'contact-form-7',
'version' => WPCF7_VERSION,
) );
Security when translating our WordPress plugin or theme
It is very important to escape strings that will be displayed on the front end. In fact, we already have functions that do this automatically. So we should:
- Use
esc_html_()instead of_e() - Use
printf(exc_html__())instead of_e()when appropriate
.mo files
If we have a translation team and they deliver the .mo file, it is always better to ask for the .po file and compile it ourselves to avoid possible errors or even malicious code.
Compiling our .po file for our WordPress translation
It is true that if we are using Poedit for translations it would deliver a compiled .mo file. The problem with using that file is that the headers will be overwritten, so it is always better to do it via the command line:
msgfmt -cv -o /path/to/output.mo /path/to/input.po
Comments
Be the first to comment on this post.