How to Hooks and Filters in Shortcodes and Gutenberg Block Patterns Integration in Multi-Language Site Networks
Leveraging WordPress Hooks and Filters for Multi-Language Shortcode and Block Pattern Integration
Integrating custom shortcodes and Gutenberg block patterns into a multi-language WordPress site network presents unique challenges, particularly when ensuring consistent translation and contextual relevance across different language versions. This guide delves into advanced techniques using WordPress hooks and filters to achieve seamless integration, focusing on dynamic content generation and translation management.
Dynamic Shortcode Rendering with Language Context
When developing shortcodes that display dynamic content, especially within a multi-site or multi-language setup (e.g., using WPML, Polylang), it’s crucial that the shortcode’s output respects the current language context. This often involves retrieving language-specific data or modifying the shortcode’s behavior based on the active language.
Consider a shortcode that displays a list of featured products. In a multi-language site, each language version might have different featured products or product descriptions. We can use the do_shortcode filter to intercept and modify the shortcode’s output before it’s rendered, injecting language-specific logic.
Example: Language-Aware Featured Products Shortcode
Let’s assume we have a shortcode `[featured_products]` that, by default, fetches products from a global setting. We want to override this to fetch products relevant to the current language.
First, the shortcode registration:
function wpd_featured_products_shortcode( $atts ) {
$atts = shortcode_atts( array(
'count' => 5,
), $atts, 'featured_products' );
// Default logic: Fetch products from a global setting or a general category.
// This part will be overridden by our filter.
$args = array(
'posts_per_page' => absint( $atts['count'] ),
'post_type' => 'product', // Assuming a 'product' post type
'meta_key' => 'is_featured',
'meta_value' => 'yes',
);
$products = get_posts( $args );
$output = '<ul>';
if ( ! empty( $products ) ) {
foreach ( $products as $product ) {
$output .= '<li><a href="' . get_permalink( $product->ID ) . '">' . get_the_title( $product->ID ) . '</a></li>';
}
} else {
$output .= '<li>No featured products found.</li>';
}
$output .= '</ul>';
return $output;
}
add_shortcode( 'featured_products', 'wpd_featured_products_shortcode' );
Now, we’ll use the do_shortcode filter to modify the output based on the current language. This requires a multi-language plugin that provides functions to get the current language.
Assuming WPML is active, we can use icl_get_current_language(). For Polylang, it might be pll_current_language(). We’ll use a placeholder function get_current_site_language() for generality.
/**
* Filters the output of the [featured_products] shortcode to provide language-specific content.
*
* @param string $output The original shortcode output.
* @return string The filtered shortcode output.
*/
function wpd_filter_featured_products_shortcode( $output ) {
// Check if the shortcode is actually being processed.
// This is a rudimentary check; a more robust method might involve checking the shortcode tag.
if ( strpos( $output, '<ul>' ) === false && strpos( $output, '</ul>' ) === false ) {
return $output; // Not our shortcode output, return as is.
}
$current_lang = get_current_site_language(); // Placeholder for your multi-language plugin's function.
// Example: Fetching language-specific product IDs from post meta.
// This assumes you have a meta field like 'featured_products_lang_ids'
// where you store comma-separated IDs for each language.
$lang_specific_ids_meta = get_option( 'featured_products_lang_ids' );
$lang_specific_ids = array();
if ( ! empty( $lang_specific_ids_meta ) && is_array( $lang_specific_ids_meta ) ) {
if ( isset( $lang_specific_ids_meta[ $current_lang ] ) && ! empty( $lang_specific_ids_meta[ $current_lang ] ) ) {
$lang_specific_ids = array_map( 'intval', explode( ',', $lang_specific_ids_meta[ $current_lang ] ) );
}
}
// If language-specific IDs are found, override the default query.
if ( ! empty( $lang_specific_ids ) ) {
$args = array(
'post_type' => 'product',
'posts_per_page' => -1, // Fetch all specified IDs
'post__in' => $lang_specific_ids,
'orderby' => 'post__in', // Maintain the order specified in post__in
'meta_query' => array( // Ensure they are marked as featured if that's a requirement
array(
'key' => 'is_featured',
'value' => 'yes',
),
),
);
$products = get_posts( $args );
$new_output = '<ul>';
if ( ! empty( $products ) ) {
foreach ( $products as $product ) {
$new_output .= '<li><a href="' . get_permalink( $product->ID ) . '">' . get_the_title( $product->ID ) . '</a></li>';
}
} else {
$new_output .= '<li>No featured products found for this language.</li>';
}
$new_output .= '</ul>';
return $new_output;
}
// If no language-specific IDs, return the original output (which might be empty or default).
return $output;
}
add_filter( 'do_shortcode', 'wpd_filter_featured_products_shortcode', 10, 1 );
// Placeholder function for getting the current site language.
// Replace with your actual multi-language plugin's function.
function get_current_site_language() {
// Example for WPML:
// if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
// return ICL_LANGUAGE_CODE;
// }
// Example for Polylang:
// if ( function_exists( 'pll_current_language' ) ) {
// return pll_current_language();
// }
return 'en'; // Default to English if no plugin detected or for testing.
}
In this example, the do_shortcode filter is applied globally. A more targeted approach might involve filtering the output of a specific shortcode if you have many shortcodes and want to avoid performance overhead. However, the do_shortcode filter is often the most straightforward way to intercept and modify shortcode output.
Integrating Block Patterns with Language-Specific Content
Gutenberg block patterns offer a structured way to create reusable content layouts. For multi-language sites, patterns need to be adaptable. While Gutenberg itself doesn’t have built-in multi-language support for patterns, we can leverage filters and custom blocks to achieve this.
Method 1: Using a Filter on Block Content
We can filter the content of blocks within a pattern before it’s saved or rendered. This is particularly useful for blocks containing translatable text, such as Paragraph, Heading, or Custom HTML blocks.
The render_block filter allows us to modify the HTML output of any block. We can detect the current language and replace specific content within blocks.
/**
* Filters the rendering of specific blocks within patterns to provide language-specific content.
*
* @param string $block_content The block content.
* @param array $block The full block object.
* @return string The filtered block content.
*/
function wpd_filter_block_content_for_language( $block_content, $block ) {
$current_lang = get_current_site_language(); // Use your multi-language plugin's function.
// Example: Targeting a specific block within a pattern.
// Let's say we have a Heading block with a specific attribute or innerHTML
// that needs translation.
if ( $block['blockName'] === 'core/heading' && $current_lang !== 'en' ) {
// This is a simplified example. In reality, you'd likely have a more robust
// mechanism for storing and retrieving translations, perhaps using custom fields
// or a translation management system.
// Example: If the heading text is "Welcome to our Site" in English,
// and we want to translate it to Spanish "Bienvenido a nuestro sitio".
$english_text = 'Welcome to our Site';
$spanish_text = 'Bienvenido a nuestro sitio';
// Check if the block content contains the English text.
if ( strpos( $block_content, '<h2>' . $english_text . '</h2>' ) !== false ) {
// Replace it with the Spanish version.
$block_content = str_replace( '<h2>' . $english_text . '</h2>', '<h2>' . $spanish_text . '</h2>', $block_content );
}
// Add more language checks and replacements as needed.
}
// Example for a Custom HTML block containing a specific string.
if ( $block['blockName'] === 'core/html' && $current_lang === 'fr' ) {
$english_cta = 'Learn More';
$french_cta = 'En savoir plus';
if ( strpos( $block_content, $english_cta ) !== false ) {
$block_content = str_replace( $english_cta, $french_cta, $block_content );
}
}
return $block_content;
}
add_filter( 'render_block', 'wpd_filter_block_content_for_language', 10, 2 );
This approach requires careful management of translatable strings. You’d typically store the original (e.g., English) text in the block and use the filter to dynamically replace it with the translated version based on the current language. This can be done by storing translations in theme options, custom post types, or by integrating with a translation API.
Method 2: Language-Specific Block Patterns Registration
A more structured approach involves registering different block patterns for different languages. This is often managed by multi-language plugins or by implementing a custom pattern registration system.
If your multi-language plugin supports it, you can register patterns directly for specific languages. For example, WPML’s String Translation and Theme and plugin localization features can be leveraged. For Polylang, you might use custom post types to store pattern configurations and associate them with languages.
Alternatively, you can use a filter to conditionally load or display patterns. The register_block_pattern filter can be used to modify pattern registration, or you can hook into the pattern querying process.
/**
* Filters block pattern registration to conditionally register language-specific patterns.
*
* @param array $pattern The pattern data.
* @param string $slug The pattern slug.
* @return array The filtered pattern data.
*/
function wpd_filter_block_pattern_registration( $pattern, $slug ) {
$current_lang = get_current_site_language(); // Use your multi-language plugin's function.
// Example: Assume patterns are named like 'my-theme/hero-section-en', 'my-theme/hero-section-es'.
// We only want to register the pattern relevant to the current language.
$pattern_lang = substr( $slug, -2 ); // Extract the last two characters as language code.
if ( $pattern_lang !== $current_lang ) {
// If the pattern's language code doesn't match the current language,
// we can either unregister it or prevent it from being registered.
// A simpler approach is to filter the patterns that are *queried* later.
// For direct registration filtering, you might return false or an empty array,
// but this can be tricky with WordPress's internal pattern handling.
// A more common approach is to filter the *query* of patterns.
}
return $pattern;
}
// add_filter( 'register_block_pattern', 'wpd_filter_block_pattern_registration', 10, 2 );
// Note: Filtering 'register_block_pattern' directly can be complex.
// It's often better to filter the query that retrieves patterns.
A more practical approach for pattern selection is to filter the query that retrieves patterns for display in the editor or on the frontend. The block_pattern_query filter is ideal for this.
/**
* Filters the query for block patterns to return language-specific patterns.
*
* @param array $query The current query arguments.
* @return array The filtered query arguments.
*/
function wpd_filter_block_pattern_query( $query ) {
$current_lang = get_current_site_language(); // Use your multi-language plugin's function.
// Example: If you name your patterns with language codes (e.g., 'my-theme/hero-en', 'my-theme/hero-es')
// you can filter by category or slug.
// Let's assume patterns are categorized by language.
if ( $current_lang !== 'en' ) { // If not English, filter for the specific language category.
// Add or modify the 'category' argument to filter by language.
// This assumes you've registered patterns with categories like 'hero-section-en', 'hero-section-es'.
$query['category'] = $current_lang; // Or a more specific category like 'hero-section-' . $current_lang;
}
// If you name patterns with language codes in their slugs:
// Example: 'my-theme/hero-section-en'
// You might need to adjust the query to fetch patterns matching a certain slug pattern.
// This is more complex as the default query doesn't directly support slug pattern matching.
// You might need to fetch all patterns and then filter them in PHP.
return $query;
}
add_filter( 'block_pattern_query', 'wpd_filter_block_pattern_query', 10, 1 );
To implement language-specific pattern categories, you would register your patterns like this:
/**
* Registers language-specific block patterns.
*/
function wpd_register_language_specific_patterns() {
// English patterns
register_block_pattern( 'my-theme/hero-section-en', array(
'title' => __( 'Hero Section (EN)', 'my-theme' ),
'description' => __( 'A hero section for English content.', 'my-theme' ),
'content' => '<!-- wp:group -->
<div class="wp-block-group"><h2>Welcome to Our Site</h2><p>Discover our amazing products.</p></div>
<!-- /wp:group -->',
'categories' => array( 'hero-section-en' ), // Language-specific category
) );
// Spanish patterns
register_block_pattern( 'my-theme/hero-section-es', array(
'title' => __( 'Sección de Héroe (ES)', 'my-theme' ),
'description' => __( 'Una sección de héroe para contenido en español.', 'my-theme' ),
'content' => '<!-- wp:group -->
<div class="wp-block-group"><h2>Bienvenido a nuestro sitio</h2><p>Descubre nuestros increíbles productos.</p></div>
<!-- /wp:group -->',
'categories' => array( 'hero-section-es' ), // Language-specific category
) );
// Add more patterns for other languages and sections.
}
add_action( 'init', 'wpd_register_language_specific_patterns' );
With this setup, the wpd_filter_block_pattern_query function, when filtering by category, will effectively serve the correct language pattern. You’ll need to ensure your get_current_site_language() function correctly returns the language code that matches your pattern categories.
Advanced: Using Custom Blocks for Translatable Components
For highly complex or dynamic components that need to be integrated into block patterns and shortcodes, developing custom Gutenberg blocks offers the most flexibility. Custom blocks can have their own saved data, which can include translation keys or references to translatable strings.
When a custom block is rendered, you can use its attributes to fetch translations. This is often done in conjunction with a JavaScript-based translation system or by making server-side calls to a translation API or database.
Consider a custom “Call to Action” block. Its attributes might store the English text, and a separate attribute could store a translation key. On render, the block’s PHP or JavaScript would look up the translation for the current language.
/**
* Server-side rendering for a custom "Translatable CTA" block.
*
* @param array $attributes Block attributes.
* @return string Rendered block HTML.
*/
function render_translatable_cta_block( $attributes ) {
$current_lang = get_current_site_language(); // Use your multi-language plugin's function.
$cta_text_en = isset( $attributes['ctaText'] ) ? $attributes['ctaText'] : __( 'Default CTA Text', 'my-theme' );
$cta_url = isset( $attributes['ctaUrl'] ) ? esc_url( $attributes['ctaUrl'] ) : '#';
$button_text = isset( $attributes['buttonText'] ) ? $attributes['buttonText'] : __( 'Learn More', 'my-theme' );
// Example: Fetching translations from a custom option or post meta.
// This assumes you have a structure like:
// 'my_theme_translations' => [
// 'cta_button_text' => [
// 'en' => 'Learn More',
// 'es' => 'Aprende más',
// 'fr' => 'Apprendre encore plus',
// ],
// 'cta_main_text' => [
// 'en' => 'Default CTA Text',
// 'es' => 'Texto de CTA predeterminado',
// 'fr' => 'Texte d\'appel à l\'action par défaut',
// ],
// ]
$translations = get_option( 'my_theme_translations', array() );
$translated_button_text = $button_text;
if ( isset( $translations['cta_button_text'][ $current_lang ] ) ) {
$translated_button_text = $translations['cta_button_text'][ $current_lang ];
}
$translated_cta_text = $cta_text_en;
if ( isset( $translations['cta_main_text'][ $current_lang ] ) ) {
$translated_cta_text = $translations['cta_main_text'][ $current_lang ];
}
ob_start();
?>
This custom block approach provides a robust framework for managing translatable components within Gutenberg. These custom blocks can then be freely used within block patterns or even called via shortcodes (by wrapping the custom block in a shortcode).
Conclusion and Best Practices
Effectively integrating shortcodes and block patterns into multi-language WordPress site networks requires a strategic use of hooks and filters. For shortcodes, the do_shortcode filter is powerful for dynamic content adjustments. For block patterns, filtering the pattern query (block_pattern_query) or modifying block rendering (render_block) are key. Developing custom blocks offers the highest degree of control for complex, translatable components.
- Prioritize Plugin Compatibility: Always ensure your custom solutions work harmoniously with your chosen multi-language plugin (WPML, Polylang, etc.).
- Centralize Translations: Avoid hardcoding translations. Use theme options, custom post types, or dedicated translation management systems.
- Performance: Be mindful of the performance impact of filters, especially those applied globally like
do_shortcode. Cache where possible and optimize queries. - User Experience: For pattern registration, ensure the language selection is intuitive for content creators.
- Testing: Thoroughly test all language versions of your site to confirm that shortcodes and patterns render correctly and translations are accurate.