WordPress Development for Intermediate Users: Internationalization

WordPress Development for Intermediate Users: Internationalization

Internationalization, or i18n, is the process of developing your plugin or theme so it can easily be translated into other languages. And since WordPress is used all over the world, it’s important to ensure your code can be easily translated into whatever language is needed.

This is the seventh post in our WordPress Development for Intermediate Users series. This series follows on from our popular WordPress Development for Beginners tutorials, which introduced you to the fundamentals of developing websites with WordPress, how to get started coding with PHP, and building themes and plugins.

In this final post in our Intermediate series you’ll learn how to get your code translation-ready for users, or how to internationalize it.

You’ll need to have a theme and/or plugins to work with if you want to work along – either continue with the code you’ve developed during the series or download it from the source files. You’ll need the code from part six, WordPress Development for Intermediate Users: Custom Fields and Metadata.

In this final tutorial of the series, you’ll do three things:

  • Learn about internationalization and what it is.
  • Get your code ready for translation with some WordPress functions.
  • Create a text domain, which WordPress uses to translate your code.

Let’s start with an overview of translation and internationalization.

Missed a tutorial in our WordPress Development for Intermediate Users series? You can catch up on all seven posts here:

An Overview of Internationalization

Translating the text in front-end and admin screens makes your theme or plugin accessible to a much wider audience. According to Wikipedia, 54% of websites are in English, while 26% of Internet users are English speakers, with Chinese as a very close second with 21%. In contrast, only 3% of websites are in Chinese. A scan of past WordCamps shows that WordPress developers are all over the world – across Europe and in India, Nepal and Japan, for example.

While many non-English speaking developers and Internet users are accustomed to coding or reading in English, this is by no means ideal. If you make your code available for translation, you are opening up your plugins and themes, and your clients’ websites, to a vast and fast growing international audience.

It’s not even as if translation is difficult – it simply involves the use of a few functions that WordPress provides.

Note: Don’t translate your code – that will always be in American English, as that’s what browsers read. What you’ll be translating is the text that appears on public sites using your themes and plugins and on any admin screens you create or modify.

It’s easy to get confused between the two terms used in translation – localisation and i18n – and you’ll find plenty of tutorials and articles online which use the terminology incorrectly. The two terms can be defined as follows:

  • Internationalization is the process of making your code available for translation using the relevant WordPress functions, which is what you’ll be doing here.
  • Localization is the process carried out by a translator on your code, to translate it into the user’s language. You don’t need to do that – your users will, if they need to.

Let’s work through how you internationalize your code. This is sometimes referred to as i18n, because there are 18 letters between the “i” and the “n” in internationalization.

Preparing your code for translation involves three steps:

  1. Using WordPress functions to internationalize text.
  2. Loading a text domain.
  3. Creating a language file.

Let’s start with the functions.

Internationalization Functions

WordPress uses four main functions to internationalize text:

  • __( ‘message’ ) translates the content of the message but doesn’t echo it out.
  • _e( ‘message’ ) echoes the content of the message.
  • printf( __( ‘message’ ) ) is used with placeholders (for example the number of comments for a post or the queried object in an archive page).
  • _n( ‘message’ ) is used for singular and plural text, so if you’re showing how many comments there are, you use this to define whether the word ‘comment’ should be singular or the plural ‘comments’.

In our theme and plugins we have some text that needs to be translated, and some that we’ve already made translation-ready. Let’s work through these and identify the i18n already added and any more we need to add.

In our theme, there are two files with internationalized text already:

  • customizer.php
  • functions.php

There are also eight files with text that needs to be internationalized:

  • loop.php
  • loop-single.php
  • loop-project.php
  • archive.php
  • archive-project.php
  • taxonomy-service.php
  • search.php
  • 404.php

Let’s take a look at each of these, focusing on different i18n functions in turn.

Using the __() Function to Translate Text

The __()function translates text but doesn’t echo it out. Use it when defining a string of text for use elsewhere. In our theme we already have some files where we’ve done that, when defining admin screen text.

Let’s look at an example. In customizer.php we have this code:

This uses the __() function to translate the name of the section created in the Customizer by that function. The __() function has two parameters: the text to be translate and the textdomain. We’ll use wpmu as our textdomain in all of our files, and we’ll set up that textdomain once we’ve added all the functions.

If you scroll through the customizer.php file you’ll see that the __()function is used in various places.

Next let’s look at functions.php, which is the other file with existing code that’s been internationalized.

In the code for registering widgets, you’ll find this:

Again we’re using the __()function.

Now let’s move on to the project archive template. Open your archive-project.php file and find this line:

That includes some static text, inside a filter. Let’s make the text translation-ready: edit your code to read like this:

We’ve added the i18n function inside the apply_filters() function, as the second parameter.

Using the printf() Function with Placeholders

The printf() function lets you internationalize more complex text, which includes the value of variables, added in to the text to be translated with placeholders.

To do this you need to do three things:

  • Define variables which will be used for your placeholder text.
  • Create a printf() function with your internationalized text as its first parameter and the variables as subsequent parameters.
  • Use the relevant function to internationalize text in the first parameter of the function.
  • Add placeholders in the text to be translated, in the same order as the variables you’ve added as parameters.

This is quite tricky to explain but it will make more sense once you see it in action.

Let’s use printf() to internationalize some text in the site’s front end.

Starting with loop.php, this code needs to be translated:

The specific bit that needs translating is the title attribute of that link. Edit your code so it reads like this:

Again we’ve used the printf() function, because we’re working with a placeholder. Let’s take a closer look at this:

  • The first parameter of the printf() function is the esc_attr__() function which translates the text. This has two parameters: the text (including the placeholder, %s) and the text domain, which is wpmu.
  • The second parameter is the value of the placeholder text (which is fetched using the_title_attribute()). This has a parameter of echo=0 because we don’t want to echo it out, just get it.

Now repeat the change you just made to the loop.php file to the same code in the loop-project.php file. You’ll also find the same code in the loop-single.php file. As the heading in a single file links to itself, remove the link in the heading from that file. If you get stuck, take a look at the source files.

Using the _e() Function to Echo Translated Text

Your loop.php file is now ready for translation, so you can close that. But loop-single.php and loop-project.php still have some more code to translate.

Open loop-single.php and find this code:

That uses the the_terms() function, which echoes out a list of terms for the current post. Let’s edit the function and add some translated text before it, instead of inside the function. Here we’ll use _e(), because we want to not only fetch the translated text, but echo it as well.

Edit your text so it reads as follows:

Now open your loop-project.php file and make the same changes. I won’t talk you through it but you can check out the series files if you need to.

Finally, in the loop-single.php file, find the entry-meta div:

You’ll need to use the _e() function as well as the printf() function we’ve already looked at. Here’s what your code will look like when you’ve done it:

Let’s take a look at what we’ve done with that code in more detail:

  1. We’ve moved the conditional tag outside the entry-meta div. This is to avoid it displaying on single posts that aren’t of the post post type.
  2. We’ve defined a variable for the author name, the link to the author’s archive and the date. Note that all of these use functions starting with get_ so they don’t echo anything out, they just fetch it from the database.
  3. We added a printf() function. Its first parameter is the __() translation function with the placeholders included. This time we used __() instead of esc_attr() because we want to include html in what’s output. The three final parameters are the values of those placeholders: our variables.
  4. We fetched the categories for the current post and assigned them to a $cats variable, then checked if that variable had any content (i.e. if there are categories.
  5. We used _e() to translate the text to precede the category list.
  6. We used the_terms() in a similar way to the untranslated version, but removing the translated static text.
  7. We repeated steps 4-6 for tags.

This won’t result in any changes to your site just yet as we haven’t added the translation file, but you might want to check a single post just to be sure it’s still working on your default language. Nothing should have changed.

So, that’s the loop files edited to include i18n. Open the archive.php file next. Find this line:

The function you need to use here is _e() again. Try adding the function yourself without guidance from me, applying what you’ve learned so far. If you get stuck, check out the source files (you’ll need the files for Part 7).

A challenge: If you test the archive template by accessing an author template, you’ll find that the author name is missing. It works just fine for tag and category archives though. Try editing the archive.php file to ensure that author names are displayed. Alternatively, create an author.php file to display author archives. If you get stuck, take a look at the source files. A hint: if working on archive.php, you’ll need a conditional tag. In both cases you’ll need to use $title->display_name, where $title is the variable defined in archive.php based on the current queried object. I’ve created an author.php template which also outputs the author profile and a link to they website – you can find it in the source files.

There’s another archive template that needs some internationalization adding: our taxonomy-service.php file. Open it and find this line:

This will look familiar to you as it’s similar to the line we edited in the archive.php file. Apply what you did there to this file too – if you get stuck, check out the source code.

Now that just leaves our search.php and 404.php files. Both have static text inside headings and/or paragraphs. Use what you’ve learned so far to use the _e() function to translate this text. It’s a good way for you to practice and it should be pretty straightforward if you’ve been following along. Again, if you get stuck, refer to the source files for this part of the series (part 7).

Note: in 404.php you’ll need to use a special character for an apostrophe. The HTML special character for an apostrophe is .

Phew! Now our theme is ready for translation. Your next job is to internationalize the plugins you’ve created as you’ve worked through this series.

Applying What You’ve Learned: Internationalizing Your WordPress Plugins

Now you’ve learned how to use translation functions in your theme, let’s apply this to plugins. Some of the plugins we’ve been developing as we’ve worked through this series also need to be internationalized. These are as follows:

  • The first call to action box plugin we built when looking at hooks. There’s static text here you need to internationalize using _e().
  • The plugin using get_posts() to output a list of posts after the content, which we developed in the series part on custom queries. The main plugin has static text in the heading and in a link. Use _e() to internationalize both.
  • The plugin to display the latest project in the sidebar – again, use _e() to internationalize the static text in the header and the link.
  • The plugin adding a metabox to the post editing screen – here you need to use __() to internationalize the title of the metabox, not _e() as you’re not echoing out the text but defining it. You also need to use _e() to internationalize the text in the metabox and in the output text. Don’t forget to use the special character in place of any apostrophes.
  • The shortcode plugin. Here you’ll need to use _e() to internationalize static text in the simple shortcode. The shortcode with attributes will need you to use printf() with placeholders. You’ll need to define two new variables – one for the telephone number and one for the email address, and use what you learned when editing the loop-single.php file in your theme, applied to this plugin.
  • The widget plugin has text you’ll need to internationalize using __() and printf() – use the variables already defined in the plugin for your placeholders.

You might notice that the plugin for registering custom post types and taxonomies already has i18n added – saving you a job!

I’m not going to work through each of those in detail as you’ll need to use techniques I’ve already shown you for the theme. Try applying what you’ve already learned to internationalize your plugins – and as always, if you get stuck you can refer to the source files.

Once you’ve added all of your i18n functions to your theme and plugins you can move on to adding a text domain.

Setting up a Text Domain

Now all of your i18n functions are in place. In future, you should be writing these into your code as you create it, using the methods you’ve learned here. That’s much easier than going back and adding it all afterwards, in my experience.

But you’re not done yet. The next step is to add a text domain to your theme and plugins. Remember that 'wpmu' text domain we added as the second parameter to each of our i18n functions? Well, WordPress doesn’t know what to do with that yet. If you define the text domain, it will.

Note: Normally you would do this for each of your themes and plugins before adding i18n functions to the next one, but I’m taking them all in one batch so you can learn about the different aspects of i18n together.

Defining the Text Domain

Let’s start with the theme. Open up your theme’s stylesheet and find the commented out text right at the beginning.

Under the existing lines of commented out text, but before the comments are closed, add these two lines:

Your commented out text will now look something like this:

Now open each of your plugins (the main plugin file) and add the same text domain and domain path inside the commented out text at the top. Create that /languages directory for each theme and plugin – you may have to change the structure of some of your plugins if you created a single file for them instead of a folder. Save all of your files.

Loading the Text Domain

The final step in setting up your text domain is to use the load_plugin_textdomain() function in your theme and plugins to load the text domain.

Open your theme’s functions.php file and add this function:

Save that file and repeat the same step in each of your plugins, but using the load_plugin_text_domain() function instead of load_theme_textdomain(). If you get stuck, check out the source files.. Make sure you give the function a different name in each case (but not the text domain itself, that can be the same). If your functions have the same name, WordPress will throw up an error when it comes across duplicates.

Creating Language Files

Your code is now ready for translation, but more needs to be done for it to be translated..

WordPress uses three kinds of language file for translation:

  • A .pot file contains a list of all the translatable messages in a theme or plugin
  • A .po file is created when the .pot file is translated (i.e. when localization takes place)
  • A .mo file is a binary file created from the .po file. This is in machine-readable text and contains the strings and their translations – you could say it caches the translations and so speeds things up when WordPress is being translated.

The file you create for your theme or plugin is the .pot file.

To create a .pot file, you use a utility such as Poedit. When creating this file, you have to provide the tool with the following information:

  • Project information, including the name, the charset and the language you’re working in.
  • The path to the folder where your .pot file will go (the /languages directory).
  • The WordPress functions used to translate text (which we’ve worked through above).

Once you’ve provided this information, the tool will scan your source files and identify text you’ve identified for translation using these functions. You then save the .pot file it creates for you into your /languages directory, giving it the same name as your text domain.

The premium version of Poedit works with WordPress, so if you’re going to be translating a lot of code, you might want to buy a copy. But you can create a .pot file with the free version. Alternatively you can use the WordPress i18n tools to create your .pot file.

For more details of these methods, check out these links:

You might also find the Codex page on i18n useful.

Translation Helps Your WordPress Themes and Plugins Reach a Wider Audience

If you’re developing a site for an audience in your own country, then internationalization may not feel like something you need to do. But if you’re building themes and plugins that you plan to make available for others to use, either free or paid, you’ll need to make your files translation-ready.

If you use the functions detailed in this part of the series then the text in your files will be translatable and your code will be able to reach a much wider audience.

We’ve now reached the end of this series. Congratulations! Your WordPress development skills should have experienced a real boost. But the best way to learn is by putting what you’ve learned into action. Make sure you find (or create) some projects that let you practice your new skills before you have time to forget them.

If you’re planning on releasing your code to the public you’ll need to test it thoroughly too, and you should also do this if you’re developing for clients. If you want to learn about testing, look out for our upcoming series on advanced WordPress development. In the meantime you’ll find plenty of posts and tutorials on our blog.

Being able to use the WordPress development skills you’ve learned in this series will enable you to create more advanced projects and make a career as a WordPress developer. Good luck!

Did you find this tutorial helpful? Why do you want to learn WordPress development? What do you want to know more about? Let us know in the comments below.
Rachel McCollin
Rachel McCollin Rachel is a freelance web designer and writer specializing in mobile and responsive WordPress development. She's the author of four WordPress books including WordPress Pushing the Limits, published by Wiley.