Les thèmes de blocs traduisent WordPress différemment d’une approche typique. Les fichiers de modèles PHP traditionnels avec des fonctions de traduction ne fonctionnent pas avec les modèles HTML, les blocs alimentés par JavaScript et l’éditeur de site. Ce changement t’oblige à comprendre différemment les systèmes d’internationalisation des blocs WordPress.

Ce guide propose des stratégies pour rendre tes thèmes de blocs multilingues. Vous apprendrez à naviguer dans les défis de la traduction des thèmes de blocs à mettre en œuvre des solutions et à intégrer des extensions de traduction.

Pourquoi les thèmes de blocs cassent les méthodes de traduction traditionnelles (et comment y remédier).

Les thèmes de blocs remplacent de nombreux fichiers PHP de WordPress par des modèles HTML qui contiennent des balises Block. Cependant, ce changement pose des problèmes car les modèles HTML ne peuvent pas exécuter les fonctions de traduction PHP telles que _() ou _e(). Par conséquent, les chaînes de traduction que vous avez déjà sont inutilement stockées dans des fichiers statiques.

WordPress 6.8 apporte quelques améliorations qui simplifient l’internationalisation des thèmes en bloc. Principalement, les thèmes avec des en-têtes Text Domain et Domain Path appropriés n’ont plus besoin d’appels à manual load_theme_textdomain().

Au lieu de cela, WordPress charge automatiquement les fichiers de traduction et donne la priorité à wp-content/languages/themes/ sur les répertoires de thèmes pour des raisons de performance.

Pour commencer, configurez votre thème selon une approche classique en ajoutant des métadonnées au fichier style.css.

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

Notez que l’en-tête du Text Domain doit correspondre au nom du dossier de votre thème (généralement en majuscule kebab) pour que les fichiers de traduction se chargent automatiquement et correctement dans les versions récentes de WordPress.

Comme pour style.css, le fichier functions.php ne nécessite qu’une configuration minimale :

<?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' );

La principale différence entre les thèmes classiques et les thèmes de blocs est que ces derniers répartissent la responsabilité de la traduction entre le PHP côté serveur et le JavaScript côté client. En revanche, les thèmes classiques doivent s’appuyer sur PHP pour gérer la plupart des traductions.

Comment construire les traductions block.json

Le fichier block.json est votre « centre de configuration » pour le bloc que vous souhaitez traduire. La mise en place d’une internationalisation appropriée garantit que vos blocs se traduisent correctement à la fois dans l’éditeur et sur l’interface publique.

La façon canonique d’enregistrer un bloc est de le faire par le biais de block.json. En commençant par la configuration textdomain, WordPress peut traduire les champs titre, description et mots-clés lorsque la configuration textdomain est définie :

{
	"$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"
		}
	}
}

Cependant, les scénarios nécessitant un « contexte » ont besoin d’un enregistrement côté serveur. Le contexte, dans ce cas, est important parce que le même mot peut se traduire différemment en fonction de son utilisation. Par exemple, « post » en tant que nom ou en tant que verbe nécessite des traductions différentes dans de nombreuses langues :

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' );

Toutes les variations de blocs que vous incluez ont également besoin d’un nom structuré, parce que WordPress recherche des modèles spécifiques lorsqu’il charge vos traductions. Chaque nom de variation fait partie de la clé de traduction :

{
	"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"
			}
		}
	]
}

L’internationalisation du JavaScript exige que vous importiez les fonctions i18n de WordPress et que vous configuriez les traductions des scripts. Cela est dû au fait que l’Éditeur de site s’exécute dans le navigateur plutôt que sur le serveur. Comme les fonctions de traduction PHP n’existent pas en JavaScript, WordPress fournit des fonctions équivalentes par le biais du paquetage @wordpress/i18n :

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>
		);
	}
});

De plus, c’est une bonne idée de générer des fichiers de traduction JSON pour JavaScript car WordPress utilise un format différent pour les traductions côté client. PHP utilise des fichiers .mo, mais JavaScript a besoin de fichiers .json avec des conventions de nommage spécifiques. Vous pouvez automatiser cela en utilisant les commandes 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

Les fichiers JSON qui en résultent suivent un modèle cohérent : {textdomain}-{locale}-{handle}.json. WordPress peut alors les charger lorsque vous appellezwp_set_script_translations().

Convertir tes modèles HTML statiques en composants PHP prêts à être traduits

Étant donné que les modèles HTML sont statiques, travailler avec eux pour l’internationalisation du thème Block est un défi, car vos fonctions et techniques de traduction existantes ne fonctionneront pas.

Les parties de modèles alimentées par PHP peuvent résoudre ce problème car WordPress les traite comme des fichiers PHP bien qu’ils soient référencés dans des modèles HTML. Cette approche hybride maintient la structure du thème de blocs tout en permettant un contenu dynamique :

<!-- 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"} /-->

Notez que la partie du modèle peut contenir du PHP :

<!-- 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 -->

Les blocs complexes ont besoin du fichier render.php parce que certains contenus nécessitent un traitement côté serveur que les attributs de bloc seuls ne peuvent pas gérer. Les requêtes de base de données, la logique conditionnelle et la génération de contenu dynamique nécessitent toutes une exécution PHP :

// 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>

Cela signifie que vous devez configurer votre bloc de façon à ce qu’il utilise le fichier de rendu qui se trouve à l’adresse block.json :

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

Comment mettre en œuvre la traduction du contenu dynamique pour les champs personnalisés et les entrées utilisateur ?

Malgré sa prévalence sur les sites WordPress, le contenu dynamique peut causer des problèmes de traduction parce qu’il existe dans la base de données plutôt que dans les fichiers de votre thème. En tant que telle, toute extension de traduction tierce que vous utilisez doit identifier et gérer ce contenu séparément des chaînes statiques du thème.

C’est là que l’enregistrement des champs personnalisés avec une configuration méta appropriée est précieux, car les extensions de traduction s’accrochent au système méta de WordPress pour détecter tout contenu traduisible. Le paramètre show_in_rest permet d’assurer la compatibilité avec l’éditeur de site :

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>';
}

Les requêtes de base de données doivent également être filtrées en fonction de la langue car WordPress ne filtre pas automatiquement le contenu en fonction de la langue. Les extensions de traduction ajoutent des modifications aux requêtes que vous devez prendre en compte :

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 );
}

Le traitement de votre formulaire mélange du contenu dynamique et statique, mais les libellés du formulaire, les messages d’erreur et les notifications de l’administrateur ont tous besoin d’une traduction adaptée à la langue. Les destinataires des e-mails peuvent également varier en fonction de la langue :

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' );

Il est également important d’évaluer la prise en compte de la langue dans votre navigation, car les éléments de menu, les URL et la structure peuvent différer d’une langue à l’autre. Votre extension de traduction dispose probablement d’une API permettant de créer des commutateurs de langue :

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;
}

Travailler avec des extensions de traduction représentera probablement une grande partie de votre travail, nous allons donc nous pencher sur cet aspect ensuite.

Travailler avec des plugins de traduction : compatibilité et optimisation

Chaque extension WoirdPress de traduction gère les thèmes de blocs d’une manière unique. Comprendre les approches des différentes solutions vous aide à construire la compatibilité et la flexibilité dès le départ.

La documentation Full Site Editing de WPML explique comment vous avez besoin d’une configuration spécifique pour les thèmes de blocs :

// 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 prend en charge l’Éditeur de site depuis la version 3.2. L’extension gère les thèmes de blocs par le biais de son interface standard de traduction de chaînes de caractères :

// 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
	);
}

La documentation de TranslatePress montre que certains éléments dynamiques doivent être exclus pour une performance optimale :

// 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 );

Enfin, il y a quelques conseils d’ordre général à transmettre lorsque vous travaillez avec des bases de code tierces (comme les extensions). Tout d’abord, assurez-vous d’utiliser une approche systématique pour déboguer les problèmes de traduction.

// 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' );

La mise en cache du site peut interférer avec les mises à jour des traductions, c’est pourquoi il est préférable de vider les caches lorsque les fichiers de traduction sont modifiés :

# 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

L’optimisation des performances devient critique avec les extensions de traduction. Chaque extension ajoute des requêtes de base de données et des frais généraux de traitement, ce qui profite à nouveau à la mise en cache des traductions fréquemment utilisées :

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;
}

Par ailleurs, il peut être judicieux d’ignorer la mise en cache jusqu’à ce que vous soyez prêt à la déployer. L’utilisation d’un environnement de staging est idéale pour cela, et vous n’aurez généralement pas besoin de l’augmentation des performances qu’apporte la mise en cache.

Résumé

L’internationalisation du thème de blocs exige que vous travailliez avec les deux méthodes de traduction de WordPress et que vous utilisiez de nouvelles approches dans l’Éditeur de site.

En configurant les métadonnées de votre thème, en mettant en œuvre des stratégies de modèles et en comprenant les exigences des extensions de traduction, vous pouvez créer des thèmes de blocs multilingues qui fonctionnent bien et offrent une expérience utilisateur de haute qualité.

Lorsque vous êtes prêt à vous lancer, l’hébergement infogéré de Kinsta pour WordPress offre les performances et la portée mondiale dont votre site a besoin, avec une mise en cache intégrée, un CDN à 37 emplacements et des outils tels que l’intégration Git et le staging.

Jeremy Holcombe Kinsta

Rédacteur en chef du contenu et du marketing chez Kinsta, développeur web WordPress et rédacteur de contenu. En dehors de WordPress, j'aime la plage, le golf et le cinéma. J'ai aussi des problèmes avec les personnes de grande taille ;).