Block themes handle WordPress translation differently from the classic approach. Instead of relying on PHP template files with embedded translation functions, they use HTML templates, JavaScript blocks, and the Site Editor. Adapting to these changes requires a fresh understanding of WordPress Block internationalization systems.

This guide explores practical methods for making your Block themes multilingual. You’ll find actionable steps to overcome common translation challenges, apply effective solutions, and seamlessly integrate translation plugins.

Why Block themes break traditional translation methods (and how to fix them)

Block themes move away from WordPress’ reliance on PHP files, adopting HTML templates containing Block markup. The core challenge here is that HTML templates can’t run PHP translation functions like _() or _e(), leaving your translation strings unused in static files.

With WordPress 6.8, internationalization for Block themes has become a bit more straightforward. Themes that include correct Text Domain and Domain Path headers don’t require manual load_theme_textdomain() anymore.

WordPress now automatically loads translation files, checking wp-content/languages/themes/ ahead of theme folders for improved performance.

To get started, configure your theme using metadata in the style.css file:

/*
Theme Name: My Block Theme
Text Domain: my-block-theme
Domain Path: /languages
*/

It’s important that the Text Domain header matches the theme’s directory name (generally in kebab-case). This alignment helps WordPress load translations correctly in the latest releases.

Similarly, your functions.php file needs only minimal setup:

<?php
// Optional in WordPress 6.8+ but included for backward compatibility
function my_block_theme_setup() {
    load_theme_textdomain( 'my-block-theme', get_template_directory() . '/languages' );
}

add_action( 'after_setup_theme', 'my_block_theme_setup' );

// Register block scripts with translation support
function my_block_theme_scripts() {
    wp_enqueue_script(
        'my-block-theme-scripts',
        get_template_directory_uri() . '/assets/js/theme.js',
        array( 'wp-i18n' ),
        '1.0.0',
        true
    );

    wp_set_script_translations( 
        'my-block-theme-scripts', 
        'my-block-theme', 
        get_template_directory() . '/languages' 
    );
}

add_action( 'wp_enqueue_scripts', 'my_block_theme_scripts' );

The major shift from classic to Block themes is that translation work is now split between server-side PHP and client-side JavaScript, whereas classic themes handled most translations solely via PHP.

How to build block.json translations

The block.json file serves as your Block’s configuration center. Proper internationalization setup here ensures your Blocks are correctly localized in both the editor and on the site front end.

The official method to register a Block is by setting up block.json. By including the textdomain key, WordPress can translate fields like title, description, and keywords automatically:

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "my-theme/testimonial",
	"title": "Testimonial",
	"category": "text",
	"description": "Display customer testimonials",
	"keywords": ["quote", "review", "testimonial"],
	"textdomain": "my-block-theme",
	"attributes": {
		"content": {
			"type": "string",
			"source": "html",
			"selector": "blockquote"
		}
	}
}

Some scenarios call for context-aware translations, which require Block registration on the server side. Context matters when words carry different meanings in varied situations. For example, the word “post” can be either a verb or a noun in English and often translates differently in other languages:

function my_theme_register_testimonial_block() {
	register_block_type_from_metadata(
		get_template_directory() . '/blocks/testimonial',
		array(
			'title' => _x( 'Testimonial', 'block title', 'my-block-theme' ),
			'description' => _x(
				'Display customer testimonials', 
				'block description', 
				'my-block-theme'
			),

			'keywords' => array(
				_x( 'quote', 'block keyword', 'my-block-theme' ),
				_x( 'review', 'block keyword', 'my-block-theme' )
			)
		)
	);
}

add_action( 'init', 'my_theme_register_testimonial_block' );

When adding Block variations, follow clear naming conventions. WordPress pulls translations based on these structured keys:

{
	"name": "my-theme/button",
	"title": "Button",
	"textdomain": "my-block-theme",
	"variations": [{
			"name": "primary",
			"title": "Primary Button",
			"attributes": {
				"className": "is-style-primary"
			}
		},
		{
			"name": "secondary",
			"title": "Secondary Button",
			"attributes": {
				"className": "is-style-secondary"
			}
		}
	]
}

For translating JavaScript, you’ll need to use WordPress i18n functions and set up the script translation handling. Since the Site Editor is browser-based, the PHP translation functions aren’t available. Use the @wordpress/i18n package to access these functions in JavaScript:

import {
	registerBlockType
} from '@wordpress/blocks';
import {
	__
} from '@wordpress/i18n';
import {
	useBlockProps,
	RichText
} from '@wordpress/block-editor';

registerBlockType('my-theme/testimonial', {
	edit: ({
		attributes,
		setAttributes
	}) => {
		const blockProps = useBlockProps();

		return ( < div { ...blockProps } >
			< RichText tagName = "blockquote" value = { attributes.content } onChange = { (content) => setAttributes({
					content
				})
			}
			placeholder = {
				__('Add testimonial text...', 'my-block-theme')
			}
			/> < cite >
			< RichText tagName = "span" value = { attributes.author } onChange = { (author) => setAttributes({
					author
				})
			}
			placeholder = {
				__('Author name', 'my-block-theme')
			}
			/> < /cite> < /div>
		);
	}
});

It’s also beneficial to create JSON translation files for JavaScript, as WordPress uses a separate format for client-side translations. PHP leverages .mo files, but JavaScript depends on .json files with specific patterns. This process can be automated using WP-CLI:

# Extract strings from JavaScript files into POT
wp i18n make-pot . languages/my-block-theme.pot

# Convert PO files to JSON for JavaScript
wp i18n make-json languages/ --no-purge --pretty-print

The generated JSON files follow this pattern: {textdomain}-{locale}-{handle}.json, which WordPress loads when you register script translations with wp_set_script_translations().

Converting your static HTML templates into translation-ready PHP components

Because HTML templates are static, adapting them for Block theme internationalization poses a challenge—standard translation functions and tactics won’t work here.

You can bridge the gap with PHP-powered template parts. Even when referenced by HTML templates, these files are processed as PHP, allowing dynamic content and translation functions to run as needed:

<!-- templates/page.html -->
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->

<main class="wp-block-group">
<!-- wp:post-title {"level":1} /-->
     <!-- wp:post-content /-->
     <!-- wp:template-part {"slug":"post-meta"} /-->
</main>
<!-- /wp:group →
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

Inside this template part, you can utilize PHP code:

<!-- parts/post-meta.html -->
<!-- wp:group {"className":"post-meta"} -->
<div class="wp-block-group post-meta">
<?php
    echo sprintf(
      /* translators: 1: Post date, 2: Post author */
      __( 'Published on %1$s by %2$s', 'my-block-theme' ),
      get_the_date(),
     get_the_author()
     );
     ?>
</div>
<!-- /wp:group -->

When you have advanced Blocks with dynamic requirements, using a render.php file allows you to support server-side processing not possible with Block attributes alone. Use this for database queries, conditional logic, or creating dynamic content:

// blocks/recent-posts/render.php
<?php
$recent_posts = get_posts( array( 
'numberposts' => $attributes['count'] ?? 5 
) );
?>
<div <?php echo get_block_wrapper_attributes(); ?>>
<h3><?php echo esc_html__( 'Recent Posts', 'my-block-theme' ); ?></h3>
     <?php if ( $recent_posts ) : ?>
      <ul>
      <?php foreach ( $recent_posts as $post ) : ?>
           <li>
           <a href="<?php echo get_permalink( $post ); ?>">
            <?php echo get_the_title( $post ); ?>
           </a>

           <span class="post-date">
            <?php echo get_the_date( '', $post ); ?>
           </span>
           </li>
         <?php endforeach; ?>
         </ul>
     <?php else : ?>
         <p><?php esc_html_e( 'No posts found.', 'my-block-theme' ); ?></p>
     <?php endif; ?>
</div>

Enable your Block to use this render file by updating block.json:

{
	"name": "my-theme/recent-posts",
	"render": "file:./render.php",
	"attributes": {
		"count": {
			"type": "number",
			"default": 5
		}
	}
}

How to implement dynamic content translation for custom fields and user input

Dynamic content—such as user-generated data stored in the database—brings its own translation hurdles, since it isn’t bundled in your theme files. Translation plugins need to locate and manage this content independently from static theme text.

Registering custom fields with the right meta settings is important, as this lets translation plugins seamlessly interact with WordPress’ meta system. Setting the show_in_rest parameter is key for integration with the Site Editor:

function my_theme_register_meta_fields() {
    register_post_meta( 'page', 'custom_subtitle', array(
        'type' => 'string',
        'description' => __( 'Page subtitle', 'my-block-theme' ),
        'single' => true,
        'show_in_rest' => true,
        'auth_callback' => function() {
            return current_user_can( 'edit_posts' );
        }
    ));
}

add_action( 'init', 'my_theme_register_meta_fields' );

// Display with plugin compatibility
function my_theme_display_subtitle( $post_id ) {
    $subtitle = get_post_meta( $post_id, 'custom_subtitle', true );

    if ( ! $subtitle ) {
        return;
    }

    // WPML compatibility
    // (documented at wpml.org/documentation/support/wpml-coding-api/wpml-hooks-reference/)
    if ( function_exists( 'icl_t' ) ) {
        $subtitle = icl_t( 
            'my-block-theme', 
            'subtitle_' . $post_id, 
            $subtitle 
        );
    }

    // Polylang compatibility
    // (documented at polylang.pro/doc/function-reference/)
    if ( function_exists( 'pll_translate_string' ) ) {
        $subtitle = pll_translate_string( $subtitle, 'my-block-theme' );
    }

    echo '<h2 class="page-subtitle">' . esc_html( $subtitle ) . '</h2>';
}

When working with database queries, ensure results are filtered by language. By default, WordPress doesn’t filter queries this way, so you’ll need to account for translation plugin modifications:

function my_theme_get_localized_posts( $args = array() ) {
    $defaults = array(
        'post_type' => 'post',
        'posts_per_page' => 10
    );

    $args = wp_parse_args( $args, $defaults );

    // Polylang adds language taxonomy
    // (documented at polylang.pro/doc/developpers-how-to/)
    if ( function_exists( 'pll_current_language' ) ) {
        $args['lang'] = pll_current_language();
    }

    // WPML filters queries automatically when suppress_filters is false
    // (wpml.org/documentation/getting-started-guide/translating-custom-posts/)

    if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
        $args['suppress_filters'] = false;
    }

    return get_posts( $args );
}

In forms, both static labels and dynamic responses need to reflect the correct language, including elements like error messages and admin notifications. In some cases, even email recipients may need to switch depending on the language:

function my_theme_process_contact_form() {
	if ( ! isset( $_POST['contact_nonce'] ) || 
	! wp_verify_nonce( $_POST['contact_nonce'], 'contact_form' ) ) {
	return;
	}

	$name = sanitize_text_field( $_POST['name'] );
	$email = sanitize_email( $_POST['email'] );
	$message = sanitize_textarea_field( $_POST['message'] );

	// Get admin email in current language
	$admin_email = get_option( 'admin_email' );

	// For language-specific admin emails, use WPML's string translation
	// (documented at wpml.org/documentation/support/wpml-coding-api/wpml-hooks-reference/)

	if ( function_exists( 'icl_t' ) ) {
		// First register the string if not already registered
		if ( function_exists( 'icl_register_string' ) ) {
			icl_register_string( 'my-block-theme', 'contact_email', $admin_email );
		}

		$admin_email = icl_t(
			'my-block-theme',
			'contact_email',
			$admin_email
		);
	}

	$subject = sprintf(
	/* translators: %s: Sender name */
	__( 'Contact form submission from %s', 'my-block-theme' ),
	$name
	);

	wp_mail( $admin_email, $subject, $message );
}

add_action( 'init', 'my_theme_process_contact_form' );

Navigation menus and URLs might differ for each language, so make sure your approach to menus and structure reflects language awareness. Most major translation plugins offer APIs for building language switchers and multilingual navigation:

function my_theme_language_switcher_block() {
    if ( ! function_exists( 'pll_the_languages' ) && 
         ! function_exists( 'icl_get_languages' ) ) {
        return;
    }

    $output = '<div class="language-switcher">';

    // Polylang language switcher 
    // (documented at polylang.pro/doc/function-reference/)

    if ( function_exists( 'pll_the_languages' ) ) {
        $languages = pll_the_languages( array( 'raw' => 1 ) );
        foreach ( $languages as $lang ) {
            $output .= sprintf(
                '<a href="%s" class="%s">%s</a>',
                esc_url( $lang['url'] ),
                $lang['current_lang'] ? 'current-lang' : '',
                esc_html( $lang['name'] )
            );
        }
    }

    // WPML language switcher
    // (documented at wpml.org/documentation/support/wpml-coding-api/multi-language-api/)
    elseif ( function_exists( 'icl_get_languages' ) ) {
        $languages = icl_get_languages();
        foreach ( $languages as $lang ) {
            $output .= sprintf(
                '<a href="%s" class="%s">%s</a>',
                esc_url( $lang['url'] ),
                $lang['active'] ? 'current-lang' : '',
                esc_html( $lang['native_name'] )
            );
        }
    }

    $output .= '</div>';
    return $output;
}

You’ll often rely on translation plugins to facilitate these tasks, so understanding their integration is crucial.

Working with translation plugins: compatibility and optimization

Translation plugins each approach Block themes differently. Knowing how various solutions operate helps you ensure long-term compatibility and flexibility in theme development.

WPML offers tailored settings for Block themes in its Full Site Editing support documentation:

// WPML FSE compatibility based on official documentation
add_action( 'init', function() {
    if ( ! defined( 'WPML_VERSION' ) ) {
    return;
    }

    // FSE themes are automatically detected in WPML 4.5.3+ // Enable FSE support
    add_filter( 'wpml_is_fse_theme', '__return_true' );

    // Register custom strings per WPML String Translation documentation
    // (documented at wpml.org/documentation/support/wpml-coding-api/wpml-hooks-reference/)

    if ( function_exists( 'icl_register_string' ) ) {
        icl_register_string(
        'my-block-theme',
        'footer-copyright',
        '© My Company.'
        );
    }
});

Polylang Pro, from version 3.2 and onward, supports the Site Editor. It handles Block themes using its familiar string translation tools:

// Polylang string registration per official documentation
if ( function_exists( 'pll_register_string' ) ) {
	pll_register_string( 
		'Footer Copyright',
		'© My Company.',
		'my-block-theme',
		true // Multiline support
	);
}

According to TranslatePress’ documentation, certain dynamic elements may need to be excluded to maintain performance:

// TranslatePress optimization based on official recommendations
// (documented at translatepress.com/docs/developers/)
add_filter( 'trp_stop_translating_page', function( $stop, $url ) {
	// Skip admin and API requests per TranslatePress documentation
	if ( is_admin() || wp_is_json_request() ) {
	return true;
	}

	// Skip pattern preview URLs that can cause rendering issues
    if ( strpos( $url, 'pattern-preview' ) !== false ) {
    	return true;
	}

	return $stop;
}, 10, 2 );

MultilingualPress provides another option by building upon WordPress Multisite. Each language runs as a separate site with its distinct Block theme, eliminating the need for extra translation layers and leaning only on WordPress’ built-in internationalization.

Here are some general tips when working with plugins or external code. Start with a systematic debugging process for tackling translation problems.

// Debug helper for translation issues
function my_theme_debug_translations() {
    if ( ! WP_DEBUG || ! current_user_can( 'manage_options' ) ) {
    return;
	}

	error_log( 'Text domain loaded: ' . is_textdomain_loaded(
    	'my-block-theme' ) );
         error_log( 'Current locale: ' . get_locale() );
         error_log( 'Translation test: ' . __(
			'Hello World',
            'my-block-theme'
         )
	);

	// Check JSON translations for blocks
    $json_file = WP_LANG_DIR . '/themes/my-block-theme-' . get_locale() . '-script-handle.json';
	error_log( 'JSON translation exists: ' . file_exists( $json_file ) );
}

add_action( 'init', 'my_theme_debug_translations' );

Remember, site caching can sometimes delay translation updates. Clear your caches whenever you change translation files to see immediate results:

# Clear WordPress transients
wp transient delete --all

# Generate fresh translation files
wp i18n make-pot . languages/my-block-theme.pot
wp i18n make-json languages/ --no-purge

When using translation plugins, performance tuning is essential since they can increase database queries and processing time. Caching your most-used translations can offset this overhead:

function my_theme_cached_translation( $text, $domain = 'my-block-theme' ) {
    $cache_key = 'translation_' . md5( $text . get_locale() );
    $cached = wp_cache_get( $cache_key, 'my_theme_translations' );

    if ( false === $cached ) {
        $cached = __( $text, $domain );
        wp_cache_set( $cache_key, $cached, 'my_theme_translations', HOUR_IN_SECONDS );
    }

    return $cached;
}

During development, consider disabling caching until you’re ready for a production release. Working in a staging environment is usually preferable, since real-world performance improvements from caching only matter at launch.

Best Practices for Organizing Translation Files in Block Themes

Maintaining an organized translation structure is critical as your Block theme grows. Place all .po, .mo, and .json files in a dedicated /languages directory within your theme. This convention makes it easier for translation plugins, developers, and contributors to locate and update language files.

  • Example structure:
    • /wp-content/themes/my-block-theme/languages/
    • my-block-theme-fr_FR.po, my-block-theme-fr_FR.mo
    • my-block-theme-fr_FR-frontend.json

Expanding on JavaScript Localization Tactics

Block themes that include custom JavaScript often need localized UI labels, instructions, and error messages. In addition to @wordpress/i18n functions such as __(), consider leveraging wp.i18n.setLocaleData() for advanced scenarios, like extending locales at runtime or conditionally loading specific translations. This ensures scripts are usable for a wider audience out of the box.

import { __, setLocaleData } from '@wordpress/i18n';
// Optionally, adjust locale data here
setLocaleData( { /* ...translations... */ }, 'my-block-theme' );

Workflow Tips for Translating Block Themes with Teams

Collaboration is easier when translation files are version-controlled. Use tools like Poedit or the Loco Translate WordPress plugin for managing translations, and enable translators to contribute via your repository or a platform like WordPress.org’s GlotPress. Document your language file update workflow clearly to keep translators aligned with theme updates.

Automating Translation Extraction

Beyond WP-CLI, use build tools such as WP-CLI’s i18n command, gettext-extract for JavaScript, or npm scripts to extract and compile translations automatically as part of your deployment workflow. This reduces errors and streamlines the translation process as your theme evolves.

User-Friendly Language Switching

To enhance user experience, provide easily visible language switchers on your site. Most translation plugins let you place switchers in menus, headers, or footers through Block or Widget interfaces. When building custom language menus, follow accessibility guidelines so all users can change languages without barriers.

SEO and Multilingual Block Themes

Search engines need to recognize each language as a unique version of the site. When integrating translation plugins, ensure proper configuration for:

  • hreflang attributes: Adds signals for Google to serve the correct language and regional URLs.
  • Unique URLs per language (subdirectories or domains).
  • Meta tags and sitemap entries reflecting language variations.

This ensures full international SEO coverage for your multilingual Block theme site.

Advanced: Handling Third-Party Blocks and Patterns

Many Block themes rely on blocks from third-party plugins or pattern libraries. Make sure you:

  • Check if the third-party block is translation-ready and follows WordPress standards.
  • Override and translate block patterns by copying their JSON or HTML into your theme’s /patterns directory and localizing accordingly.
  • Test third-party blocks across all languages you support to catch edge cases early.

Troubleshooting and Support Resources

If you encounter challenges:

  • Verify theme metadata and directory structures for accuracy.
  • Check browser console for missing .json files or JavaScript translation errors.
  • Review the WordPress Theme Internationalization Guide for deep troubleshooting tips.
  • Engage with support forums for your translation plugin, as new Block theme features are actively being developed and improved.

Stay Updated

The WordPress Block theme and global Site Editor ecosystem is rapidly evolving. Stay updated with upcoming core features, as continued improvements to internationalization are expected in future WordPress releases. By being proactive now, your multilingual site will be future-proof and ready for the next wave of innovation in WordPress theme development.

Summary

Bringing Block theme internationalization to life involves combining traditional and new WordPress translation practices while leveraging the Site Editor in innovative ways.

By accurately configuring theme metadata, adopting smart template workflows, and thoroughly understanding translation plugin integration, you can provide a seamless multilingual experience for your Block themes users.

As you prepare your site for launch, make sure your WordPress hosting solution supports your internationalization efforts with robust performance, global reach, and developer-friendly tools.

  • Advanced WordPress automation workflows for agencies

    Advanced WordPress automation workflows for agencies

    Many agencies still spend countless hours on tasks that should run automatically—think plugin updates, deployment prep, and client notification emails. These chores can sap your billable hours and open the door to errors. Automation is the fix. It lets you save time, reduce mistakes, and keep your team focused on high-impact client work. It also…

  • Developer guide: Making WordPress block themes multilingual

    Developer guide: Making WordPress block themes multilingual

    Block themes handle WordPress translation differently from the classic approach. Instead of relying on PHP template files with embedded translation functions, they use HTML templates, JavaScript blocks, and the Site Editor. Adapting to these changes requires a fresh understanding of WordPress Block internationalization systems. This guide explores practical methods for making your Block themes multilingual.…