I am struggling to load translation in JavaScript for my simple plugin. Translation works for PHP, but not in JS. What seems to be the problem, how can I debug this?

  1. I have loaded text domain in my plugin method and this works ok. My text domain is instantsearch, locale for my language is hr

    load_plugin_textdomain('instantsearch', FALSE, basename( dirname( __FILE__ ) ) . '/languages/'); // returns true
    var_dump(__('No results', 'instantsearch')); // This shows correct translation for my language
  2. I have generated .json file with WP CLI

    wp i18n make-json languages/
  3. This gives me new file /myplugin/languages/instantsearch-hr-hash.json. My JS file is assets/instant-search.js and somewhere I have read that I need to manually rename that hash. I just copied that file two times and renamed them to the following just to try it out, so something out of those 3 should be working :)

  4. I have registered the script for translation

    wp_register_script('instant-search', plugins_url('assets/instant-search.js', __FILE__), array('jquery', 'wp-i18n'), false, true);
    wp_set_script_translations('instant-search', 'instantsearch', plugins_url('languages', __FILE__));
  5. In the script at the very top I have tried this, but it doesn't give me translation like in PHP, it just shows english default string

    console.log(wp.i18n.__('No results', 'instantsearch'));
  6. Here is example of json

    {"translation-revision-date":"2019-12-31 13:41+0100","generator":"WP-CLI\/2.4.0","source":"assets\/instant-search.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"hr","plural-forms":"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);"},"No results":["Nema prona\u0111enih rezultata"]}}}
  7. I know I can use wp_localize_script() to move string from PHP to JS but from WP 5.* we should be able to do it

  • you use wp_register_script instead of wp_enqueue_script then translation are not loaded. then in wp_set_script_translations you have to set the path look in the documentation
    – Kaperto
    Commented Dec 31, 2019 at 15:06
  • I use both, first I register and then a little bit later enqueue. I have updated my code. I tried setting wp_set_script_translation with a path (which correctly points to languages directory in plugin) but unfortunately still no success
    – Marko
    Commented Dec 31, 2019 at 15:46
  • I tried you json content with a file instantsearch-hr-instant-search.json and it works. then I think you are near of having a good result but I don't know how to help you.
    – Kaperto
    Commented Dec 31, 2019 at 16:43
  • 2
    I'm having the same issue. Did you ever solve this? Commented May 1, 2020 at 20:57
  • The same and it looks like the @JosiahSprague's answer doesn't work for me. I tried a lot variations. In the page, I can see an inline script and empty i18n JSON '{ "locale_data": { "messages": { "": {} } } }'. It's right above my app.js. wp_set_script_translations() returns true. get_locale() is 'nl_NL' and is correct. Commented Aug 5, 2020 at 18:22

5 Answers 5


I was having the same problem and this is how I solved it:

First, the generated JSON file has some errors. You need to update where it says messages with your text domain. There are three places where that needs to be changed. I also had to change the lang attribute to be all lowercase with a dash. Here are the fields I changed:

  "domain": "my-text-domain",
  "locale_data": {
    "my-text-domain": { // Instead of "messages"
      "": {
        "domain": "my-text-domain",
        "lang": "es-es"

Second, the file name with the md5 hash tends to have the wrong md5 hash, so it's best to rename the file to {domain}-{locale}-{script-name}.json, so mine became my-text-domain-es_ES-my-script-name.js.

Third, the wp_set_script_translations function needs to be called with the right path, and attached to the right hook. This is what I had to do, since I was localizing an admin-side script:

function enqueue_scripts() {
    wp_set_script_translations( 'script-name', 'my-text-domain', plugin_dir_path( dirname(__FILE__) ) . 'languages' );
add_action( 'admin_enqueue_scripts', 'enqueue_scripts' );

Try echoing out the value of plugin_dir_path( dirname(__FILE__) ) . 'languages' to make sure you're getting the path where your translation files are.

Once I sorted out all of these little details, my translations started working, so I hope this helps someone else!

  • 1
    It doesn't work for me. I read a lot of articles and posts. It should work, but... I can't figure out where I'm wrong. Perhaps, now you can add some additional ingo by the question. Commented Aug 5, 2020 at 18:38
  • It seems that replacing message with the text domain has no effect, instead having a wrong MD5 hash can cause the issue, as you mentioned later. I added some details about that case here: wordpress.stackexchange.com/a/415545/231798
    – xonya
    Commented Apr 22, 2023 at 20:53

TL;DR: run npm run build before executing wp i18n commands!

The problem described in this post also happens if you generate the .pot and .po files before generating the production js files. This will cause the wp CLI to produce only one JSON file (instead of two) and the app will not be able to find the one with the desired hash.

Suppose that you have a file src/App.js that is transformed to build/app.js by your npm scripts.

If you haven't produced the build/app.js file yet and you generate the POT file using wp i18n make-pot you will end having something like this in your POT file:

#: src/App.js:11
msgid "My label"
msgstr ""

The same will be reported in the .po files and only one JSON file will be produced, e.g. my-plugin-it_IT-14b1e33d5bf5649597cdc0e4f684dadd.json

If you run npm run build and then wp i18n make-pot, you can see that both the source file and the production file will be listed:

#: src/App.js:11
#: build/app.js:462
msgid "My label"
msgstr ""

Starting from this POT file, wp i18n make-json will produce 2 JSON files:

  • my-plugin-it_IT-14b1e33d5bf5649597cdc0e4f684dadd.json (src/App.js)
  • my-plugin-it_IT-b8c773ce202d23e4e6718b08f32ecf3c.json (build/app.js)

To verify the expected hash you can use the following command:

echo -n "build/app.js" | md5sum

Moreover, in your JavaScript files you may have to use

const { __ } = wp.i18n;

instead of

import { __ } from '@wordpress/i18n';

Inside the wp_set_script_translations function, you use plugins_url('languages', __FILE__) which is not the correct path.

You should use plugin_dir_path(__FILE__) . 'languages/'


I'll leave it here. Perhaps it will help someone. I had the same issue. I did all steps and all looked working, but in JS __('Hello world', 'textdomain'); didn't work.

There are a lot of recommendations about the JSON file for example to use 'nl' instead 'nl_NL', etc. For today (2020.08.06) you shouldn't change a json-file in any way except its name as custom_dir/{domain}-{locale}-{handle}.json.

The base steps:

  1. PHP code:
load_theme_textdomain( 'textdomain', get_template_directory() . '/languages' );
// ...
wp_enqueue_script( 'scripts', asset_url( 'js/app.js' ), [ 'jquery', 'wp-i18n' ], null, true );
wp_set_script_translations( 'scripts', 'textdomain', get_template_directory() .'/languages/js' );
  1. I use the Poedit application for creating & translating the nl_NL.po file.

  2. With WP-CLI I generate wp i18n make-json ./languages ./languages/js --no-purge --pretty-print a json-file and I reneme it to ./languages/js/textdomain-nl_NL-scripts.json.

The essential thing: The author's mistake is plugins_url('languages', __FILE__) in wp_set_script_translations();. My mistake was a simple get_template_directory_uri() instead of get_template_directory(). But the function requires "$path The full file path to the directory containing translation files".

All you need is placed here: https://developer.wordpress.org/block-editor/developers/internationalization/.


I too could not get @wordpress/i18n to translate strings, despite following the directions on https://developer.wordpress.org/block-editor/how-to-guides/internationalization/ under "Provide Your Own Translations".

The following worked for me:

  1. I was translating to Spanish and using just 'es' in my po file, which used the same in the generated json files. I changed that to es_ES.

  2. I had multiple components importing @wordpress/i18n and when I ran "wp i18n make-json languages", separate json files were created for each with a hash in the file name. There is one json file for build/index.js - that file contained all translations and is the only one I needed.

  3. I changed the json filename of the file referencing build/index.js (in #2 above) to: {Text Domain}-{locale}-{script-name}.json, so mine became localestr-es_ES-scriptname.json. In the above, localestr is my text domain set in my plugin and in my translation strings, ex: __('Back', 'localestr '). scriptname is the name used with wp_enqueue_script('scriptname')

Many people, in many places say wp_set_script_translations( 'myguten-script', 'myguten', plugin_dir_path( FILE ) . 'languages' ); Needs forward slashes before and/or after languages. I found a forward slash around 'languages' are not needed at all.

I was also able to leave "domain":"messages" in my json file. That works for me, and I did not need to change "messages" in 3 places to my Text Domain.

I hope this helps someone else. It's super frustrating to debug because there is so little documentation, debug options, and the documentation is missing a very important bit about the generated json files with wp-cli are not named correctly to automatically get set up with wp_set_script_translations.

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.