7

When using @wordpress/create-block to scaffhold a plugin for a block, the generated bundle is automatically registered via the block.json metadata :

{
  ...
  "name": "my-block",
  "textdomain": "my-block",
  "editorScript": "file:./index.js",
  ...
}

No need to call wp_register_script myself. This is great since it automatically handles dependencies via the index.asset.php file generated in the build folder.

Following the procedure mentionned in the doc, I then create a JET translation file. Here is the procedure :

  1. Creating POT file with wp i18n make-pot . languages/my-block.pot
  2. Creating PO file with cp languages/test.pot languages/my-block-FR_BE.po
  3. Filling msgstr strings in my-block-FR_BE.po
  4. Adding line "Language: fr_BE\n" to my-block-FR_BE.po
  5. Creating JSON file with wp i18n make-json languages/my-block-FR_BE.po --no-purge

The JSON generated is appended with a md5 hash: my-block-fr_BE-cae574befd871d4f740fd8b719bac1db.json.

Now I have to call wp_set_script_translations in my init method :

function my_block_init() {
   register_block_type( __DIR__ . '/build' );
   wp_set_script_translations( 'my-block-script', 'my-block', plugin_dir_path( __FILE__ ) . 'languages/' );
}

This does not work.

In order to make it work, I have to register the script and enqueue it, loosing the ability to have dependencies automatically injected :

function my_block_init() {
    wp_register_script(
        'my-block-script',
        plugins_url('/build/index.js', __FILE__),
        array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-react-refresh-runtime')
    );
    wp_enqueue_script('my-block-script');
    register_block_type( __DIR__ . '/build' );

    wp_set_script_translations('my-block-script', 'my-block', plugin_dir_path( __FILE__ ) . 'languages/');
}

I also need to rename my JSON file to include the script handle instead of the automatically generated md5 hash. The "Load Translation File" section in the doc gives me the impression that this shouldn't be necessary when keeping the generated name, though I'm not sure of what I'm supposed to do here :

WordPress will check for a file in that path with the format ${domain}-${locale}-${handle}.json as the source of translations. Alternatively, instead of the registered handle you can use the md5 hash of the relative path of the file, ${domain}-${locale} in the form of ${domain}-${locale}-${md5}.json.

Is there a way to register JET translations for a script that is automatically registered via the block metadata ? And how can I use the generated name for the JSON file when registering my translation ?

Edit

Here's the folder structure :

app/plugins/my-block/
├── build
│   ├── block.json
│   └── index.asset.php
│   └── index.css
│   └── index.js
│   └── style-index.css
├── languages
│   ├── my-block.pot
│   └── my-block-fr_BE.po
│   └── my-block-fr_BE-my-block-script.json
├── src
│   ├── block.json
│   └── index.js
│   └── ...
└── my-block.php

And here's the content of the my-block.php file (plugin root) :

<?php
/**
 * Plugin Name:       My Block
 * Requires at least: 5.9
 * Requires PHP:      7.0
 * Version:           0.1.0
 * Author:            The WordPress Contributors
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       my-block
 */

function my_block_init() {
    register_block_type( __DIR__ . '/build' );

    wp_set_script_translations( 'my-block-script', 'my-block', plugin_dir_path( __FILE__ ) . 'languages/' );
}
add_action( 'init', 'my_block_init' );
6
  • 1
    I thought that it was automatically loaded if you used it via block.json the same way the script and styles were, is that not the case? Are you sure that plugin_dir_path( __FILE__ ) . 'languages/' is the actual folder? If you're not doing this in the main plugin folder then __FILE__ will have the incorrect value and plugin_dir_path will return an incorrect result, plugin_dir_path is expecting the main file of the plugin, not the current file the code is in.
    – Tom J Nowell
    Jul 4, 2022 at 13:22
  • the call to wp_set_script_translations is in the main plugin php file at the root of the plugin. Jul 4, 2022 at 13:28
  • have you checked the actual value directly and made sure it is indeed resolving to the correct directory? Ignore the fact that it's supposed to be or that it should be correct, and check if it actually is correct.
    – Tom J Nowell
    Jul 5, 2022 at 13:14
  • Thanks for the follow up @TomJNowell. I did check the path, and it works when I register and enqueue the script manually so I would think WP can see the language files. My workaround doesn't work on my staging environment for some reason, so I'm back at trying to make it work automatically. I added the folder structure in the OP if that can help, maybe the structure generated with @wordpress/create-block is not working for i18n'd blocks. Jul 6, 2022 at 13:01
  • can you make sure that you include not just your functions, but the add_action calls you used, and make sure they're in the order they are in your codebase too. It's possible the order things are called in is causing issues, eitherway seeing the code will help eliminate that question. ( note that the packages you're using to generate this have changed over time so simply referring to what the package does as the answer isn't enough )
    – Tom J Nowell
    Jul 6, 2022 at 13:26

4 Answers 4

10

@drskullster already posted a good answer, but I thought I should share the following which explains why wp_set_script_translations() needs to be called manually:

  1. Yes it's true that WordPress will automatically register and enqueue the block editor script. So we do not need to manually register/enqueue that script!

  2. WordPress will also automatically set the script's translations, but only if the block metadata file (i.e. block.json) specifies a valid text domain (e.g. "textdomain": "gutenpride") and that the script has wp-i18n as one of its dependencies. See register_block_script_handle().

  3. WordPress uses wp_set_script_translations( $script_handle, $metadata['textdomain'] ) to register/set the translations for the script, which means the 3rd parameter (the file path) is not specified and therefore, WordPress will try to load the translation files from the wp-content/languages/plugins folder.

    So for example, if my-block-fr_BE-dfbff627e6c248bcb3b61d7d06da9ca9.json was the JSON file name, then WordPress would attempt to load wp-content/languages/plugins/my-block-fr_BE-dfbff627e6c248bcb3b61d7d06da9ca9.json.

    And thus, you should copy that file to the wp-content/languages/plugins folder. That way, your main plugin file would only need to call register_block_type() and that's it — no need to worry about the JSON file name or MD5 hash, and you also would not need to manually call wp_set_script_translations().

But if you don't want to having to copy the files, then yes, you will need to manually call wp_set_script_translations(), but remember that the script handle is in the form of <block name>-editor-script where <block name> is the block name (which is the name property in block.json), but with slashes (/) replaced with hypens (-).

  • So if the block name is my-block, then the script handle would be my-block-editor-script. Or if the name is create-block/gutenpride, then the handle would be create-block-gutenpride-editor-script. See generate_block_asset_handle() for more details.

So I hope that helps, and FYI, the following is what I had in my main plugin file (wp-content/plugins/gutenpride/gutenpride.php):

function create_block_gutenpride_block_init() {
    register_block_type( __DIR__ . '/build' );

    // Load MO files for PHP.
    load_plugin_textdomain( 'gutenpride', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );

    // Load JSON files for JS - this is necessary if using a custom languages path!!
    $script_handle = generate_block_asset_handle( 'create-block/gutenpride', 'editorScript' );
    wp_set_script_translations( $script_handle, 'gutenpride', plugin_dir_path( __FILE__ ) . 'languages' );
}
add_action( 'init', 'create_block_gutenpride_block_init' );
  • Note: I followed the same steps as in your procedure, but I got an extra step — I created an MO file for use in standard PHP translations, e.g. <?php _e( 'text', 'gutenpride' ); ?>.

Additional Notes

  1. If the 3rd parameter for wp_set_script_translations() is specified and the path/folder contains a JSON translations file named <text domain>-<locale>-<script handle>.json, e.g. gutenpride-fr_BE-create-block-gutenpride-editor-script.json, then WordPress will attempt to load that first.

    So if the file doesn't exist, then WordPress will attempt to load the default one with the MD5 hash, e.g. gutenpride-fr_BE-dfbff627e6c248bcb3b61d7d06da9ca9.json.

  2. load_script_translations() is the function used to load translations from script translations files, i.e. that function opens the file and reads its content; and the function applies filters like load_script_translation_file which you may find useful when debugging.. :)

  3. My "Gutenpride" block was tried & tested with WordPress v6.0, and the latest version of the @wordpress/create-block package at the time of writing.

6
  • 1
    Amazing answer, thank you ! Can you also explain why we should call wp_set_script_translations during the init hook and not during wp_enqueue_scripts as suggested above ? Jul 11, 2022 at 12:47
  • 1
    See the source code of _wp_scripts_maybe_doing_it_wrong() which is used by wp_register_script() (and wp_enqueue_script(), too). And in that source, _doing_it_wrong() is skipped if the init hook has run, which means it can be used to register scripts.
    – Sally CJ
    Jul 11, 2022 at 14:49
  • 1
    And although indirectly, register_block_type() will automatically register the block editor script (and other scripts like viewScript, if set in the block metadata), so I wouldn't say we should call wp_set_script_translations() during the init hook, but it's that we can call it because the script has already been registered via register_block_type(). You can also see the official Gutenberg examples also used the same hook, e.g. this.
    – Sally CJ
    Jul 11, 2022 at 14:52
  • automatically created handle is in the form block-name-editor-script, but if slash is in the name, then it is replaced by hyphen - so for "maintainer/block" is "maintainer-block-editor-script"
    – Lovor
    May 22, 2023 at 10:25
  • My answer did state that "slashes (/) replaced with hypens (-)", but thanks @Lovor :)
    – Sally CJ
    May 29, 2023 at 17:45
1

Here's how I managed to make it work :

I found the handle of the script registered automatically with register_block_type by printing the $this->registered variable inside the WP_Scripts->set_translations method, it was called something like my-block-editor-script.

By using this handle inside the call to wp_set_script_translations I was able to remove the code that registered and enqueued the script, and I also could use the generated name for the json files.

Here's what my main plugin file looks like now :

<?php
/**
 * Plugin Name:       My Block
 * Requires at least: 5.9
 * Requires PHP:      7.0
 * Version:           0.1.0
 * Text Domain:       my-block
 */

function my_block_init() {
    register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'my_block_init' );

function my_block_set_translations() {
    wp_set_script_translations( 'my-block-editor-script', 'my-block',  plugin_dir_path( __FILE__ ) . 'languages/' );
}
add_action( 'init', 'my_block_set_translations' );

And the path to the JET file: languages/my-block-fr_BE-dfbff627e6c248bcb3b61d7d06da9ca9.json.

0

Here's the problem:

    wp_set_script_translations( 'my-block-script', 'my-block', plugin_dir_path( __FILE__ ) . 'languages/' );
}
add_action( 'init', 'my_block_init' );

init is too early, WP hasn't registered your blocks assets yet, it knows about the block but that doesn't mean it instantly did all the work all at once in a single atomic step. If it registered and enqueued the scripts and styles on init it would generate warnings and doing it wrong notices!

And as the docs say:

Works only if the script has already been registered.

https://developer.wordpress.org/reference/functions/wp_set_script_translations/

It's too early!!! A useful thing to do is check the return value which is a true/false indicating success.

Instead, call it on the wp_enqueue_scripts hook the same way the docs say wp_enqueue_script and others should be called, but give it a higher priority value on the hook to ensure it runs afterwards.

Here's a useful example from the official WP docs:

https://developer.wordpress.org/reference/functions/wp_set_script_translations/#comment-5072

6
  • Still not working. When hooking to wp_enqueue_scripts with a priority of 100, the wp_set_script_translations call returns false. When registering and enqueuing manually, the script returns true. Should I set the handle value to something specific ? Do you know how Wordpress reconciles the automatic registering with register_block_type and the wp_set_script_translations call ? Jul 6, 2022 at 16:24
  • hmmm, my suspicion is that this is being conditionally loaded only when the block is in use
    – Tom J Nowell
    Jul 6, 2022 at 16:33
  • I printed the $this->registered variable inside the WP_Scripts->set_translations method, and got the handle from that: my-block-editor-script. Using this handle, the wp_set_script_translations returns true without having to manually register and enqueue the script, so that's progress ! But... It still doesn't work (my workaround method doesn't work anymore so it may be due to other changes I made while debugging). Do you know where I can look in the source code for the actual injection of the messages inside the client's wp.i18n variable ? Jul 6, 2022 at 16:33
  • have you tried doing this without your workaround?
    – Tom J Nowell
    Jul 6, 2022 at 20:41
  • The workaround I'm referring to is registering and enqueuing manually. I did try without it, and now that I know the handle generated by register_block_type I don't need the workaround anymore. Jul 7, 2022 at 7:06
0

My issue was a wrong md5 hash.

It used the src/ path to generate the md5, while wordpress was using the final build/ path to generate the md5 and therefore it failed.

I fixed this by using --exclude option for make-pot and updated the po files from the pot. Now only build/* paths are present and the correct md5 is generated:

    wp i18n make-pot . languages/my-block.pot --exclude=node_modules,dist,src
    wp i18n make-json languages/ --no-purge

(it's also possible to define --include=build)

New contributor
Delta is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.