Nei temi a blocchi la traduzione di WordPress avviene in modo diverso. I tradizionali template file PHP con funzioni di traduzione non funzionano con i template HTML, i blocchi JavaScript e l’editor del sito. I temi a blocchi richiedono un sistema diverso di internazionalizzazione dei blocchi di WordPress.
Questa guida fornisce soluzioni tecniche per rendere multilingue i temi a blocchi. Scopriremo come affrontare le sfide della traduzione dei temi a blocchi, come implementare le soluzioni e come integrare i propri siti con i plugin di traduzione.
Perché i temi a blocchi non funzionano con i metodi di traduzione tradizionali
I temi a blocchi sostituiscono molti dei file PHP di WordPress con template HTML che contengono il markup dei blocchi. Tuttavia, questo crea delle difficoltà perché i template HTML non possono eseguire le funzioni di traduzione PHP come _()
o _e()
. Di conseguenza, le stringhe di traduzione già esistenti rimangono inutilizzate nei file statici.
WordPress 6.8 apporta alcuni miglioramenti che semplificano l’internazionalizzazione dei temi a blocchi. In primo luogo, i temi con le intestazioni Text Domain e Domain Path corrette non necessitano più delle chiamate manuali a load_theme_textdomain()
.
Al contrario, per garantire le prestazioni, WordPress carica automaticamente i file di traduzione e dà priorità a wp-content/languages/themes/
rispetto alle directory dei temi.
Per iniziare, configuriamo il tema seguendo un approccio classico, aggiungendo i metadati al file style.css
.
/*
Theme Name: My Block Theme
Text Domain: my-block-theme
Domain Path: /languages
*/
Si tenga presente che l’intestazione del Text Domain deve corrispondere al nome della cartella del tema (solitamente in kebab case) per garantire il caricamento automatico dei file di traduzione nelle versioni recenti di WordPress.
Come il file style.css
, il file functions.php
richiede una configurazione minima:
<?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 differenza fondamentale tra i temi classici e quelli a blocchi è che questi ultimi dividono la responsabilità della traduzione tra PHP lato server e JavaScript lato client. Al contrario, i temi classici devono affidarsi a PHP per gestire la maggior parte delle traduzioni.
Come creare le traduzioni nel file block.json
Il file block.json è una sorta di “hub di configurazione” per il blocco da tradurre. L’impostazione di una corretta internazionalizzazione fa sì che i blocchi vengano tradotti correttamente sia nell’editor che sul front-end.
Il modo canonico per registrare un blocco è tramite il block.json
. Iniziare con la configurazione textdomain
significa che WordPress può tradurre i campi title, description e keywords quando textdomain
è stato impostato:
{
"$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"
}
}
}
Tuttavia, nei casi in cui è richiesto un “contesto”, è necessaria una registrazione lato server. Il contesto, in questo caso, è importante perché la stessa parola può essere tradotta in modo diverso a seconda del suo utilizzo. Ad esempio, “post” come sostantivo o come verbo richiede traduzioni diverse in molte lingue:
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' );
Anche le variazioni di blocco hanno bisogno di una denominazione strutturata, perché quando carica le traduzioni WordPress cerca degli schemi specifici. Il nome di ogni variante diventa parte della chiave di traduzione:
{
"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’internazionalizzazione di JavaScript richiede l’importazione delle funzioni i18n di WordPress e la configurazione delle traduzioni degli script. Questo perché l’Editor del sito viene eseguito nel browser e non sul server. Dato che le funzioni di traduzione PHP non esistono in JavaScript, WordPress fornisce funzioni equivalenti attraverso il pacchetto @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>
);
}
});
Inoltre, è una buona idea generare file di traduzione JSON per JavaScript perché WordPress utilizza un formato diverso per le traduzioni lato client. PHP utilizza i file .mo
, mentre JavaScript ha bisogno dei file .json
con specifiche convenzioni di denominazione. È possibile automatizzare questa operazione utilizzando i comandi di 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
I file JSON risultanti seguono uno schema coerente: {textdomain}-{locale}-{handle}.json
. Così, quando si invoca wp_set_script_translations()
, WordPress può caricarli.
Convertire i template HTML statici in componenti PHP pronti per la traduzione
Dato che i template HTML sono statici, non è semplice lavorarci per l’internazionalizzazione di un tema a blocchi, in quanto le funzioni e le tecniche di traduzione esistenti non funzionano.
Le parti di template basate su PHP possono risolvere questo problema perché WordPress le elabora come file PHP nonostante siano referenziate nei template HTML. Questo approccio ibrido consente di mantenere la struttura del tema a blocchi e al contempo di creare contenuti dinamici:
<!-- 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"} /-->
Nota che il template part può contenere 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 -->
I blocchi complessi necessitano del file render.php
perché alcuni contenuti richiedono un’elaborazione lato server che gli attributi del blocco da soli non possono gestire. Le query sul database, la logica condizionale e la generazione di contenuti dinamici richiedono l’esecuzione di 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>
Questo significa configurare il blocco in modo che utilizzi il file di rendering in block.json
:
{
"name": "my-theme/recent-posts",
"render": "file:./render.php",
"attributes": {
"count": {
"type": "number",
"default": 5
}
}
}
Come implementare la traduzione dei contenuti dinamici per i campi personalizzati e gli input degli utenti
Nonostante la notevole presenza nei siti WordPress, i contenuti dinamici possono causare problemi di traduzione perché esistono nel database e non nei file del tema. Per questo motivo, i plugin di traduzione di terze parti devono individuare e gestire questi contenuti separatamente dalle stringhe statiche del tema.
È qui che è preziosa la registrazione dei campi personalizzati con una corretta configurazione dei meta, perché i plugin di traduzione si agganciano al sistema dei meta di WordPress per rilevare qualsiasi contenuto traducibile. Il parametro show_in_rest
consente la compatibilità con l’Editor del sito:
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>';
}
Anche le query del database necessitano di un filtro per la lingua perché WordPress non filtra automaticamente i contenuti in base alla lingua. I plugin di traduzione aggiungono modifiche alle query di cui bisogna tenere conto:
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 );
}
L’elaborazione dei moduli mescola contenuti dinamici e statici, ma le etichette dei moduli, i messaggi di errore e le notifiche di amministrazione necessitano di una traduzione consapevole della lingua. Anche i destinatari delle e-mail potrebbero variare in base alla lingua:
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' );
È anche importante valutare la consapevolezza della lingua di navigazione perché le voci di menu, gli URL e la struttura potrebbero differire da una lingua all’altra. Il plugin di traduzione probabilmente dispone di un’API per la creazione di pulsanti per il cambio di lingua:
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;
}
Lavorare con i plugin di traduzione sarà probabilmente una parte importante del lavoro, quindi analizziamo questo aspetto.
Lavorare con i plugin di traduzione: compatibilità e ottimizzazione
Ogni plugin di traduzione di WordPress gestisce i temi dei blocchi in modo diverso. Comprendere gli approcci adottati dalle diverse soluzioni aiuta nella compatibilità e flessibilità fin dall’inizio.
La documentazione di WPML sul full site editing illustra la necessità di una configurazione specifica per i temi a blocchi:
// 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 supporta l’Editor del Sito dalla versione 3.2. Il plugin gestisce i temi a blocchi attraverso la sua interfaccia standard di traduzione delle stringhe:
// 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 documentazione di TranslatePress indica che alcuni elementi dinamici devono essere esclusi per ottenere prestazioni ottimali:
// 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 );
Infine, ci sono alcuni consigli di carattere generale da trasmettere quando si lavora con codebase di terze parti (come i plugin). Innanzitutto, bisogna seguire un approccio sistematico al debug della traduzione.
// 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 cache del sito può interferire con gli aggiornamenti delle traduzioni, quindi è meglio cancellare la cache quando i file di traduzione vengono modificati:
# 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’ottimizzazione delle prestazioni diventa fondamentale con i plugin di traduzione. Ogni plugin aggiunge query al database e costi di elaborazione, e anche in questo caso la cache delle traduzioni utilizzate più di frequente ne trae vantaggio:
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;
}
In alternativa, potrebbe essere utile saltare la cache fino a quando non si è pronti per la distribuzione. Un ambiente di staging è l’ideale per questo scopo e in genere non si avrà bisogno dell’aumento delle prestazioni fornito dalla cache.
Riepilogo
L’internazionalizzazione dei temi a blocchi richiede di lavorare seguedo i metodi di traduzione di WordPress e al contempo di seguire i nuovi approcci dell’Editor del sito.
Configurando i metadati del tema, implementando strategie d’uso dei template e comprendendo i requisiti dei plugin di traduzione, possiamo creare temi a blocchi multilingue che funzionano bene e offrono un’esperienza utente di alta qualità.
Quando siamo pronti per il lancio, l’hosting gestito per WordPress di Kinsta offre le prestazioni e la portata globale di cui ogni sito ha bisogno, grazie alla cache integrata, a un CDN con 37 location e a strumenti come l’integrazione Git e gli ambienti di staging.