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

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

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.

Resources