How to Get Your Plugin Strings Translatable by Gutenberg

world map poster near book and easel
Photo by Nicola Nuttall on Unsplash

Getting your strings translated by Gutenberg isn’t the most straight forward process. You’ll need to make sure your compressed scripts keep the __ variable intact, so that can process your strings.

In this tutorial, I’ll be making use of Create Guten Block and WP CLI to demonstrate how to get translatable strings. If you don’t have WP CLI installed, the installation instructions are pretty straightforward.

Migrating Your Plugin to Create Guten Block

The first thing I do is to is create a dummy block and steal the files it creates and integrate that into my existing plugin.

Here’s the dummy block I created for WP Plugin Info Card, which I’ll be using as the example in this tutorial.

Let’s start by navigating to the plugins folder using command line.

Command Line Plugin Screen
Navigate To Your Plugins Directory Using Command Line

Next, run the Create Guten Block command, which is: npx create-guten-block my-block

If all is well, you should see this screen.

Create Gutenberg Block Terminal Screen
Create Gutenberg Block Terminal Screen

Once you have the block created, you’ll notice a folder structure like this:

Create Gutenberg Block Folder Structure
Create Gutenberg Block Folder Structure

What I usually do is copy the entire src folder, and copy over the .gitignore and package.json file to my existing plugin.

Since I’m using WP Plugin Info Card as the example, here’s the folder structure for WP Plugin Info Card after copying over the files. You can see the structure available in GitHub.

WP Plugin Info Card Folder Structure
WP Plugin Info Card Folder Structure

What I did is copied over the src folder, the .gitignore, and the package.json file. I modified the package.json file to include the dependencies that I needed, and I then copied over my blocks JS into /src/block/ as shown in the example below.

My plugin already had an src folder, so I merged the contents.

Plugin Info Card src Folder
Plugin Info Card src Folder
Plugin Info Card src/block Folder
Plugin Info Card src/block Folder

I then edit /src/block.js to point to import my blocks.

/** * Gutenberg Blocks * * All blocks related JavaScript files should be imported here. * You can create a new block folder in this dir and include code * for that block here as well. * * All blocks should be included here since this is the file that * Webpack is compiling as the input file. */ import './block/block.js'; import './block/wppic-query.js';

And here is my /block/block.js file.

/** * BLOCK: wp-plugin-info-card * * Registering a basic block with Gutenberg. * Simple block, renders and saves the same content without any interactivity. */ // Import CSS. import './style.scss'; import './editor.scss'; import edit from './edit'; const { __ } = wp.i18n; // Import __() from wp.i18n const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks /** * Register: aa Gutenberg Block. * * Registers a new block provided a unique name and an object defining its * behavior. Once registered, the block is made editor as an option to any * editor interface where blocks are implemented. * * @link https://www/mediaron_279/ * @param {string} name Block name. * @param {Object} settings Block settings. * @return {?WPBlock} The block, if it has been successfully * registered; otherwise `undefined`. */ registerBlockType( 'wp-plugin-info-card/wp-plugin-info-card', { // Block name. Block names must be string that contains a namespace prefix. Example: my-plugin/my-custom-block. title: __( 'WP Plugin Info Card', 'wp-plugin-info-card' ), // Block title. icon: <svg version="1.1" id="Calque_1" xmlns="" x="0px" y="0px" width="850.39px" height="850.39px" viewBox="0 0 850.39 850.39" enable-background="new 0 0 850.39 850.39" > <path fill="#DB3939" d="M425.195,2C190.366,2,0,191.918,0,426.195C0,660.472,190.366,850.39,425.195,850.39 c234.828,0,425.195-189.918,425.195-424.195C850.39,191.918,660.023,2,425.195,2z M662.409,476.302l-2.624,4.533L559.296,654.451 l78.654,45.525l-228.108,105.9L388.046,555.33l78.653,45.523l69.391-119.887l-239.354-0.303l-94.925-0.337l-28.75-0.032l-0.041-0.07 h0l-24.361-42.303l28.111-48.563l109.635-189.419l-78.653-45.524L435.859,48.514l21.797,250.546l-78.654-45.525l-69.391,119.887 l239.353,0.303l123.676,0.37l16.571,28.772l7.831,13.596L662.409,476.302z"/> </svg>, className: 'wp-plugin-info-card-block', category: 'common', // Block category — Group blocks together based on common traits E.g. common, formatting, layout widgets, embed. keywords: [ __( 'WP Plugin Info Card', 'wp-plugin-info-card' ), __( 'Plugin', 'wp-plugin-info-card' ), __( 'Info', 'wp-plugin-info-card' ), ], edit: edit, // Render via PHP save() { return null; }, } );

If you have never initialized this plugin, you will need to run npm install first. After you are set up and running, in terminal, run npm run build. This will create a dist folder with all your JS and styling.

Run npm run build
Run npm run build
Create Gutenblock Output
Create Gutenblock Output

Let’s now set up our scripts to point to the newly created dist folder, which we’ll be including in the .org version.

Here’s enqueuing the CSS files needed. Note, I use the same styles on both the front and back-end for this particular plugin.

/** * Enqueue Gutenberg block assets for both frontend + backend. * * @uses {wp-editor} for WP editor styles. * @since 1.0.0 */ function wp_plugin_info_card_cgb_block_assets() { // phpcs:ignore // Styles. wp_enqueue_style( 'wp_plugin_info_card-cgb-style-css', // Handle. plugins_url( 'dist/', dirname( __FILE__ ) ),// Block style CSS. array( 'wp-editor' ), WPPIC_VERSION, 'all' ); } // Hook: Frontend assets. add_action( 'enqueue_block_assets', 'wp_plugin_info_card_cgb_block_assets' );

And here’s the code for the JavaScript.

/** * Enqueue Gutenberg block assets for backend editor. * * @uses {wp-blocks} for block type registration & related functions. * @uses {wp-element} for WP Element abstraction — structure of blocks. * @uses {wp-i18n} to internationalize the block's text. * @uses {wp-editor} for WP editor styles. * @since 1.0.0 */ function wp_plugin_info_card_cgb_editor_assets() { // phpcs:ignore // Scripts. wp_enqueue_script( 'wp_plugin_info_card-cgb-block-js', // Handle. plugins_url( '/dist/', dirname( __FILE__ ) ), // We register the block here. Built with Webpack. array( 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' ), WPPIC_VERSION, true // Enqueue the script in the footer. ); wp_localize_script( 'wp_plugin_info_card-cgb-block-js', 'wppic', array( 'rest_url' => get_rest_url(), ) ); if ( function_exists( 'wp_set_script_translations' ) ) { wp_set_script_translations( 'wp_plugin_info_card-cgb-block-js', 'wp-plugin-info-card' ); } elseif ( function_exists( 'gutenberg_get_jed_locale_data' ) ) { $locale = gutenberg_get_jed_locale_data( 'wp-plugin-info-card' ); $content = 'wp.i18n.setLocaleData( ' . wp_json_encode( $locale ) . ', "wp-plugin-info-card" );'; wp_script_add_data( 'wp_plugin_info_card-cgb-block-js', 'data', $content ); } elseif ( function_exists( 'wp_get_jed_locale_data' ) ) { /* for 5.0 */ $locale = wp_get_jed_locale_data( 'wp-plugin-info-card' ); $content = 'wp.i18n.setLocaleData( ' . wp_json_encode( $locale ) . ', "wp-plugin-info-card" );'; wp_script_add_data( 'wp_plugin_info_card-cgb-block-js', 'data', $content ); } } // Hook: Editor assets. add_action( 'enqueue_block_editor_assets', 'wp_plugin_info_card_cgb_editor_assets' );

And finally, registering the blocks.

function wppic_register_block() { register_block_type( 'wp-plugin-info-card/wp-plugin-info-card', array( 'attributes' => array( 'type' => array( 'type' => 'string', 'default' => 'plugin', ), 'slug' => array( 'type' => 'string', 'default' => '', ), 'loading' => array( 'type' => 'boolean', 'default' => true, ), 'html' => array( 'type' => 'string', 'default' => '', ), 'align' => array( 'type' => 'string', 'default' => 'center', ), 'image' => array( 'type' => 'string', 'default' => '', ), 'containerid' => array( 'type' => 'string', 'default' => '', ), 'margin' => array( 'type' => 'string', 'default' => '', ), 'clear' => array( 'type' => 'string', 'default' => 'none', ), 'expiration' => array( 'type' => 'int', 'default' => 0, ), 'ajax' => array( 'type' => 'string', 'default' => 'false', ), 'scheme' => array( 'type' => 'string', 'default' => 'default', ), 'layout' => array( 'type' => 'string', 'default' => 'card', ), 'custom' => array( 'type' => 'string', 'default' => '', ), 'width' => array( 'type' => 'string', 'default' => '', ), ), 'render_callback' => 'wppic_block_editor', ) ); register_block_type( 'wp-plugin-info-card/wp-plugin-info-card-query', array( 'attributes' => array( 'search' => array( 'type' => 'string', 'default' => '', ), 'tag' => array( 'type' => 'string', 'default' => '', ), 'author' => array( 'type' => 'string', 'default' => '', ), 'user' => array( 'type' => 'string', 'default' => '', ), 'browse' => array( 'type' => 'string', 'default' => '', ), 'per_page' => array( 'type' => 'int', 'default' => 24, ), 'per_page' => array( 'type' => 'int', 'default' => 24, ), 'cols' => array( 'type' => 'int', 'default' => 2, ), 'type' => array( 'type' => 'string', 'default' => 'plugin', ), 'slug' => array( 'type' => 'string', 'default' => '', ), 'loading' => array( 'type' => 'boolean', 'default' => true, ), 'html' => array( 'type' => 'string', 'default' => '', ), 'align' => array( 'type' => 'string', 'default' => 'center', ), 'image' => array( 'type' => 'string', 'default' => '', ), 'containerid' => array( 'type' => 'string', 'default' => '', ), 'margin' => array( 'type' => 'string', 'default' => '', ), 'clear' => array( 'type' => 'string', 'default' => 'none', ), 'expiration' => array( 'type' => 'int', 'default' => 0, ), 'ajax' => array( 'type' => 'string', 'default' => 'false', ), 'scheme' => array( 'type' => 'string', 'default' => 'default', ), 'layout' => array( 'type' => 'string', 'default' => 'card', ), 'custom' => array( 'type' => 'string', 'default' => '', ), 'width' => array( 'type' => 'string', 'default' => '', ), ), 'render_callback' => 'wppic_block_editor_query', ) ); } function wppic_block_editor_query( $attributes ) { if ( is_admin() ) { return; } $args = array( 'cols' => $attributes['cols'], 'per_page' => $attributes['per_page'], 'type' => $attributes['type'], 'align' => $attributes['align'], 'image' => $attributes['image'], 'containerid' => $attributes['containerid'], 'margin' => $attributes['margin'], 'clear' => $attributes['clear'], 'expiration' => $attributes['expiration'], 'ajax' => $attributes['ajax'], 'scheme' => $attributes['scheme'], 'layout' => $attributes['layout'], ); if ( ! empty( $attributes['browse'] ) ) { $args['browse'] = $attributes['browse']; } if ( ! empty( $attributes['search'] ) ) { $args['search'] = $attributes['search']; } if ( ! empty( $attributes['tag'] ) ) { $args['tag'] = $attributes['tag']; } if ( ! empty( $attributes['user'] ) ) { $args['user'] = $attributes['user']; } if ( ! empty( $attributes['author'] ) ) { $args['author'] = $attributes['author']; } $html = ''; if ( '' !== $attributes['width'] ) { $html = sprintf( '<div class="wp-pic-full-width">%s</div>', wppic_shortcode_query_function( $args ) ); } else { $html = wppic_shortcode_query_function( $args ); } return $html; } function wppic_block_editor( $attributes ) { if ( is_admin() ) { return; } $args = array( 'type' => $attributes['type'], 'slug' => $attributes['slug'], 'align' => $attributes['align'], 'image' => $attributes['image'], 'containerid' => $attributes['containerid'], 'margin' => $attributes['margin'], 'clear' => $attributes['clear'], 'expiration' => $attributes['expiration'], 'ajax' => $attributes['ajax'], 'scheme' => $attributes['scheme'], 'layout' => $attributes['layout'], ); $html = ''; if ( '' !== $attributes['width'] ) { $html = sprintf( '<div class="wp-pic-full-width">%s</div>', wppic_shortcode_function( $args ) ); } else { $html = wppic_shortcode_function( $args ); } return $html; } add_action( 'init', 'wppic_register_block' );

Use WP CLI To Generate Your POT File

Since the scripts are compiled, you can now generate a POT file. Let’s run the following:

wp i18n make-pot . langs/wp-plugin-info-card.pot --exclude="/src/js,src/block"

This assumes your languages folder is in the langs directory, but most plugin authors use languages.

If all is well, you should see your JavaScript strings present, as shown in the screenshot below.

Poedit Strings
Poedit Strings

Now update your plugin in .org and it’ll eventually show the JavaScript strings it needs. Just give it time to sync.

What If My Plugin Isn’t on .org? How Do I Provide Translatable Gutenberg Blocks?

You’ll need to run a few steps to tell WordPress to look inside your languages folder first, and generate the .json file it needs for the translations.

First, you’ll want to install po2json.

Navigate to the root of your plugin folder and run: npm install po2json -g

Find the .po file you want to translate into JSON. I’ll pick on a German translation and fill it with test variables so you can see the output.

I ran the following command in the root of my directory. Notice I used my text domain and the JavaScript handler name as the extension for the JSON file.

po2json langs/wp-plugin-info-card-de_DE.po langs/wp-plugin-info-card-de_DE-wp_plugin_info_card-cgb-block-js.json -f jed

The JSON file will look like this with all my test strings inputted.

{"domain":"messages","locale_data":{"messages":{"1":["1"],"2":["2"],"3":["3"],"":{"domain":"messages","plural_forms":"nplurals=2; plural=n != 1;","lang":"de_DE"},"WP Plugin Info Card":["WP Plugin Info Card Einstellungen"],"":[""],"WP Plugin Info Card displays plugins & themes identity cards in a beautiful box with a smooth rotation effect using Plugin API & Theme API. Dashboard widget included.":["test"],"Brice CAPOBIANCO, Ronald Huereca":["Brice CAPOBIANCO, Ronald Huereca"],"":[""],"WP Plugin Info Card Settings":["WP Plugin Info Card Einstellungen"],"Settings":["Einstellungen"],"Documentation and examples":["Dokumentation und Beispiele"],"More plugins by b*web":["Weitere Plugins von b*web"],"More plugins by MediaRon":["Weitere Plugins von b*web"],"Add a plugin":["Ein Plugin hinzufügen"],"Please refer to the plugin URL on to determine its slug":["Bitte schaue im Plugin-Verzeichnis nach um die Kurzform festzustellen"],"is not a valid plugin name format. This key has been deleted.":["ist keine gültige Namensform für ein Plugin. Dieser Schlüssel wurde entfernt."],"Plugins":["Plugins"]," Plugin Page":[" Plugin Seite"],"Ratings:":["Bewertungen:"],"Active Installs:":["test"],"Last Updated:":["Zuletzt aktualisisert:"],"Nothing found, please add at least one item in the WP Plugin Info Card settings page.":["Nichts gefunden. Bitte mindestens ein Element in der WP Plugin Info Card Einstellungen Seite hinzufügen."],"Item not found:":["Element nicht gefunden:"],"does not exist.":["gibt es nicht."],"Add a theme":["Ein Theme hinzufügen"],"Please refer to the theme URL on to determine its slug":["Bitte schaue im Plugin-Verzeichnis nach um die Kurzform festzustellen"],"is not a valid theme name format. This key has been deleted.":["ist keine gültige Namensform für ein Plugin. Dieser Schlüssel wurde entfernt."],"Themes":["Themes"],"Downloads:":["Downloads:"],"Color scheme":["Farbschema"],"Force enqueuing CSS & JS":["Enqueuing von CSS & JS erzwingen"],"By default the plugin enqueues scripts (JS & CSS) only for pages containing the shortcode. If you wish to force scripts enqueuing, check this box.":["Standardmässig werden Scripts (JS & CSS) nur auf Seiten welchen den Plugin-Shortcode enthalten geladen. Um das das Laden auf allen Seiten zu erzwingen, hier klicken."],"Display a discrete credit":["test"],"If you like this plugin, check this box!":["test"],"Enable dashboard widget":["Das Dashboard Widget einschalten"],"Help: Don't forget to open the dashboard option panel (top right) to display it on your dashboard.":["Hilfe: Vergiss nicht in den Dashboard Optionen (oben rechts) die Anzeige einzuschalten"],"Ajaxify dashboard widget":["Ajax für das Dashboard Widget einschalten"],"Will load the data asynchronously with AJAX.":["Daten werden mittels AJAX asynchron geladen."],"List of items to display":["Liste der Elemente welche angezeigt werden sollen"],"Cache was successfully cleared":["Der Cache wurde erfolgreich geleert"],"Something went wrong":["Es ist etwas schief gelaufen"],"Empty all cache":["Gesamten Cache leeren"],"How to use WP Plugin Info Card shortcodes?":["Wie werden WP Plugin Info Card shortcodes verwendet?"],"Shortcode parameters":["Shortcode Parameter"],"(default: plugin)":["(Standard: Plugin)"],"plugin slug name - Please refer to the plugin URL on to determine its slug: https://www/mediaron_279/":["Plugin Kurzform - Bitte schaue im Plugin-Verzeichnis nach um die Kurzform festzustellen: https://www/mediaron_279/"],"template layout to use - Default is \"card\" so you may leave this parameter empty. Available layouts are: card, large (default: empty)":["Zu verwendendes Layout - Standard ist \"card\". Der Parameter kann weggelassen werden. Mögliche Layouts sind: card (Karte), large (gross)."],"card color scheme: scheme1 through scheme10 (default: default color scheme defined in admin)":["Farbschema für die Karte: scheme1 bis scheme10 (Standard: das im Administrationsbereich festgelegte Farbschema)"],"Image URL to override default WP logo (default: empty)":["Bild URL, uberschreibt das Standard WP Logo (Standard: leer)"],"(default: empty)":["(Standard: leer)"],"custom div id, may be used for anchor (default: wp-pic-PLUGIN-NAME)":["Benutzerdefinierte Div Id. Kann als interner Link verwendet werden (Standard: wp-pic-PLUGIN-NAME)"],"custom container margin - eg: \"15px 0\" (default: empty)":["Benutzerdefinierter Aussenabstand (Margin) - z. B. \"15px 0\" (Standard: leer)"],"clear float before or after the card: before, after (default: empty":["Clearing des Float vor oder nach der Karte: before, after (Standard: leer)"],"cache duration in minutes - numeric format only (default: 720)":["Cachedauer in Minuten - numerischer Wert (Standard: 720)"],"load the plugin data asynchronously with AJAX: yes, no (default: no)":["Plugin Daten mittels AJAX asynchron laden: yes (Ja), no (Nein) (Standard: no)"],"value to display: (default: empty)":["Wert der angezeigt werden soll: (Standard: leer)"],"For plugins:":["Für Plugins:"],"For themes:":["Für Themes:"],"General options":["Allgemeine Einstellungen"],"Dashboard Widget Settings":["Einstellungen für das Dashboard Widget"],"Default color scheme for your cards.":["Standard Farbschema für deine Karten."],"Plugin homepage":["Plugin Homepage"],"Support":["Support"],"Give me five!":["Gib mir Fünf!"],"Any suggestions?":["Vorschläge?"],"Card (default)":["Karte (Standard)"],"Large":["gross"],"WordPress":["test"],"Theme Preview":["Theme Vorschau"],"Download":["Heruntergeladen"],"Downloads":["Heruntergeladen"]," Theme Page":[" Theme Verzeichnis"],"Author(s):":["Autoren:"],"Ratings":["Bewertungen"],"Direct download":["Direkt herunterladen"],"Version":["Version"],"More info":["Weitere Informationen"],"Back":["Zurück"],"Compatible with WordPress":["test"],"Download:":["Downloads:"],"Current Version:":["Aktuelle Version:"],"Tested Up To:":["test"],"Installs":["test"],"Requires":["Benötigt"],"This card has been generated with WP Plugin Info Card":["test"],"Insert WP Plugin Info Card Shortcode":["WP Plugin Info Card Shortcode einfügen"],"Type of data to retrieve":["Datentyp welcher abgefragt werden soll"],"The Slug/ID":["Kurzform/ID"],"Layout structure":["Layout"],"Color scheme (not available for WordPress layout)":["test"],"Custom image URL":["Eigene Bild URL"],"Cards alignment":["Ausrichtung der Karten"],"Custom container ID":["Benutzerdefinierte Container ID"],"Custom container margin (15px 0)":["Benutzerdefinierter Aussenabstand (15px 0)"],"Clear container float":["Float des Containers clearen"],"Cache duration in minutes (num. only)":["Cachedauer in Minuten (numerisch)"],"Load data async. with AJAX":["Daten mittels AJAX asynchron laden"],"Single value to output":["Einzelner Wert ausgegeben"],"Do not specify":["Keine Angabe"],"Default scheme":["Standardschema"],"yes":["ja"],"no":["nein"],"center":["zentriert"],"left":["linksbündig"],"right":["rechtsbündig"],"before":["vor"],"after":["nach"],"You have to provide at least the slug/id parameter to continue.":["Du musst mindestens die Kurzform/Id angeben um weiterfahren zu können"],"Plugin":["Plugins"],"Info":["test"],"Reset":["test"],"Left":["test"],"Center":["zentriert"],"Right":["rechtsbündig"],"Theme":["Themes"],"None":["test"],"Before":["vor"],"After":["nach"],"No":["test"],"Yes":["ja"],"Default":["Standardschema"],"Scheme 1":["test"],"Scheme 2":["test"],"Scheme 3":["test"],"Scheme 4":["test"],"Scheme 5":["test"],"Scheme 6":["test"],"Scheme 7":["test"],"Scheme 8":["test"],"Scheme 9":["test"],"Scheme 10":["test"],"Scheme 11":["test"],"Card":["test"],"Flex":["test"],"Full Width":["test"],"Scheme":["Themes"],"Layout":["test"],"Width":["test"],"Upload Image!":["test"],"Plugin Card Image":["Plugin Homepage"],"Reset Image":["test"],"Container ID":["Benutzerdefinierte Container ID"],"Margin":["test"],"Clear":["test"],"Expiration in minutes":["test"],"Load card via Ajax?":["test"],"Select a Type":["test"],"Enter a slug":["test"],"Go":["test"],"WP Plugin Info Card Query":["WP Plugin Info Card Einstellungen"],"URL":["test"],"Name":["test"],"Author":["Autoren:"],"Screenshot URL":["test"],"Number of Ratings":["test"],"Active Installs":["test"],"Last Updated":["Zuletzt aktualisisert:"],"Homepage":["Plugin Homepage"],"Download Link":["Heruntergeladen"],"Search":["test"],"Tags (can be comma separated)":["test"],"Username":["test"],"Filter by Username":["test"],"User":["test"],"See the favorites from this username":["test"],"Browse":["test"],"Featured":["test"],"Updated":["Zuletzt aktualisisert:"],"Favorites":["test"],"Popular":["test"],"Per Page":["test"],"Columns":["test"]}}}

Finally, update your script translation to first look inside your languages folder for the file.

wp_set_script_translations( 'wp_plugin_info_card-cgb-block-js', 'wp-plugin-info-card', plugin_dir_path( dirname( __FILE__ ) ) . 'langs' );

If all is well, your translations should now show up for your Gutenberg plugin (note mine is filled with test strings).

Gutenberg Plugin Translated
Gutenberg Plugin Translated


I first ported my plugin over to Create Guten Block, I created a POT file, and showed you how to generate a JSON file if you want to create your own translations.

Feel free to leave comments or questions and I’ll be glad to answer them. Thanks for reading.

Ronald Huereca
Ronald Huereca

Ronald Huereca

Ronald has been part of the WordPress community since 2006, starting off writing and eventually diving into WordPress plugin development and writing tutorials and opinionated pieces.

No stranger to controversy and opinionated takes on tough topics, Ronald writes honestly when he covers a topic.

Leave a Comment