A Full WordPress Plugin Example — Part 6 Internationalization

In this section, we internationalize the plugin GCMovie to make it can be translated into other languages.

The theory of internationalization is not complex. To use a translated string, WordPress provides a set of special functions (the gettext libraries). Therefore we should use these functions to get strings rather than using the literal text directly when we want them to be translatable.

Because there are normally many plugins in a WordPress site, each one may provide one or several translation files for different languages, it needs a way to tell WordPress where to load the translation and how to distinguish them. That why text domain and domain-path come. They are two header fields (you’v seen some header fileds in the main plugin file) serverd for internationalization. WordPress use a text-domain to denote all text belonging to your plugin and a domain path to define the location for a plugin’s translation.

Now let’s add our internationalization.

Add header fields in main plugin file

Add text domain and domain path to our main plugin file gcmovie.php, now it looks like:

<?php
/*
 * Plugin Name: GCMovie
 * Plugin URI: https://www.gloomycorner.com/gcmovie/
 * Description: A plugin example with custom post type, shortcode, widget, etc.
 * Author: Gloomic
 * Version: 0.1
 * Author URI: https://gloomycorner.com
 * License: GPL2+
 * Text Domain: gcmovie
 * Domain Path: /languages
 */

text domain

The text domain need to be a unique identifier for WordPress to distinguish between all loaded translations. In format, it match the slug of the plugin. And the text domain name must use dashes instead of underscores, be lower case, and have no spaces.

domain path

Normally the translation is located in a folder named languages within your plugin. It must be written with the first slash in the header field.

Use strings with gettext functions

There are kinds of gettext functions for different needs. __() and _e() are two most used ones.

  • __(), retrieve the translation of a text.
    __( $text, $domain );
    

    Examples:

    echo __( 'Search Movies', 'gcmovie' );
    
  • _e(), display translated text. Its effect equals echo __(...);.

See How to internationalize your plugin for more usages of these functions.

Now replace string literals with such function in all your code. Below are dome code that enables translations:

       add_meta_box(
            'gcmovie_meta_box_information',       // id
            __( 'Information', 'gcmovie' ),       // name
            array( $this, 'display_meta_box_information' ),  // display function
            'gcmovie'                             // post type
        );

Adding a text domain for every such a function can be burden if not done continuously when writing code. How to internationalize your plugin mentions a way to do it automatically. The method is downloading a tool add-textdomain.php (a php file) and run it to create a new file with the text domain added.

Create translation files

Now we can extract the strings that need translations from the code.

There are three kinds of translation files: POT (the template file) , PO (files with real translations) and MO (Compiled PO files).

  • POT (Portable Object Template) This file contains the original strings (in English) in your plugin.
  • PO (Portable Object) files Take the POT file and translate the msgstr sections into some language, it will become a PO file.
  • MO (Machine Object) files This a kind of machine-readable, binary files that are compiled from PO files.

There are several ways to generate a POT file, you can run a WP-CLI command or use a separate tool.

Generate the POT file

Here we use Poedit (a small GUI tool). Download and install it.

  1. Download a Blank POT file and put the Blank-WordPress.pot file to the languages folder under our gcmovie plugin. Rename it as gcmovie.pot, now we use it as the base of our own POT file.
  2. Open Poedit > Open > Choose our gcmovie.pot file > Click “Update from the code” button in the tool bar > Save.

Now we can create a translation file of some language from the pot file. Copy the pot file and rename it as gcvmovie-{local}.po. The locale is the language code and/or country code. such as de_DE for German, zh_CN for Chinese.

Below is a part of a Chinese translation filegcmovie-zh_CN.po. In the file add our translated strings in the “msgstr” section of the string indicated by “msgid”. You may also use the “Pretranslation” feature supplied by Poedit ( pro version provides online translation) and then correct the result instead translate one by one.

#: gcmovie.php:137
#: gcmovie.php:221
#: inc/class-gcmovie-admin.php:32
msgid "Director"
msgstr "导演"

Compile PO file to MO file

Open Poedit > Open > Choose gcmovie-zh_CN.po > Select “Compile to MO” from the file menu > Choose the default name.

In the langeuages folder there will be a gcmovie-zh_CN.mo file.

Load text domain

We need to load the MO file when our plugin is loaded.

As loading text domain said, since WordPress 4.6 translations now take translate.wordpress.org as priority and so plugins that are translated via translate.wordpress.org do not necessary require load_plugin_textdomain() anymore.

If you don’t want to add a load_plugin_textdomain() call to your plugin you have to set the Requires at least: field in your readme.txt to 4.6.

Here we still show how to that with code (not the preferred way) by calling the load_plugin_textdomain() ( and load_muplugin_textdomain() if your plugin will be used as a must-use plugin). This call loads the corresponding mo according the site language settings from the plugin’s base directory.

Add below to the constructor of class GCMovie in gcmovie.php

    add_action( 'plugins_loaded', array( $this, 'load_textdomain' ) );

Implement load_textdomain() method inside GCMovie calss by calling load_plugin_textdomain():

    function load_textdomain() {
        load_plugin_textdomain( 'gcmovie', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
    }

Resources

Gettext Functions