Blog Menu

I write and curate content for Bluehost. I hope this blog post is helpful.
Are you looking at creating a blog, website or an online store? Bluehost has something for everyone. Get started today.

Since 2007, WordPress has supported the translation of text strings in PHP. As of 2018, we now have a similar process for translating strings in JavaScript.
Traditionally, WordPress developers have utilized the wp_localize_script() function for passing translated strings from PHP into JavaScript. Unfortunately, this approach doesn’t scale well and makes for some messy code. Thankfully, with the dawn of Gutenberg, we now have the new WordPress i18n npm library.
At the time of this writing, it is a bit hard to find examples of how to properly translate JavaScript strings in WordPress from start to finish. Most examples will give you a piece of the puzzle, but not the entire picture. Our goal today is to document the entire process from start to finish.

Internationalization and Localization Overview

There are two steps in the translation process: Internationalization and localization.
It is common to see the abbreviations i18n and l10n when referring to internationalization and localization. The logic behind this is that you take the first and last letter of the word, count the remaining letters and put that number in between.
At the most basic level, internationalization is the process of preparing a theme or plugin for translation. Localization is the process of leveraging that preparation and actually performing the translation.
Internationalization starts by utilizing the translation functions that WordPress provides. Essentially, all of these functions allow you to provide a text string to be translated and what is called a text domain.

Text Domain – a namespace, or unique identifier, under which all of your translated strings are managed. WordPress uses the text domain to tell the difference between the translation for your theme or plugin and that of another.

Finally, an automated tool is used to locate and compile all of the text strings with your text domain into a .pot (portable object template) file.
Localization, the next step in the process, starts when you provide the .pot file to a translator for translation into the language(s) of choice. The translator will take the file, translate the strings, and save the translated strings into a .po(portable object) file. However, WordPress requires a .mo (machine object) file in order to dynamically translate the text via code.
The last step is to convert the .po file into a .mo file. There are a number of ways to do this, but if you use a tool like Poedit both of these files can be generated simultaneously.
At this point, we’ve prepped for translation and have a file that will allow WordPress to translate the strings via code. However, nothing is going to be translated unless WordPress knows what language is in use and where the language file is located:

  1. Set the language – you can tell WordPress what language you want to use by going to Settings > General in the WordPress admin and selecting the desired language from the Site Language dropdown. While your users would normally be the ones doing this, you should do this to thoroughly test that your translation is working.
  2. Register your language file – WordPress has a few functions you can use to register your language file so it can be found. If you are translating a theme, you will want to use load_theme_textdomain(). If you are translating a plugin, you will want to use load_plugin_textdomain().

Translating JavaScript Strings

Now that we have a clear understanding of how translations work in WordPress, let’s take a look at exactly how we can translate strings in JavaScript.

Using the WordPress i18n Package

Let’s start by taking a look at a translation function in JS:

__( 'Hello World!', 'text-domain' );

Looks familiar, doesn’t it? The JavaScript i18n API mirrors the translation functions that you would normally use in PHP. The only exception is that you need to first load the WordPress i18n NPM library into your project and import the necessary functions to make them available.
To do this, start by installing the npm module:

npm install --save @wordpress/i18n

Next, import the functions that you will be using from the module:

import { __, _n, sprintf } from '@wordpress/i18n';

Note: This approach uses JavaScript ES6. It will be necessary to run an automated build process to convert the code into browser-safe JavaScript. We recommend using Babel and Webpack to accomplish this.

Generating a .pot File

Traditionally, generating a .pot file for strings in a PHP file has typically been accomplished using the Grunt task runner and the grunt-wp-i18n package. However, this tool doesn’t scan JavaScript files.
WordPress has released an NPM package called babel-plugin-makepot for the specific purpose of automatically generating (or updating) a .pot file as you make changes to your JavaScript files.
This Babel plugin requires the use of Babel. Typically, Babel is configured to run via Webpack.
To do this, start by installing the npm module:

npm install --save-dev @wordpress/babel-plugin-makepot

Next, update your Babel loader configuration to include the makepot plugin:

[ "@wordpress/babel-plugin-makepot", {
	"output": "languages/myplugin.pot"
} ]

If you aren’t familiar with Babel or Webpack, all of this can be confusing. When learning something for the first time, it is often easier to look at a working example. Perhaps reviewing this webpack.config.js file will help.
Once you’ve set up this package you will run Webpack, which will then watch your JavaScript files for changes, automatically detect when you are using translation functions, and will automatically generate or keep your .pot file up to date in your languages/ directory.

Loading the Translation File

Essentially, once you’ve gone through the process of converting your .pot file into a .mo file, you are ready to register the translation file with WordPress in the traditional way.
In a plugin:

<?php
add_action( 'plugins_loaded', function () {
	load_plugin_textdomain( 'text-domain', plugin_basename( __DIR__ ) . '/languages' );
} );

or in a theme:

<?php
add_action( 'after_setup_theme', function () {
	load_theme_textdomain( 'text-domain', get_template_directory() . '/languages' );
} );

Passing Translations to JavaScript

WordPress will load our .mo files via PHP, which is sufficient for ensuring that any strings in PHP are translated. However, since we are trying to translate strings in JavaScript, we still have one more hurdle. We need to take the translations that PHP knows about and pass them to our JavaScript on the front-end.
Much like the old method of using wp_localize_script() to pass translated strings to our JavaScript, we need to make sure we bridge the gap between the back-end and the front-end.
Note: As of this writing the code that follows is currently dependent on the WordPress Gutenberg plugin being installed and active on a site. Once the new Gutenberg WordPress editing experience is rolled into core, there will not be a plugin dependency, but some of these functions may have slightly different names. However, most current use cases for JavaScript translation are within the context of creating Gutenberg blocks.
The WordPress i18n JavaScript library is presently loaded under the wp global variable in JavaScript. This means that in order to access the i18n library in WordPress, you would do so like this: wp.i18n.
What we want to do is leverage the setLocaleData() API call from the WordPress i18n library to register our translation(s):

<?php
add_action( 'wp_enqueue_scripts', function () {
	$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
	wp_enqueue_script(
		'my-script-js',
		plugins_url( "/assets/js/script{$suffix}.js", __FILE__ ),
		[ 'wp-i18n' ],
		filemtime( __DIR__ . "/assets/js/script{$suffix}.js" )
	);
	if ( function_exists( 'gutenberg_get_jed_locale_data' ) ) {
		$locale  = gutenberg_get_jed_locale_data( 'text-domain' );
		$content = 'wp.i18n.setLocaleData( ' . json_encode( $locale ) . ', "text-domain" );';
		wp_script_add_data( 'my-script-js', 'data', $content );
	}
} );

In this example, we are loading our script on the wp_enqueue_scripts hook. If you are creating a Gutenberg block, you may want to use the enqueue_block_assets or enqueue_block_editor_assets hook.
In order to follow best practices, we ensure that we load either the minified or non-minified JavaScript code depending on the state of the SCRIPT_DEBUG constant.
We register our custom script and give it a handle (my-script-js in this case). Only after we’ve registered or enqueued our script can we call wp_script_add_data(), which allows us to register custom data with our JavaScript file. Essentially, it will output and run any JavaScript code that we provide, but only when our file is loaded.
The JavaScript code we want to run is wp.i18n.setLocaleData(), which will be passed the locale data and our custom text domain. We are setting up the JavaScript code in PHP so we can easily pass our translations from the back-end to the front-end.
As you can see, we are using the gutenberg_get_jed_locale_data() function which accepts a text domain and returns the translation data in a structure that the setLocaleData() JavaScript method expects. We use json_encode() to make sure we convert the PHP arrays into JSON and we are good to go!
Note: Just for good measure, we’re wrapping our call to gutenberg_get_jed_locale_data() in a function_exists() check just in case the Gutenberg plugin isn’t active or WordPress changes the function name once Gutenberg is rolled into core. In either one of those cases, it would disable the JavaScript translations.
Have any questions? Leave a comment below.



Micah Wood is a WordPress Developer at Bluehost. A professional WordPress developer for over a decade, Micah has worked on sites for Fortune 100 companies, has released over a dozen WordPress plugins, is a frequent speaker at WordCamps, co-organizes the WordPress Gwinnett meetup, is a co-host on the WP Square One podcast and shares his knowledge by blogging on WordPress development topics.
Email: [email protected]

Learn more about Bluehost Editorial Guidelines

Write A Comment