Introducing Internationalization: Making our theme translation ready

In the last lesson, I made a mistake. 

Our theme added a piece text to the “Menus” panel inside the Admin Dashboard.

“What’s the problem with that?”

Oh dear! It’s a very big problem. Let me show it. 

To demonstrate the problem, I switched the language of our Dosth website to my native language, Telugu.

Then I went back to the Menus panel and this is how it looks like now:

Do you see the problem?

Every piece of text on the screen is now in the Telugu language except the text we added ourselves.

We created pages and added titles to them ourselves. WordPress did not translate them. 

We added the “Display Location” description text ourselves inside our theme. Again, WordPress did not translate them.

And, WordPress did not magically translate our text because there is a tedious and specialized process involved in translating a piece of text added by us. 

Forget about translating the text we added using the Admin Dashboard. For example, Page title, Page Content, Categories, etc. Translating a Page’s content is entirely a different process from translating the text in a theme and it is out of the scope of this course.

But, as a theme developer, let’s discuss the process of translating the general text inside a theme. 

It takes Four steps to translate.

Step 1) Making our theme translation ready (Internationalization)

We make a theme translation readyby wrapping our theme’s general client readable text inside WordPress Translation functions. 

For example, instead of typing the client readable text like this:

echo 'Display this menu in Header';

We wrap the text inside a WordPress Translation Function like this:

_e('Display this menu in Header','unique_theme_id');

That’s all it takes to make our theme’s text translation ready. It is not at all complicated process.

This entire process of making a theme ( or plugin ) translation ready is called Internationalization or i18n for short. 

So, next time, whenever you read or hear the word Internationalization or i18n, remember that it is just a process of making our theme/plugin translation ready. That’s all.

Speaking of terminology, WordPress Translation functions are also called “gettext” functions. 

Step 2) Extracting the translatable text from our theme

Once we make our theme translation readywe have to extract the translatable text inside our theme into a POT ( Portable Objects Template )  file. This can be done using the infamous poedit tool. 

Next, this POT file is sent to the translators.

Step 3) Doing the Actual Translation (Localization)

Once the translators receive this POT file, they will use a tool like poedit to adapt our theme’s original text for regional or local consumption. 

Now, there are two types of translation. 

1) Literal Word to Word Translation: I also call this “Crappy Translation”. In this type of translation, words are simply substituted from one language to another with some basic context. That’s all. No proper sentence formation and no proper grammar.

2) Localization or L10n: I also call this “Heartful Translation”. It is more about adaptation instead of simply substituting the words. In this type of translation, translators take a specialized approach so that the local language translation hits the target local language readers with the right feels and the emotion. To achieve this nativity, translators often need to modify the original text to match the grammar and the sentence constructs of the local language. They also take local cultural preferences into account.

Back to the process of translation, Once the translators are done with the translation, be it localization or literally word-to-word, they provide us back the PO ( Portable Object ) file. It is just a text file that contains the original texts from our theme and their translations. 

Now, if our site is getting translated into multiple languages, we will receive multiple PO Files from the translators. Not just one.

Finally, These PO files is compiled into their respective MO( Machine Object ) files. 

“Woah! What is an MO file?

An MO file contains the exact same contents as a PO file. The only difference is that:

  • PO file is a human-readable file
  • An MO file is not for humans. It is for computers. Computers can read an MO file faster when compared to a PO file.

Step 4) Place the MO File inside our theme

Once we have access to the MO File of a local language, we have to place this inside our theme, preferably inside a directory of its own. Theme/Plugin developers generally names this directory as “languages”.

Finally, we have to tell WordPress where this MO File is located inside our theme.

That’s all. WordPress and the Web server will take care of the rest for us.

Don’t worry, You don’t have to take care of the actual translation (Localization)

Generally speaking, as a theme developer, you don’t really have to translate your theme into other languages unless you have time and proficiency in other languages. Also, You don’t really get time to do the translation yourself. People hire you to get their WordPress Website done. Not to do the translation. Similarly, if the client wants to get the translation done, he/she will hire someone else who does the translation for their living.

So, Translation is something that we don’t perform that often as developers. 

But we have to make our theme translation ready. No exceptions for this.

Get it?

“Yep! But I have three questions”

Shoot!

“Do we have to translate programmatic PHP IDs and HTML markup too?”

No. We only translate general text that would be read by a normal user of our website. For example, clients, administrators and our website visitors. PHP IDs, HTML markup like classes, attributes are the things we feed to the browser or a server. Normal users can not see them unless they view the page source, right? 

So, No!

“Alright! I understand that we have to make our theme translation ready. I will do it if I am developing a multi-language website. But, what if I am developing a single language website? Most of the websites only support a single language, right? “

How can you be so sure that your client only wants to provide his/her website in a single language? The client can change the requirement of the website at any time. This is because there is no way we can judge the future readers of a website. Not even the client. 

Also, it doesn’t hurt to develop your theme in a future proof way, right? Trust me, it is really easy to make a theme translation ready. You’ll see that in a moment. You won’t believe how easy it is.

“Whatever! I am convinced. How do we make our theme translation ready( Internationalize )?”

Simple, First of all, Inside our theme, we need to wrap every custom text inside a WordPress translation function.

1) What does exactly a WordPress Translation function do?

A Translation function either echoes or returns the translated version of the original text. If the translated version is not available or if the theme is not even translated in the first place, the translated function either echoes or returns the Original text we fed it. That’s all.  That’s pretty much what a translation function does. No other responsibilities.

Translation functions are basically divided into two categories. 

1.1) Introducing the __() translation function and its family

The __() WordPress translation function just retrieves the translated text for use at a later point in WordPress execution but doesn’t echo the text. It is totally up to us however we want to use it. We usually provide this retrieved text as a value to a function’s argument so that WordPress will echo it at some appropriate location. The Display Location description we saw above is the best example. Every time we want to echo some general descriptive text inside the Admin Dashboard, we have to use this function or its family.

The usage of __() function

Let’s use this function to fix the mistake I made in the last lesson.

Open up the functions.php file and replace the following code:


register_nav_menus( array(
    'header'   = 'Display this menu in Header',
    'footer'   = 'Display this menu in Footer'
) );

With the below code:


register_nav_menus( array(
    'header'   => __( 'Display this menu in Header', 'nd_dosth' ),
    'footer'   => __( 'Display this menu in Footer', 'nd_dosth')
) );	

Not only the __() function, but all the WordPress translation functions accept at least two parameters for sure. The Original text and the Text Domain.

The purpose of the Text Domain

For people who translate, The Text Domain is a unique ID that tells them the origin of a text that needs to be translated. Simply put, it answers the following questions:   

  • Where is this text coming from? Is it From a theme? If so, Which theme?
  • Or, is it From a plugin? If so, Which plugin?   
  • Or is it from the WordPress core itself?

Also, we must use a single Text Domain for our entire theme or plugin. And to avoid any conflicts with other themes and plugins the Text Domain must be unique. 

So, in our case, I provided nd_dosth as the text domain and It is a unique ID for our theme and chances of getting the conflict with other themes and plugins is pretty rare.

“Hey! How can you be so sure that our theme text domain is unique?”

I mean what are the odds of the client installing a theme or plugin which uses the same Text Domain as our theme? 

“Pretty much rare!”

Correct!

That’s all you need to know about the text domain.

Introducing the family of __() function for who cares about the security

If you think from the security perspective, all the translated text that the translators are providing is an untrusted data, right? 

What if the translator is a disguised malicious hacker? 

So, it is always a good idea to sanitize the text that we receive from the translators. 

And the friends of __() function will help us achieve just that. They does whatever the __() function does. But they add a security layer on top it.

Let’s take a look at the couple of them

  • The esc_html__() translationfunction returns the translated text by escaping out any malicious HTML if present.
  • The esc_attr__() translationfunction returns the translated text that is suitable to be outputted as an HTML attribute value.

There are more, but these are the most widely used secured translation functions. 

Anyway, let’s put these to use. 

If you remember, WordPress is outputting the Display Location description text inside the Menus panel.  And it is being outputted inside the label.

This tells us that we have to use  esc_html__() translationfunction for this text. 

So, Switch back to the functions.php file and replace the following code:


register_nav_menus( array(
    'header'   => __( 'Display this menu in Header', 'nd_dosth' ),
    'footer'   => __( 'Display this menu in Footer', 'nd_dosth')
) );


With the below code:

register_nav_menus( array(
    'header'   => esc_html__( 'Display this menu in Header', 'nd_dosth' ),
    'footer'   => esc_html__( 'Display this menu in Footer', 'nd_dosth')
) );

In the above code, we replaced the __() function with the esc_html__() function. That’s all. We did not touch anything else.

Now, we can happily sleep during the nights.

1.2) Introducing the _e() translation function and its family

There is only one difference between the _e() and __()WordPress translation functions. 

The _e() WordPress translation function retrieves the translated text and echoes it instead of returning it. 

This is the only difference. The usage and everything else is the same for this family of translation functions too.

The usage of _e() function

If we are echoing some readable text to the front-end visitor, we shouldn’t use the following basic echo statement any more.


echo 'Read More';

Instead, We should echo using the _e() function, for example:


_e( 'Read More', 'text-domain' );

Introducing the family of _e() function for who cares about the security

Let’s take a look at the couple of them

  • The esc_html_e() translationfunction echoes the translated text by escaping out any malicious HTML if present.
  • The esc_attr_e() translationfunction echoes the translated text that is suitable to be outputted as an HTML attribute value.

We will put these translation functions to use when we build our blog. When we build our blog, we need to echo some important text to the frontend using our theme. 

2) Again, Our Job is only half done!

We are using the WordPress translation functions to retrieve the translated text for the user readable text. Great! 

If at all our theme is translated into other local languages, How do these translation functions retrieve the translated text in the first place?

They are not going to do this automatically. We have to load the translated files before the translation functions try to retrieve the translated text. 

And, the load_theme_textdomain() WordPress function allows us to do just that. 

We already learned that all the translated text is stored inside a language-specific MO file inside our theme. And we can load all the translated Mo files using the load_theme_textdomain() function.

This function loads the translated files if they exist and returns a boolean true to tell us it has successfully loaded the translation files. On the opposite side, It returns false if our theme is not translated. So, it doesn’t throw any errors if our theme is not translated.

Important Realization: It is important that you must still call the load_theme_textdomain() function even if your theme is not translated. We are making our theme translation ready no matter what. It is the only way that our theme is future proof.

Also, We have to load these translated files as soon as WordPress is done with the theme setup. So, we need to call this function from within an action that we hooked to the after_theme_setup action hook.

So go back to the functions.php file and put the following code at the beginning of the nd_dosth_theme_setup action:


/*
 * Make theme available for translation.
 * Translations can be filed in the /languages/ directory.
 */
load_theme_textdomain( 'nd_dosth', get_stylesheet_directory() . '/languages' );

This function accepts two parameters. 

  1. Text Domain of the theme. 
  2. Location of the language-specific translation files that contains the translated text.

In our case, we are telling WordPress that “Hey! our theme’s Text Domain is ‘nd_dosth’ and the theme related translation files would be located inside the ‘language’ directory located at the root level of the theme itself. So, use these pieces of information to correctly load the translation files if they exist! “

Here is the final nd_dosth_theme_setup action after adding the above code:


function nd_dosth_theme_setup() {

    /*
    * Make theme available for translation.
    * Translations can be filed in the /languages/ directory.
    */
    load_theme_textdomain( 'nd_dosth', get_stylesheet_directory() . '/languages' );

    // Add <title>: tag support
    add_theme_support( 'title-tag' );  

    // Add custom-logo support
    add_theme_support( 'custom-logo' );

    // Register Navigation Menus
    register_nav_menus( array(
        'header'   => esc_html__( 'Display this menu in Header', 'nd_dosth' ),
        'footer'   => esc_html__( 'Display this menu in Footer', 'nd_dosth')
    ) );

}
add_action( 'after_setup_theme', 'nd_dosth_theme_setup');
  

3) Create the “languages” directory and once you think that the theme development is complete, generate the PO file. 

In the previous step, We told WordPress that the theme related translation files would be located inside the languages directory. But we did not create this directory yet! So let’s create it at the root level of the nd_dosth theme.

Here is our theme’s updated directory structure for this lesson:

Now, we shouldn’t generate the PO file yet! We are at the early stage of theme development. And we can’t keep generating the PO file every time we add some general text. So, it is a better idea to generate this file only when the client asks you. And for the purposes of this course, we will conclude our course by generating this very file.

That’s it. That’s all we need to do to make our theme translation ready. 

Remember that making our theme translation ready is a continuous journey. It only ends if the project is dead. So, what we have seen in this lesson is just the tip of the iceberg. We just made our baby steps into the world of WordPress translation concepts.

In the next lesson, we will style our header navigation and implement the menu dropdown functionality using the Superfish Javascript plugin.