WordPress is used all over the world and is translated into many non-english languages. Just like WordPress we need our plugins, themes, and blocks to provide that same access. Because internationalization (often referred to as i18n) is such an important aspect of WordPress, an API is exposed to help developers implement i18n features into their plugins and themes.
With the introduction of Gutenberg we need to bring i18n into our JavaScript code and WordPress has already provided the same API for us to use. I’m going to walk you through getting i18n setup for your Gutenberg block. I will not be going through how to use the i18n API’s WordPress provides but you can check out the official documentation linked below:
Getting started
In Getting Started with Block Development in ES.Next, we created a simple block with a minimal build process using webpack and Babel. We’re going to use that as a base to work from. Work through that article to get the base setup or grab the code from GitHub.
We are going to be using two utilities on the command line that will generate the proper .pot, .po, .mo, and .json files for our translations. You will need to install these if you haven’t already:
- WP-CLI – The command line interface for WordPress. WP-CLI won’t need an installation of WordPress to use the commands we’ll be using.
- gettext – internationalization and localization system used for writing multilingual programs. We will be utilizing the
msgfmt
utility that’s included with gettext.
Although we don’t have any translatable strings in our PHP code, we’re still going to setup and load our language files for the entire project.
Updating our starting point
From implementing i18n into my own projects I found that changing how the scripts and styles for our block are registered needs to change. Instead of hooking into enqueue_block_assets
and enqueue_block_editor_assets
we will utilize the register_block_type
function within the init
hook.
After removing the gwg_block_assets
and gwg_editor_assets
functions, we will create a new function named gwg_register_block_type
with the following contents.
In this function we first make sure that Gutenberg is active before registering the related styles and scripts. We then use the register_block_type
to tell WordPress about the scripts and styles we’ve registered, if they should load in the editor or in the theme, and which block they belong to. It’s important to use register_block_type
here so that when we tell WordPress about our translation files, the slugs will be registered in the background on time.
Make your strings translatable
We will be utilizing the @wordpress/i18n
component which provides the i18n functions we’re used to in PHP but for JavaScript. Within our plugin’s index.php
file, we’re going to update our wp_register_script
call to include this dependency.
Now we’ll use the __()
(double-underscore) function to retrieve the translation of the text pass to it in our JavaScript code. First import the function from the i18n component accessed from the wp
global variable in our block.js
file.
Next we’ll update all translatable strings in our code just like we would in PHP.
Generate the language files
Using WP-CLI we are going to generate our .pot
file using the wp i18n make-pot
command. This will search all files (excluding vendor/
, node_modules/
, and others for strings passed to our i18n functions with the defined text domain for our plugin and generate the .pot
file used as a template for translators to use.
Check the documentation for more information about the available parameters if you’d like to customize it. I like to make sure the “Last-Translator” and “Language-Team” headers are passed into the generated file for reference. When using the WP-CLI command you can pass these headers as a json string to the --headers
flag.
$ wp i18n make-pot ./ ./languages/gwg.pot --headers='{"Last-Translator":"JR Tashjian <jr@getwithgutenberg.com>","Language-Team":"Get With Gutenberg <info@getwithgutenberg.com>"}'
The WP-CLI command will create the langauges/
directory with our gwg.pot
file within it. If you take a look at that file you will see references toblock.js
file with our translatable strings.
Now that we have our base translation file, let’s go ahead and add the en_US
.po
file since that is the locale I’m writing this for. All we have to do is copy our gwg.pot
file to gwg-en_US.po
.
$ cp ./languages/gwg.pot ./languages/gwg-en_US.po
Let’s make this easily repeatable by utilizing our npm scripts. Let’s make a new script called i18n-pot
that we can run with npm run i18n-pot
to automatically do the above two steps for us.
We now have the translation file for our plugin to use. However this is only going to be used on the PHP side right now. The way the @wordpress/i18n
component works is that it will read the data from a localized variable. This localized variable is generated by PHP when we load a .json
file containing these translated strings through the wp_set_script_translations
function in WordPress (available in version 5.0.0).
We’re going to use WP-CLI again to generate our .json
file using the wp i18n make-json
command. This command will extract the strings referenced in our build.js
file from the gwg-en_US.po
file and create our .json
file.
$ wp i18n make-json languages/
This WP-CLI command will create a new .json
file within our languages/
directory for every .po
file within it. You’ll notice these files have an md5 hash appended to them which is a hash of the file path. We will change this because the path used for the md5 hash doesn’t match the one the wp_set_script_translations
function in WordPress creates. This might be a bug or I might be missing something but after diving into the code I found renaming the file was a quick fix. We will rename the md5 hash portion of the filename to our enqueued script slug (gwg-block
) which is the first file the function looks for.
$ mv languages/gwg-en_us-5bd7b3c720e0df736021f834799d5ef3.json languages/gwg-en_US-gwg-block.json
Again, let’s make this easily repeatable for us by adding a new script called i18n-json
that we can run with npm run i18n-json
. I’m going to add a call to the unix tool rename
to rename all the md5 hashes our .json
files have to gwg-block
.
$ wp i18n make-json languages/ && rename 's/(gwg-[a-zA-Z_]+-)[^\.]*(\.json)/$1gwg-block$2/' ./languages/*
And here it is altogether in our package.json
file.
And finally the last portion of generating our translation files, the .mo
files. We will use msgfmt
to generate a .mo
file for every .po
file we have in the languages directory.
$ for file in `find . -name "*.po"` ; do msgfmt -o ${file/.po/.mo} $file ; done
And here it is in out package.json
file.
Now let’s add it all together into a single command. We’ve prefixed each command with i18n
and I think that’s the perfect name to consolidate all the commands. We can then run every command with npm run i18n
.
Putting it all together now
We have translated the strings in our code and we’ve generated the required files used to provide new translations to our project. We now only need to tell WordPress where these files are for our translations to load properly. We need to hook into the init
action and call two functions in order for everything to work properly. First, we’ll add a new function to handle loading our translation files for PHP to use.
Now we can update our gwg_register_block_type
function at the end with a call to wp_set_script_translations
.
If you create a new post or page with Gutenberg and view the page source in your browser, search for gwg-esnext-i18n/block.build.js
. You should see this snippet of code making your translated strings available to the @wordpress/i18n
component.
Conclusion
There you have it. Internationalization is a part of plugin and theme development that should not be overlooked by developers. With only a little bit of additional work you can help bring your code to an entirely new audience and help others contribute to WordPress by providing translations.
I’ve put all the code for this walk-through up on Github for reference.
Oh dude Thanks, really.
It was driving nuts. It didn’t worked just for one of my files (the other was just fine).
I don’t know why, but renaming it without the md5 just worked fine.
I’m digging further to understand the reason why.
Thanks again!
Ok so: md5 only works when the file is provided by wp.org directly.
For plugins in repository, we don’t have to generate the files ourselves (but I still need to because wp.org can’t generate of my files).
For the plugins in the repository, just use wp_set_script_translations() with only 2 arguments.
don’t use load_plugin_textdomain().
Don’t even make a languages folder