Step-by-Step Guide to building a custom automatic translation switcher block for Gutenberg using REST API custom routes
Setting Up the WordPress Environment and Plugin Structure
Before diving into the custom block development, ensure you have a local WordPress development environment set up. This typically involves tools like LocalWP, Docker, or a LAMP/LEMP stack. We’ll be creating a simple WordPress plugin to house our custom Gutenberg block and its associated REST API endpoint.
Create a new directory for your plugin within the wp-content/plugins/ directory of your WordPress installation. For this example, let’s name it custom-translation-switcher.
Inside this directory, create the main plugin file, custom-translation-switcher.php. This file will contain the plugin header and the necessary hooks to register our block and REST API route.
<?php
/**
* Plugin Name: Custom Translation Switcher
* Description: A custom Gutenberg block for language switching using the REST API.
* Version: 1.0.0
* Author: Your Name
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: custom-translation-switcher
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Enqueue block assets.
*/
function cts_enqueue_block_assets() {
// Enqueue the block editor script.
wp_enqueue_script(
'custom-translation-switcher-editor-script',
plugins_url( 'build/index.js', __FILE__ ),
array( 'wp-blocks', 'wp-wp-data', 'wp-edit-post' ),
filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' )
);
// Enqueue the block editor styles.
wp_enqueue_style(
'custom-translation-switcher-editor-style',
plugins_url( 'build/index.css', __FILE__ ),
array( 'wp-edit-blocks' ),
filemtime( plugin_dir_path( __FILE__ ) . 'build/index.css' )
);
// Enqueue the front-end styles.
wp_enqueue_style(
'custom-translation-switcher-frontend-style',
plugins_url( 'build/style-index.css', __FILE__ ),
array(),
filemtime( plugin_dir_path( __FILE__ ) . 'build/style-index.css' )
);
}
add_action( 'enqueue_block_editor_assets', 'cts_enqueue_block_assets' );
/**
* Register custom REST API route.
*/
function cts_register_rest_route() {
register_rest_route( 'custom-translation-switcher/v1', '/languages', array(
'methods' => 'GET',
'callback' => 'cts_get_available_languages',
) );
}
add_action( 'rest_api_init', 'cts_register_rest_route' );
/**
* Callback function to get available languages.
* In a real-world scenario, this would fetch languages from your translation plugin or configuration.
* For this example, we'll return a static list.
*/
function cts_get_available_languages() {
$languages = array(
'en' => array( 'name' => 'English', 'url' => '/' ),
'es' => array( 'name' => 'Español', 'url' => '/es/' ),
'fr' => array( 'name' => 'Français', 'url' => '/fr/' ),
);
return new WP_REST_Response( $languages, 200 );
}
/**
* Register the custom block.
*/
function cts_register_block() {
register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'cts_register_block' );
Next, we need to set up the build process for our JavaScript and CSS. Gutenberg blocks are typically built using modern JavaScript (ESNext) and React. We’ll use @wordpress/scripts for this, which provides a convenient way to compile our assets.
Create a package.json file in the root of your plugin directory:
{
"name": "custom-translation-switcher",
"version": "1.0.0",
"description": "A custom Gutenberg block for language switching using the REST API.",
"main": "build/index.js",
"scripts": {
"build": "wp-scripts build",
"start": "wp-scripts start"
},
"keywords": [
"wordpress",
"gutenberg",
"block"
],
"author": "Your Name",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@wordpress/scripts": "^26.10.0"
}
}
Install the dependencies by running the following command in your terminal, from the plugin’s root directory:
npm install
Now, create a src directory within your plugin folder. This is where your block’s JavaScript and CSS files will reside.
Developing the Gutenberg Block (JavaScript/React)
Inside the src directory, create two files: index.js (for the block’s JavaScript logic) and style.scss (for front-end styles). We’ll also create an editor.scss file for styles specific to the block editor.
src/index.js
import { registerBlockType } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
import { useState, useEffect } from '@wordpress/element';
import { SelectControl } from '@wordpress/components';
import './editor.scss';
import './style.scss';
registerBlockType( 'custom-translation-switcher/block', {
title: __( 'Translation Switcher', 'custom-translation-switcher' ),
icon: 'translation',
category: 'widgets',
edit: ( { attributes, setAttributes } ) => {
const { selectedLanguage } = attributes;
// Fetch languages from the REST API
const { languages, isLoading } = useSelect( ( select ) => {
const apiFetch = select( 'core' ).apiFetch;
return {
languages: apiFetch( { path: '/custom-translation-switcher/v1/languages' } ),
isLoading: true, // A simple way to indicate loading, could be more sophisticated
};
}, [] );
const availableLanguages = ( languages || [] ).map( ( lang, key ) => ( {
label: lang.name,
value: key, // Using key as value for now, ideally would be language code
} ) );
const handleLanguageChange = ( newLanguageValue ) => {
setAttributes( { selectedLanguage: newLanguageValue } );
// In a real scenario, you'd trigger a page redirect or update state
// For this example, we'll just log it.
console.log( 'Selected Language:', newLanguageValue );
};
return (
<div className="custom-translation-switcher-block">
<h3>{ __( 'Language Switcher', 'custom-translation-switcher' ) }</h3>
{ isLoading && <p>{ __( 'Loading languages...', 'custom-translation-switcher' ) }</p> }
{ ! isLoading && availableLanguages.length > 0 && (
<SelectControl
label={ __( 'Select Language', 'custom-translation-switcher' ) }
value={ selectedLanguage }
options={ availableLanguages }
onChange={ handleLanguageChange }
/>
) }
{ ! isLoading && availableLanguages.length === 0 && (
<p>{ __( 'No languages found.', 'custom-translation-switcher' ) }</p>
) }
</div>
);
},
save: ( { attributes } ) => {
// The save function determines how the block is rendered on the front-end.
// For a dynamic block, this might be empty or render a placeholder.
// For simplicity, we'll render a placeholder.
return (
<div className="custom-translation-switcher-block">
<p>{ __( 'Language Switcher (Front-end)', 'custom-translation-switcher' ) }</p>
{/* In a real scenario, you'd render the actual switcher here */}
</div>
);
},
} );
Explanation:
registerBlockType: Registers our new Gutenberg block with WordPress.title,icon,category: Basic block metadata.edit: This function defines the block’s appearance and behavior within the Gutenberg editor.useSelect: A hook from@wordpress/datato access WordPress data. We use it here to call our custom REST API endpoint usingapiFetch.useStateanduseEffect: Standard React hooks for managing component state and side effects.SelectControl: A component from@wordpress/componentsto render a dropdown select element.attributesandsetAttributes: These are passed to theeditfunction and allow us to manage the block’s data.
save: This function defines how the block’s content is saved to the database and rendered on the front-end. For dynamic blocks that fetch data, this might render a placeholder or be handled by PHP.
src/editor.scss
.custom-translation-switcher-block {
border: 1px dashed #ccc;
padding: 15px;
background-color: #f9f9f9;
text-align: center;
h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 1.1em;
}
.components-base-control__field {
margin-bottom: 0;
}
}
src/style.scss
.custom-translation-switcher-block {
padding: 10px;
background-color: #e0e0e0;
text-align: center;
p {
margin-bottom: 0;
}
}
Building the Block Assets
With the JavaScript and SCSS files in place, navigate to your plugin’s root directory in the terminal and run the build command:
npm run build
This command will compile your src/index.js and src/style.scss into build/index.js and build/style-index.css respectively. It will also create build/index.css for the editor styles.
If you want to watch for changes and automatically rebuild during development, use:
npm run start
Integrating with WordPress and Testing
Activate your “Custom Translation Switcher” plugin from the WordPress admin area. Then, navigate to the post or page editor and add your new “Translation Switcher” block. You should see the block appear, and if the REST API call is successful, the select dropdown should populate with the languages defined in your PHP file.
Troubleshooting REST API Calls:
- Check Permalinks: Ensure your WordPress permalink structure is not set to “Plain”. Go to Settings > Permalinks and select any option other than “Plain”.
- REST API Enabled: Verify that the REST API is enabled in your WordPress installation. It’s enabled by default, but some security plugins might disable it.
- Inspect Network Requests: Use your browser’s developer tools (usually F12) to inspect the Network tab. Look for a request to
/wp-json/custom-translation-switcher/v1/languages. Check the response for errors or the expected JSON data. - Plugin Conflicts: Temporarily deactivate other plugins to rule out conflicts.
- Server Logs: Check your web server’s error logs (Apache or Nginx) for any PHP errors related to the REST API callback.
Front-end Rendering:
As noted in the save function, this example uses a placeholder for the front-end. For a fully functional translation switcher, you would typically:
- Implement logic in the
savefunction to render the actual switcher UI (e.g., a list of links). - Alternatively, make the block “dynamic” by removing the
savefunction and handling the front-end rendering entirely in PHP, similar to how shortcodes are rendered. This would involve creating a server-side rendering function hooked intorender_block. - Integrate with your specific translation plugin (e.g., WPML, Polylang) to dynamically fetch available languages and generate correct URLs.
This guide provides a foundational structure for building a custom Gutenberg block that interacts with a custom REST API route. From here, you can expand upon this by adding more complex attributes, dynamic rendering, and deeper integration with translation plugins.