How To Internationalize Your WordPress Website

About The Authors

Lizzie Kardon is a digital marketer from New York City and the Head of Content and Engagement at Pagely, the WordPress hosting market leader in innovation, … More about Lizzie Kardon ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

As an open-source community, we should all strive to localize our open-source contributions. Before you can transcribe your digital assets though, you have to internationalize your codebase. WordPress is currently fully localized for over 65 languages and offers partial translations for an additional 95 locales. If you haven’t internationalized your WordPress website yet, it’s probably time to do so.

On September 30th, 2017, the international WordPress community united for 24 hours to translate the WordPress ecosystem. For the third time, #WPTranslationDay fused an all-day translating marathon with digital and contributor day events designed to promote the value of creating accessible experiences for global users, better known as "localization".

As an open-source community, we should all strive to localize our open-source contributions. Before you can transcribe your digital assets though, you have to internationalize your codebase.

The terms “internationalization” and “localization” are often used interchangeably, though they technically represent two different aspects of the translation process.

  • Internationalization (I18N) is the process of internationalizing or adapting your theme or plugin to translate it into any language in the world.
  • Localization (L10N) is the subsequent process of localizing, or translating your internationalized tools into a given language.

WordPress is currently fully localized for over 65 languages and offers partial translations for an additional 95 locales. International use continues to rise as more localizations are introduced.

Stats from WordPress.org
Stats from WordPress.org (Large preview)

Non-English speaking WordPress users surpassed English speaking users in 2014, and they inadvertently continue to dilute English users’ percentage of pie in 2017 as efforts like #WPTranslateDay grow.

As WordPress becomes more and more linguistically accessible, knowledge of I18N & L10N is essential for plugin and theme developers to thrive in the global WordPress economy. For scaling businesses, these development skills can open doors to foreign markets. Web accessibility, including language accessibility, is good for business, and better for people.

As a continuation of #WPTranslationDay, here’s an updated guide to internationalizing your WordPress plugins and themes.

Here is a brief overview of the process we’ll be exploring today.

  • Discovery
    • Get to Know Translation Files
      • POT File
      • PO File
      • MO File
    • GlotPress & Language Packs
    • Backup & Prepare Your Environment
  • Plugin I18N
    • Plugin Header
    • Load Text Domain
    • Strings Audit
    • Generate POT File
  • Theme I18N
    • Theme Header
    • Load Text Domain
    • Strings Audit
    • Generate POT File
  • JavaScript I18N
  • Additional Resources

Discovery

Get To Know Translation Files

WordPress uses the GNU gettext library to facilitate I18N.

First, let’s get familiar with the gettext translation files generated throughout the process.

Portable Object Template File (POT)

During the I18N process, we’ll use a tool to find internationalized strings and generate a POT file containing all of the translatable text in your plugins and theme.

Portable Object File (PO)

In appearance, there are no remarkable differences between a POT file and PO file. They’re syntactically the same and only differentiated by their intended purposes.

After generating a POT file, text strings should be interpreted by a translator into your preferred language. The PO file will eventually contain text strings in your native language, as well as the appropriate translations.

Machine Object File (MO)

Finally, the PO file is converted into a machine-readable document or Machine Object file. This file will live in your theme or plugin directory for WordPress to call upon when it’s time to serve a translated version.

Back-Up And Prepare Your Environment

Before modifying any markup, take a backup of your plugin, theme or your entire site (whatever you are internationalizing!) and get your development environments in order.

Be sure to do a quick plugin audit if you plan to localize your website. Delete or deactivate any plugins that you’re no longer actively using. This quick audit will save you time in the long run.

Internationalize Your Plugins

Plugin Header

First, we’ll update the plugin header. Specifically, the text domain and the domain path.

Example Plugin Header

/*
 Plugin Name: My Rad Plugin
 Plugin URI: https://myradplugin.com
 Description: Custom Plugin That Makes My Site Rad
 Author: Rad Plugin Creator
 Version: 1.0
 Author URI: https://radplugincreator.com
 Text Domain: rad-plugin
 Domain Path: /languages/
 */

Text Domain

The text domain is a unique identifier for WordPress to recognize all of the text belonging to a plugin and must match the plugin’s slug.

This is especially important if your plugin is hosted on WordPress.org; these need to match in order for GlotPress to properly import translations for your plugin.

In our example, the plugin file is named rad-plugin.php and the text domain is rad-plugin

You may already have a text domain set. If you don’t, remember to use hyphens, not underscores.

Domain Path

The domain path is the folder that the finalized translation files will live in. You should create a new folder within the plugin directory, such as /languages/, and update the domain path, so WordPress knows exactly where to search for translation files.

Load Text Domain

Next up, we’re going to the load the text domain by adding the following function to our code.

load_plugin_textdomain()

load_plugin_textdomain( $domain, $abs_rel_path, $plugin_rel_path );

If a translation file is available for the user’s language, the load text domain function will tell WordPress to deliver it.

Parameters

  • $domain — text domain (required)
  • $abs_rel_path — false (optional, deprecated)
  • $plugin_rel_path — /languages/ (optional, this is the relative path to the directory containing your translation files. In our example, /languages/. As of 4.6, WordPress will search for these files in the plugin’s /languages/ directory, if this goes unspecified.)

Rad Plugin Example

To load the text domain, we’ll hook into the ‘plugins_loaded’ action.

add_action( 'plugins_loaded', 'rad_plugin_load_text_domain' );
function rad_plugin_load_text_domain() {
    load_plugin_textdomain( 'rad-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
}

Strings Audit

The next step is to wrap all of the plugin’s text strings in translation functions.

The most common translation functions are __() for returning a translated string and _e() for echoing a translated string.

Rad Plugin Example

__()
$text = __( 'Super Rad!', 'rad-plugin' );

This function will work for simple translations. It takes into account two parameters; the text string and the text domain.

_e( 'Super Rad!', 'rad-plugin' );

This function simply echoes the value. Otherwise, it works exactly as the previous function does.

There are a slew of additional translation functions that should also be used when appropriate.

Other Basic Functions

__()
_e()
_x()
_ex()
_n()
_nx()
_n_noop()
_nx_noop()
translate_nooped_plural()

Date & Number Functions

number_format_i18n()
date_i18n()

Escape Functions

Don’t forget to escape any data that’s being output using the following escape functions.

esc_html__()
esc_html__()
esc_html_x()
esc_attr__()
esc_attr_e()
esc_attr_x()

In our Rad example, we might have a line of plugin code that looks like this:

function create_section() {
    esc_html_e( 'Below are your settings for Rad Plugin', 'rad-plugin' );
}

Create a POT File

The final step is to create a POT file. There are a few different tools available to generate this type of file, but for this tutorial, we’ll stick to recommendations from the WordPress codex and use Poedit, an easy-to-use GUI for managing translations.

Note: If your plugin or theme is already in the WordPress.org repository, you can skip this process and generate a POT file from the admin page. In this case, it isn’t necessary to generate a .po file for each language. The introduction of GlotPress and language packs in 2015 simplified the translation process; all that is needed for GlotPress is the .pot file, which will be imported by WordPress.org and served to translators through the translate.wordpress.org dashboard. Collaborators from around the world will use this dashboard to provide translations on #WPTranslateDay.

Download Poedit

Configuring Poedit

Translation Properties

Start by going to File > New. Then select your plugin’s native language and hit OK.

Large preview

Next, select Catalog > New. Enter the project name, version, contact and language.

Large preview

Paths

Select the Sources Paths tab next. If this is a new file, you will have to save the file first.

Following our example, we’d select File > Save As to save as a .po file to the /languages/ directory inside of rad-plugin. Again, the file name should match your plugin’s slug.

rad-plugin.po

After saving, then enter the relative path to the directory where your translation files will live.In this scenario, languages.

Large preview

Keywords

Select the Sources Keywords tab. Click the + icon to add the appropriate function names.

Large preview

Cheatsheet

__
_e
_x
_ex
_n
_nx
_n_noop
_nx_noop
translate_nooped_plural
number_format_i18n
date_i18n
esc_html__
esc_html__
esc_html_x
esc_attr__
esc_attr_e
esc_attr_x

Afterwards, you can click OK and then File > Save.

Poedit will save both a .mo and a .po file. You can create the .pot file by copying the .po file and adding a .pot extension. International users can use the template .pot file to translate the strings into their language.

Internationalize Your Theme

The process for internationalizing your theme is practically identical to I18N for plugins. We’ll start by checking the theme header and end by generating another POT file.

Theme Header

Double check your theme header to make sure your text domain and domain path are set.

Example Theme Header

/*
Theme Name: Rad Theme
Author: Rad Theme Author
Text Domain: rad-theme
Domain Path: /languages/
*/

Load Text Domain

The next step is load a text domain in your theme’s functions.php file this time.

load_theme_textdomain()

load_theme_textdomain( 'radtheme', get_template_directory() . '/languages' );

In order for these files to load, you have to register them with the after_setup_theme action.

add_action( 'after_setup_theme', 'rad_theme_setup' );
function rad_theme_setup() {
    load_theme_textdomain( 'radtheme', get_template_directory() . '/languages' );
}

Strings Audit

Time for another strings audit! You should be familiar with the basic translation functions at this point.

Let’s take a look some trickier translations.

Placeholders

PHP variables like the example below will not be translated properly without the use of placeholders.

echo "We added $count rad points.";

printf() & sprintf()

These functions use placeholders such as %s, or %d for integers, to interpolate a string of text with dynamic content.

Rad Theme Example

/* Translators: %d is the number of rad points added */

printf( esc_html__( 'We added %d rad points.', 'rad-plugin' ), $count );

Plurals

The _n() function can handle more complex string translations like plurals, but it’s not recommended because of its limitations within translation software. Instead you can write a simple if statement to distinguish between singular and plurals words.

Rad Plugin Example

If ( 1 === $rad_points_found ) {
    $message = __( '1 rad point', 'rad-plugin' );
} else {
    /* Translators: %s is the number of rad points found */
    $message = sprintf( __( '%s rad points' , 'rad-plugin' ) , $rad_points_found );
}

Generate POT File

The process for generating theme POT files is the same as generating plugin POT files. Explore some methods below.

Command Line

With WordPress Trunk and the gettext GNU package installed, you can breeze through this step by running the makepot.php script in the command line.

Open the command line and navigate to the I18N tools directory.

cd wpdev/tools/i18n/

The script should look something like this:

php path/to/makepot.php wp-theme path/to/rad-theme rad-theme.pot

The script will do its thing and the finished file, rad-theme.pot, will end up in the current directory.

Grunt Tasks

Another method for creating a POT file is to run Grunt tasks using either grunt-wp-i18n or grunt-pot. Task runners like Grunt can automate lots of tedious tasks, such as generating POT files.

To create your POT file, make sure you have node.js installed first. Then, install Grunt in your language directory via the command line, and away you go. Now, you can quickly run these I18N commands and make your translation file without leaving the comfort of the command line.

JavaScript I18N

If you’re a modern theme or plugin developer, chances are you use JavaScript to handle some component of your project. wp_localize_script is an effective function for extracting PHP data to provide to your scripts, and it’s the only way to translate JavaScript inside of WordPress.

Wp_Localize_Script()

This function will allow us to localize strings server-side in PHP and provide text strings as a JavaScript object to the script.

wp_localize_script( $handle, $name, $data );

Parameters

  • $handle — script handle the data needs to be available for (required — needs to match the handle of the script this data is for, see example below)
  • $name — name of the object to contain the data (required — should be unique)
  • $data — an array of data to pass to the script (required).

Rad Plugin Example

add_action( 'wp_enqueue_scripts', 'rad_theme_scripts' );
function rad_theme_scripts() {
    wp_enqueue_script( 'rad-theme-script', get_template_directory_url() . '/js/rad-theme-script.js' );
    wp_localize_script( 'rad-theme-script', 'rad-I18n',
        array(
            'message' => __( 'Super Rad!', 'rad-theme' ),
        ) 
    );
}

Acces Data In JavaScript

The simple code snippet below is an example of how to access this data in your JavaScript file.

alert( rad-I18n.message );

Additional Resources

Wrapping It Up

Congratulations! You’ve just internationalized your theme and/or plugin, making it accessible for people of all native languages to benefit from. Whether your project is distributed through the WordPress repository, or custom-developed for your organization’s website, there’s only an upside to internationalizing your files from start.

  • Make your open-source contributions accessible globally.
  • Create conversations with new customers in foreign markets.
  • Save time and reduce laborious updates in future.

Always internationalize.

Further Reading

Smashing Editorial (mc, ra, yk, il, mrn)