Table of contents
Intro
I’m writing this article because I just ran into this issue today.
I’ve created a custom Gutenberg block that uses view.js
to render the content on the front-end
Usually I use render.php
to do that, but in this case the front-end block rendered a React application.
This now had to be translated and it was a bit annoying until I understood all the details of how to do it.
I’m going to walk you through the steps of adding translation to a custom WordPress Gutenberg block that relies on JS to render the front-end.
Setup
I’m using the npx wordpress/create-block
to get the scaffolding and the initial setup for my blocks.
You should end-up with a folder structure similar to the one below:
├── src
├── block-name
├── block.json
├── edit.js
├── editor.scss
├── index.js
├── render.php
├── style.scss
├── view.js
Since in most cases I only need to translate the front-end render.php
using the __()
, it works fine and there is no issue.
But in this case I’m not using the render.php
, but instead the view.js
.
My block.json
looks something like this.
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "custom-block",
"version": "0.1.0",
"title": "Custom Block",
"category": "dm-block-general",
"description": "Some description goes in here",
"keywords": [ "custom"],
"supports": {
"anchor": true,
"color": {
"text": true,
"background": true
}
},
"example": {
},
"textdomain": "my-text-domain",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScript": "file:./view.js"
}
Internationalization
What you need to do is make sure you add
import { __ } from "@wordpress/i18n";
in your view.js
. This way you can start using the internationalization with your text-domain inside the JS file.
view.js
will look like this:
import { __ } from "@wordpress/i18n";
return (
<section
className="custom-block"
>
{__("Here is some text that needs to be translated", "my-text-domain")}
</section>
);
Generate PO/POT and JSON files
If you don’t have a PO or POT file, you’ll need to generate them because we’ll need them later.
You can run make-pot
wp i18n make-pot
Next you need to generate the JSON file for the JS translation.
For your PHP you use the PO/MO files, but for JS you need to use JSON.
You can run the command make-json
wp i18n make-json
if you already have an PO file you can run
wp i18n make-json es_ES.po
Add --no-purge
at the end if you want the PO to remain untouched.
wp i18n make-json es_ES.po --no-purge
This will generate a JSON file based on the PO that you indicated.
The new JSON file might have a name like this:
my-text-domain-es_ES-1234556789.json
make sure you change the name to this
[text-domain]-[language]-[script-handle].json
We’ll get to the actual naming a bit lower on the page, showing you 2 different examples.
Load JSON translation
Next we need to make sure the text-domain is registered and that the script translation is loaded correctly.
TBH I don’t think you need to do the text-domain explicitly, it’s already mentioned in the block.json
and it seems WordPress picks it up automatically, but I’m going to add it here anyway.
function dm_load_theme_textdomain() {
load_theme_textdomain( 'my-text-domain', get_template_directory() . '/languages' );
// Load the translation for the block
wp_set_script_translations( 'customb-block-view-script', 'my-text-domain', __DIR__ . '/languages/' );
}
add_action( 'init', 'dm_load_theme_textdomain' );
As you can see in the code above, I’m using /languages/
as the path for all translation files. That is where they are expected to be.
And now for the confusing part, for me at least, loading the translation JSON file to the block JS.
It requires
wp_set_script_translations( string $handle, string $domain = 'default', string $path = '' )
$handle
is the script handle the textdomain will be attached to.$domain
is the text-domain$path
is the full file path to the directory containing translation files.
Determine script handle for Gutenberg blocks
For our example with a custom Gutenberg block, the handle is the thing that confused me.
The handle is composed from the block name (in block.json
) + -view-script
, if you’re referencing the view.js
or block name + -edit-script
, if you’re referencing the edit.js
.
The name will be custom-block
which makes the handle custom-block-view-script
or custom-block-edit-script
.
And if you used a block name that has a grouping / namespace, like the one below.
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "dm-blocks/custom-block",
}
The name will be dm-blocks-custom-block
which makes the handle dm-blocks-custom-block-view-script
or dm-blocks-custom-block-edit-script
.
For me the confusion started from the fact there is nowere a mention of -edit-script
or -view-script
.
Correct JSON file naming
Now, with this information in mind we can rename the language JSON correctly.
It will be my-text-domain-es_ES-custom-block-view-script.json
or if you use a grouping name, it will be my-text-domain-es_ES-dm-blocks-custom-block-view-script.json
.
Once you’ve figure this out and it’s all in place, the translation works great.