While there are plenty of articles about translating WordPress, very few of them go into specific details of how this is done and managed on a day-to-day basis.
What happens when you change something in the code? What happens when you add new strings? What happens when you update a plugin to a new version?
These are the types of questions I will answer in this article. I will show you, step-by-step, how to get this done for new plugins and updates so you will be able to translate any plugin into any language.
Let’s get started!
Creating a Plugin
The first step you will need to take, of course, is to create a plugin. If you already have one, great, if not you can follow our detailed getting started with plugin development guide. If you need a quick example plugin, let’s create one now together.
All we’ll be doing is adding some motivational text to the header. Think the Hello Dolly plugin with explicit, motivational text, not lyrics.
First, create a folder in your plugins directory and name it
wp-admin-motivation. Then, create a file within that folder named
wp-admin-motivation.php. The code to paste within that file is below.
The first function
get_motivation_text() contains an array of motivational strings. The array is shuffled and the first one is returned. This is effectively a randomizer, the result of this function will be a randomly picked item from the array.
We then hook the
show_motivation_text() function to the
admin_notices hook. The function gets a random motivation text and echoes it inside a paragraph with the id of
wp-admin-motivation. At this stage our text will be visible but lets position it right near the screen options/help tabs with some CSS.
The Hello Dolly plugin adds CSS by simply outputting it in the header. This is not really the correct way of doing things so lets enqueue a stylesheet instead. This is done by hooking the
motivation_assets() function to
admin_enqueue_scripts and enqueueing the stylesheet there.
The stylesheet positions the text and takes care of RTL (right-to-left) issues as well.
With all that done, the only problem is that it only works in English. If you wanted to use another language you would need to create the exact same plugin, replacing the strings to the target language. This seems like a waste, there must be – and indeed there is – a better way.
Internationalizing a Plugin
Internationalizing a plugin essentially means making it ready for translations. This involves wrapping translatable strings in special functions which will be used later to load the correct translations.
There are a number of internationalization functions, but for our simple plugin we’ll take a look at just one and then talk about the others later once we understand the example.
The function we’ll be using is
__(). This function takes two arguments: the text to translate and the text domain, it looks something like this in action:
The function returns the translation for the marked string if it exists. If it doesn’t, the original string is returned.
It uses the current language to determine the needed translation. You can switch languages in WordPress in the Settings section, whatever setting is selected there will be applied to all translations.
The text domain is the final missing piece of the puzzle. A text domain is a way to group translations that belong together. The theme you are running will have its own text domain and so will all your plugins. This makes it easy to differentiate between them.
Note that for plugins the text domain should be the name of the plugin’s folder. In our example this means the text domain should be
wp-admin-motivation. To internationalize our plugin all we need to do is wrap our strings in the
We’ve now marked translation ready strings, but there are no translations anywhere in sight. We need to tell WordPress where translations for our plugin reside. The standard is a
langdirectory within our plugin. Create that now and use the following code in the main plugin file to let WordPress know where to look for translations.
load_plugin_textdomain() function is key here and it must be called from within a function hooked to
plugins_loaded. The first argument is the text domain which we’ve talked about, the second has been deprecated and the third is the location of the language folder.
Our translation files will be named
[plugin-name]-[locale].mo. I’ll show you how to make these a bit later, for now remember that you will place your files in here. In our example a German file would be named
wp-admin-motivation-de_DE.mo. You can look up the WordPress locale if you don’t know it.
At this stage if we had language files everything would be working just fine so the last step is to create translations.
Creating a Translation
There are a number of ways you can create translations but the end result is always the same. You will end up with two files, a
po file and an
mo file. The
po file is a human-readable translation file. You can open it in any text editor and edit it. The
mo file is a
po file which has been converted to a machine-readbale format. This is much smaller in size but completely unreadable of course.
The following is an example of an untranslated PO file. As you can see there is some basic information about the translation, translator and project at the top, followed by a list of translatable strings and their to-be translations.
The translatable bits can be generalized like this:
Since the locations of the strings are given right down to the line number you may be thinking that this will be a bit hard to create and maintain. This is why we have custom tools to help us out! Once you have a plugin in the repository the admin tools allow you to generate a .PO file but you can also create one with the awesome and free Poedit.
Download and fire up Poedit, click on New and then New Catalog. In the window that pops up fill out your details. You don’t need to worry about the charset and plural forms right now, just the basic details.
Switch over to Source paths and let’s discuss the base path and paths. The big box labeled Paths tells Poedit where to search for translatable strings in relation to the base path.
We’ll be putting our translations in a sub-directory of our plugin. This means we have two options:
- Set the base path to
../(parent directory) and set a path in the paths section to
- Set the base path to
.current directory and set a path in the paths section to
In the first example we’re telling Poedit that our code actually resides in the parent directory of the language folder. It should go to the parent directory and then subsequently search that directory (hence the use of
. in the paths section).
In the second example we are saying that our current directory is the one this language file is in. However, the files we need to check are one directory above us, which is why we used
../ in the paths section.
Either way will be fine, I think the first version is semantically more correct, although I’ve tended to use the second one for plugins myself until now.
The next step is to set the keywords we use in the “Sources keywords” section. Here we define the names of the translator function we used. In our case we just need to add one –
__. More likely than not you’ll be adding at least 2-3 functions here, take a look further down for advanced translation.
Sign up for more
When you click through, Poedit will scan the files and spit back any translatable strings. It will organize them into a list you can click through, writing the translations in the bottom-most page.
Once you’re happy with your translations save the file, it should auto-generate an MO file for you. By default the file name will be
default which you will have to change to the one discussed above:
[plugin-name]_[locale].mo. The name of your PO file is irrelevant although for sanity’s sake I would name it the same.
In my example I’ve translated to Hungarian so my file name is
wp-admin-motivation-hu_HU.mo. Once this file is in place the plugin will show the specified text in Hungarian as long as my WordPress instalation is set to use the Hungarian language.
Updating translations is not the pain it sounds. While it is true that the PO file contains the location of the translated text down to the line number, these aren’t taken into account when WordPress uses the file. The location and line number is for the translator to be able to look up the string if needed.
As long as you are just juggling code around without changing the translatable string contents you will not need to update the language files. What I usually do is create a POT file, this is the only file I update every time I update the plugin itself, even if strings don’t change.
POT files are just like PO files but they do not contain any actual translations – they are template files. As long as this template file is updated and contains the right line for each string new translations can be made easily.
If you do need to update a language file you’ll need to open it up in Poedit and click on the Update icon. Poedit will notify you of changed and new strings and you can then go in and edit them as needed.
One place where I see confusion (at least I was confused by this at first) is how translations make it into a plugin. If I download a plugin how can I add my own translation? I thought this process was automated somehow, like installing a plugin, but it isn’t.
if someone likes a plugin, they translate it into their own language, using the POT file within the plugin and then send the result to the developer. The developer then adds it to the lang folder, creates a new version and pushes it to the WordPress repository. When a user receives the update the plugin will magically show up in his language if it is in the languages folder.
I host all my repositories on Github which makes it extremely easy for developers to send me these files using pull requests. All I need to do is press a button and create the new version for the WordPress repository.
You now know how to translate basic strings using the
__() function which returns the value. There are
a total of 14 functions you can use to fine tune your translations although you’ll mostly ever use 3-4 of them. Let’s start with the most common ones.
This is the basic function we looked at, it takes a string and returns its translation if it exists.
This function does the exact same thing as the previous one but instead of returning the result it echoes it straight away.
This function allows you to handle plurals elegantly. It takes 4 parameters, the single form, the plural form, the number based on which one or the other will be displayed and the text domain. Here’s a quick example:
The purpose of this function is to prevent collisions of similar words. If you plugin uses “pair” in the sense of a couple of people and pair in the sense of pairing a device via bluetooth as well, collisions could arise. A foreign language might well use very different words for the two.
_x() allows you to indicate your intent.
These are the same as our collision preventing friend from above but the first echoes the result straight away and the second allows you to add plurals into the mix.
There are six functions that allow us to escape the value for safe usage in attributes and HTML. Their names are very descriptive, I’m sure you’ll be able to guess what they will do:
This one is a bit complex so I’ll refer you guys Konstantin Kovshenin’s excellent article on Understanding _n_noop. Noop functions basically register plural strings in POT file, but don’t translate them. The reason for this is that the number is not known in advance which means we need to add another function to grab them.
Due to this we use the noop function and translate the actual value later. There are only a couple of cases where you would need this functionality, take a look at the article above for more info.
Thee functions that make this happen in WordPress are:
wp_localize_script() function. This function takes three arguments: the handle of the script you want to translate, an object name to be used within the script and the translation array. Here’s an example:
Once you’ve added your translations here you can use them in the registered JS file. You’ll need to use the object name and the keys of the translation array:
Millions upon millions of people use WordPress in their own language and why not help them do so with our products? Making our plugins translation ready is easy and maintaining it isn’t a hassle either.
By marking our strings for translation and creating the po/mo files we’re helping out everyone and making our product better in the process.
By the way, if you’d like to see the little example plugin we built in this tutorial you can download it here and take it for a spin.
Now that you know, why not find a plugin you like and help translate it?
What resources do you use for translating plugins? Let us know in the comments below.