Blokthema’s vertalen WordPress anders dan een typische aanpak. Traditionele PHP templatebestanden met vertaalfuncties werken niet met HTML templates, blokken met JavaScript en de Site Editor. Deze verschuiving vereist dat je anders moet omgaan met de internationaliseringssystemen van WordPress blokken.
Deze gids biedt strategieën om je blokthema’s meertalig te maken. Je leert hoe je door de uitdagingen van het vertalen van blokthema’s navigeert, oplossingen toepast en integreert met vertaalplugins.
Waarom traditionele vertaalmethoden niet werken met blokthema’s (en hoe je het oplost)
Blokthema’s vervangen veel van de PHP-bestanden van WordPress door HTML-templates die blok-opmaak bevatten. Deze overstap brengt echter uitdagingen met zich mee, omdat HTML-templates geen PHP-vertaalfuncties kunnen uitvoeren zoals _()
of _e()
. Dit maakt de vertaalstrings die je al hebt volledig nutteloos in statische bestanden.
WordPress 6.8 brengt een aantal verbeteringen die de internationalisatie van blokthema’s vereenvoudigen. Thema’s met de juiste headers voor Text Domain en Domain Path hebben niet langer manual load_theme_textdomain()
calls nodig.
In plaats daarvan laadt WordPress automatisch vertaalbestanden en geeft wp-content/languages/themes/
voorrang boven themamappen om de prestaties te verbeteren.
Om te beginnen stel je je thema in volgens de klassieke aanpak door metadata toe te voegen aan het style.css
bestand.
/*
Theme Name: My Block Theme
Text Domain: my-block-theme
Domain Path: /languages
*/
Let op dat de kop Text Domain moet overeenkomen met de mapnaam van je thema (meestal in kebab-case) om ervoor te zorgen dat vertaalbestanden correct worden geladen in recente WordPress versies.
Net als bij style.css
vereist je functions.php
bestand minimale instellingen:
<?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' );
Het belangrijkste verschil tussen klassieke en blokthema’s is hier dat de laatste de verantwoordelijkheid voor vertalingen splitst tussen server-side PHP en client-side JavaScript. Klassieke thema’s moeten daarentegen vertrouwen op PHP om de meeste vertalingen af te handelen.
Zo bouw je block.json vertalingen
Het block.json bestand is je ‘configuratiehub’ voor het blok dat je wilt vertalen. Het instellen van de juiste internationalisatie zorgt ervoor dat je blokken zowel in de editor als op de front-end correct worden vertaald.
De canonieke manier om een blok te registreren is via block.json
. Beginnen met de configuratie textdomain
betekent dat WordPress de velden title, description en keywords kan vertalen als de textdomain
is ingesteld:
{
"$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"
}
}
}
Scenario’s die ‘context’ vereisen, hebben echter een server-side registratie nodig. Context is in dit geval belangrijk omdat hetzelfde woord anders vertaald kan worden op basis van het gebruik. Bijvoorbeeld, “berichten” als zelfstandig naamwoord versus als werkwoord vereist verschillende vertalingen in veel talen:
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' );
Elke blokvariatie die je toevoegt heeft ook een gestructureerde naam nodig, omdat WordPress zoekt naar specifieke patronen bij het laden van je vertalingen. Elke variatienaam wordt onderdeel van de vertaalsleutel:
{
"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"
}
}
]
}
JavaScript internationalisatie vereist dat je WordPress i18n functies importeert en de scriptvertalingen configureert. Dit komt omdat de Site Editor in de browser draait en niet op de server. Omdat PHP vertaalfuncties niet bestaan in JavaScript, biedt WordPress gelijkwaardige functies via het @wordpress/i18n
pakket:
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>
);
}
});
Daarnaast is het een goed idee om JSON vertaalbestanden voor JavaScript te genereren, omdat WordPress een andere indeling gebruikt voor vertalingen aan de clientzijde. PHP gebruikt .mo
bestanden, maar JavaScript heeft .json
bestanden nodig met specifieke naamgevingsconventies. Je kunt dit automatiseren met behulp van WP-CLI commando’s:
# 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
De resulterende JSON bestanden volgen een consistent patroon: {textdomain}-{locale}-{handle}.json
. WordPress kan deze dan laden wanneer je wp_set_script_translations()
aanroept.
Je statische HTML templates omzetten in vertaalklare PHP componenten
Omdat HTML templates statisch zijn, is het een uitdaging om ze te gebruiken voor de internationalisatie van blokthema’s, omdat je bestaande vertaalfuncties en -technieken niet werken.
PHP-ondersteunde templateonderdelen kunnen dit probleem oplossen, omdat WordPress ze verwerkt als PHP-bestanden ondanks dat er naar verwezen wordt in HTML-templates. Deze hybride aanpak behoudt de structuur van het blokthema en maakt tegelijkertijd dynamische inhoud mogelijk:
<!-- 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"} /-->
Merk op dat het templateonderdeel PHP kan bevatten:
<!-- 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 -->
Complexe blokken hebben het render.php
bestand nodig omdat sommige inhoud server-side verwerking vereist die blok-attributen alleen niet aankunnen. Database queries, voorwaardelijke logica en het genereren van dynamische inhoud vereisen allemaal PHP uitvoering:
// 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>
Dit betekent dat je je Block moet configureren om het renderbestand in block.json
te gebruiken:
{
"name": "my-theme/recent-posts",
"render": "file:./render.php",
"attributes": {
"count": {
"type": "number",
"default": 5
}
}
}
Dynamische inhoud vertalen voor custom velden en gebruikersinvoer implementeren
Ondanks dat dynamische inhoud veel voorkomt op WordPress websites, kan het vertaalproblemen veroorzaken omdat het in de database staat en niet in de bestanden van je thema. Daarom moeten externe vertaalplugins die je gebruikt deze inhoud apart van statische thema-strings identificeren en beheren.
Dit is waar het registreren van custom velden met de juiste metaconfiguratie waardevol is, omdat vertaalplugins inhaken op het metasysteem van WordPress om vertaalbare inhoud te detecteren. De parameter show_in_rest
maakt compatibiliteit met de Site Editor mogelijk:
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>';
}
Databasequery’s hebben ook taalfiltering nodig omdat WordPress inhoud niet automatisch filtert op taal. Vertaalplugins voegen query-aanpassingen toe die je moet aanpassen:
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 );
}
Je formulierverwerking mengt dynamische en statische inhoud, maar formulierlabels, foutmeldingen en beheermeldingen moeten allemaal taalbewust worden vertaald. De e-mailontvangers kunnen ook per taal verschillen:
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' );
Het is ook belangrijk om je navigatie taalbewust te maken, omdat menu-items, URL’s en structuur per taal kunnen verschillen. Je vertaalplugin heeft waarschijnlijk een API voor het bouwen van taalswitchers:
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;
}
Het werken met vertaalplugins zal waarschijnlijk een groot deel van je werk zijn, dus laten we dit aspect als volgende bekijken.
Werken met vertaalplugins: compatibiliteit en optimalisatie
Elke WordPress vertaalplugin behandelt blokthema’s op een unieke manier. Inzicht in de aanpak van de verschillende oplossingen helpt je om vanaf het begin compatibiliteit en flexibiliteit op te bouwen.
WPML’s Full Site Editing documentatie beschrijft hoe je een specifieke configuratie nodig hebt voor blokthema’s:
// 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 ondersteunt de Site Editor sinds versie 3.2. De plugin behandelt blokthema’s via zijn standaard string vertaalinterface:
// 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
);
}
De documentatie van TranslatePress laat zien dat bepaalde dynamische elementen moeten worden uitgesloten voor optimale prestaties:
// 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 );
Tot slot zijn er een paar algemene tips om door te geven als je werkt met externe codebases (zoals plugins). Ten eerste, zorg ervoor dat je een systematische aanpak voor het debuggen van vertaalproblemen gebruikt.
// 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' );
Site caching kan interfereren met vertaalupdates, dus misschien wil je caches wissen wanneer vertaalbestanden veranderen:
# 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
Prestatieoptimalisatie wordt kritisch met vertaalplugins. Elke plugin voegt database queries en verwerkingsoverhead toe, wat weer ten goede komt aan het cachen van veelgebruikte vertalingen:
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;
}
Als alternatief kan het verstandig zijn om caching over te slaan totdat je klaar bent om te deployen. Het gebruik van een testomgeving is hiervoor ideaal en je hebt de prestatieboost die caching biedt meestal niet nodig.
Samenvatting
De internationalisatie van het blokthema vereist dat je zowel met WordPress vertaalmethoden werkt als nieuwe benaderingen gebruikt in de Site Editor.
Door de metadata van je thema’s te configureren, templatestrategieën te implementeren en de vereisten van vertaalplugins te begrijpen, kun je meertalige blokthema’s maken die goed presteren en een gebruikerservaring van hoge kwaliteit bieden.
Als je klaar bent om je site te lanceren, levert Kinsta’s managed hosting voor WordPress de prestaties én het wereldwijde bereik die je site nodig heeft, met ingebouwde caching, een CDN met 37 locaties en tools zoals Git-integratie en testomgevingen.