Internationalizing and localizing block based plugins is an important stage in the creation of WordPress blocks that can be used worldwide. Once it’s been internationalized a plugin can be delivered in any locale that WordPress supports; over 200 at the last count.
In this post I summarize the process I use to automatically perform internationalization ( i18n ), translation and localization ( l10n ) of my block based WordPress plugins from US English to UK English and the bbboing language.
Using easy to use translation tools, internationalized plugins can be made available in many other languages.
Overview
The overall process for internationalization, translation and localization of translatable strings in a WordPress plugin is as follows:
- Write the code so that the translatable strings can be extracted from the relevant files
- Extract the strings using
makepot
into a.pot
file - Translate the strings into each required locale, producing one .
po
file per locale - Convert the translated
.po
files into.mo
files, usingmsgfmt
if not already done bypoedit.
- Extract the strings required in the JavaScript routines into
.json
files - Ensure the localized files are loaded by the plugin.
These steps will be explained in the following sections.
There are two other sections
Install tools
Prerequisite to performing these tasks you need to install the required tools.
- npm – used to build the JavaScript blocks using
wp-scripts
. - WP-CLI – used to run the internationalization and localization.
- l10n – used to run the automated translation into the target locales.
npm – Node Package Manager
This is used to control the build of the blocks. See Block Editor Handbook: @wordpress/scripts
WP-CLI
– Command line interface for WordPress
The WP-CLI command line interface is required to run the internationalization and localization routines. Download from wp-cli.org
To get the routine to extract strings from the block.json
files you need to install the latest version of the i18n-command
.
wp package install [email protected]:wp-cli/i18n-command.git
l10n
– automatic translation to en_GB and bb_BB
Download the oik-i18n plugin from bobbingwide/oik-i18n. It requires oik-batch to run it.
Internationalization ( i18n )
A major part of internationalization is the process of ensuring that the translatable strings in your code are extractable and include enough context to enable translators to perform their role.
The translatable strings should be written in US English. Examples below for a plugin with a text domain of text-domain
. The __()
function returns the translated string. For more information see How to internationalize your plugin.
PHP
__( 'Color', 'text-domain' );
JavaScript
import { __ } from @wordpress/i18n;
__('Color', 'text-domain' );
block.json
{ "title": "Color",
"description": "Color",
"textdomain": "text-domain"
}
Extract strings
All of the translatable strings need to be extracted into a single .pot
file.
For block based plugins this is done using npm run makepot
, where the makepot
command is defined in package.json
.
"makepot": "wp i18n make-pot . languages/sb-starting-block.pot --exclude=node_modules,vendor,src/*.js",
The npm
command invokes WP-CLI
‘s i18n
command to run make-pot
against the current directory, producing the sb-starting-block.pot
output file in the languages
folder.
- Makepot extracts strings from
.php
files,.js
files and certain.json
files. - The
--exclude
parameter indicates which files to ignore. - Note: the only JavaScript file that we actually want processed is in the
build
folder;build/index.js
. - But since the
block.json
files are in thesrc
folder we have to excludesrc/*.js
.
For some plugins additional parameters are required.
"makepot": "wp i18n make-pot . languages/oik.pot --ignore-domain --domain=oik --exclude=node_modules,vendor,src/*.js,tests",
For the oik plugin, for example, we need --ignore-domain
to enable makepot to extract strings regardless of the value of the text-domain literal used in the (PHP) source. We therefore need to use --domain=oik
to indicate the text-domain to use. This is required to enable makepot to extract translatable strings from PHP shared libraries, where the text-domain is null.
Additionally, when using --ignore-domain
, due to a limitation of the current string extraction process, it is necessary to omit the textdomain
attribute from the block.json
files and provide the value at run time.
This is achieved using a block_type_metadata
filter hook, which sets the textdomain
for each block, if it’s not set and the block prefix matches the expected value for the plugin.
add_filter( 'block_type_metadata', 'oik_block_type_metadata', 10 );
function oik_block_type_metadata( $metadata ) {
if ( !isset( $metadata['textdomain']) ) {
$name = $metadata['name'];
$name_parts = explode( '/', $name );
$textdomain = $name_parts[0];
if ( 'oik' === $textdomain ) {
$metadata['textdomain'] = $textdomain;
}
}
return $metadata;
}
Translation
Translation – automated
For my plugins I automatically translate the US English strings into UK English ( locale en_GB
) and the obfuscated bbboing language ( locale bb_BB
). The process is fully automated and is performed by npm run l10n
.
"l10n": "l10n sb-starting-block",
For each target locale l10n
produces a .po
file, then this is formatted using msgfmt
into the .mo
file.
The translation into UK English is performed for two reasons.
- To deliver the translations with correct spelling for the UK English community.
- To check that the original spellings are US English.
I can test that my plugins have been correctly internationalized and localized into the bbboing language using PHPUnit.
Sample translations
US English ( en_US ) | UK English ( en_GB ) | bbboing ( bb_BB ) |
---|---|---|
Color | Colour | Cloor |
Check – context banking | Cheque | Cehck |
Starting block | Starting block | Sattrnig bolck |
Translation – manual
For other languages the translation has to be performed manually. This can be done using a tool such as poedit
. Using poedit
, when the .po
file is saved the .mo
file is generated automatically.
Localization ( l10n )
For strings which need to be translated in the block editor a further step is required. The strings from the relevant .js
files needs to be localized into a locale specific .json
file. This is performed using npm run makejson
.
- This command creates a locale specific file, called
locale-MD5.json
, for each JavaScript file found in the.po
file. - The
MD5
part is the string returned bymd5()
for the file name - md5 for
build/index.js
isdfbff627e6c248bcb3b61d7d06da9ca9
.
The final part of the localization is performed by the routines that enable the run time string translation. For strings in .php
files this is achieved by loading the required locale’s text domain, the .mo
file.
load_plugin_textdomain( 'sb-starting-block', false, 'sb-starting-block/languages' );
load_plugin_textdomain()
needs to be run before the blocks are registered in the server, as the translation of strings in block.json
is performed when each block is registered.
$args = [ 'render_callback' => 'oik_sb_sb_starting_block_dynamic_block'];
register_block_type_from_metadata( __DIR__ . '/src/starting-block', $args );
register_block_type_from_metadata( __DIR__ . '/src/second-block' );
For the JavaScript strings to be loaded we use wp_set_script_translations
(). The translations are associated with the script handle that’s generated for the editor script.
/**
* Localise the script by loading the required strings for the build/index.js file
* from the locale specific .json file in the languages folder.
* oik-sb/sb-starting-block
*/
$ok = wp_set_script_translations( 'oik-sb-sb-starting-block-editor-script', 'sb-starting-block' , __DIR__ .'/languages' );
For multi-block plugins where the block.json
file refers to the build/index.js
file using a relative path that’s not ./build/index.js
, we have to implement a special filter hook. This adjusts the relative path to match the one that was used to create the locale-MD5.json
file generated by makejson
. See https://github.com/bobbingwide/sb-post-edit-block/issues/5#issuecomment-899015787.
add_filter( 'load_script_textdomain_relative_path', 'oik_sb_sb_starting_block_load_script_textdomain_relative_path', 10, 2);
function oik_sb_sb_starting_block_load_script_textdomain_relative_path( $relative, $src ) {
if ( false !== strrpos( $relative, './build/index.js' )) {
$relative = 'build/index.js';
}
return $relative;
}
Finally, since multiple blocks are delivered in the single build/index.js
file we only need to specify the following attributes in one of the block.json
files.
"editorScript": "file:../../build/index.js",
"editorStyle": "file:../../build/index.css",
"style": "file:../../build/style-index.css"
This ensures that the JavaScript for all the blocks delivered by the plugin is only enqueued once.
Further work?
I feel I need to investigate whether or not my block based plugins can be successfully translated into other popular languages.
- I’ve used this process doucmented above to internationalize and localize nearly all of my block based plugins.
- I’ve manually tested the translation to bbboing in the block editor.
- A couple of the plugins are available from wordpress.org: eg oik, uk-tides
But I don’t yet know if the translation files created using the WordPress translation tools will produce satisfactory results for these plugins.
- I don’t understand how wordpress.org knows which files to use as the input to
makepot
andmakejson
. - I also don’t know what process wordpress.org uses to produce the
.pot
file for thereadme.txt
file
My process for multiple block plugins seems rather complex. This could be due to some current limitations of the tools. Should I consider raising some feature requests/issues against these tools? Or should I look at how other plugins support internationalization and localization?
References
- See also Migrating block based plugins from webpack to wp-scripts
- https://developer.wordpress.org/plugins/internationalization/
- https://developer.wordpress.org/block-editor/how-to-guides/internationalization/
- Sample internationalized and localized plugin: bobbingwide/sb-starting-block
- Localization of Full Site Editing themes
- How I’m testing the internationalization and localization of my WordPress plugins