Commit 650a94976b1 for woocommerce
commit 650a94976b1e31d4a0150b49665b4106edd2f128
Author: Luigi Teschio <gigitux@gmail.com>
Date: Wed Oct 14 12:08:09 2026 +0200
Fix extraction of bundled pattern translations (#69697)
* Fix extraction of bundled pattern translations
* Add changelog entry for pattern translation extraction
* simplify test
* rename file
* fix unit test
diff --git a/plugins/woocommerce/bin/class-generate-pattern-translations.php b/plugins/woocommerce/bin/class-generate-pattern-translations.php
new file mode 100644
index 00000000000..f6e16cce98f
--- /dev/null
+++ b/plugins/woocommerce/bin/class-generate-pattern-translations.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Pattern header translation extraction.
+ *
+ * @package WooCommerce
+ */
+
+namespace Automattic\WooCommerce\Tools;
+
+use WP_CLI\I18n\FileDataExtractor;
+
+/**
+ * Generates extractable PHP strings from pattern headers.
+ *
+ * @internal
+ */
+class Generate_Pattern_Translations {
+
+ /**
+ * Generates a PHP translation source without executing pattern files.
+ *
+ * @since 11.3.0
+ *
+ * @param string $plugin_directory The plugin directory containing patterns.
+ * @return string
+ * @throws \RuntimeException When no pattern files are found.
+ */
+ public function generate( string $plugin_directory ): string {
+ $pattern_files = glob( $plugin_directory . '/patterns/*.php' );
+ if ( empty( $pattern_files ) ) {
+ throw new \RuntimeException( 'No pattern files found for translation extraction.' );
+ }
+ $strings = array();
+ foreach ( $pattern_files as $pattern_file ) {
+ $headers = FileDataExtractor::get_file_data(
+ $pattern_file,
+ array(
+ 'Pattern title' => 'Title',
+ 'Pattern description' => 'Description',
+ )
+ );
+ foreach ( $headers as $context => $text ) {
+ if ( '' === $text ) {
+ continue;
+ }
+ $strings[] = sprintf(
+ "\t_x( %s, %s, 'woocommerce' ),\n",
+ var_export( $text, true ), // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export -- Serialize PHP string literals for the generated file.
+ var_export( $context, true ) // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export -- Serialize PHP string literals for the generated file.
+ );
+ }
+ }
+ sort( $strings, SORT_STRING );
+
+ return "<?php\n/**\n * Pattern metadata strings for translation extraction.\n *\n * Generated from pattern headers by `composer generate-pattern-translations`.\n * This file is not loaded at runtime.\n *\n * @package WooCommerce\n */\n\ndefined( 'ABSPATH' ) || exit;\n\nreturn array(\n"
+ . implode( '', $strings )
+ . ");\n";
+ }
+}
diff --git a/plugins/woocommerce/bin/generate-pattern-translations.php b/plugins/woocommerce/bin/generate-pattern-translations.php
new file mode 100644
index 00000000000..9e9e7f82e34
--- /dev/null
+++ b/plugins/woocommerce/bin/generate-pattern-translations.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Generates pattern metadata strings for translation scanners.
+ *
+ * @package WooCommerce
+ */
+
+use Automattic\WooCommerce\Tools\Generate_Pattern_Translations;
+
+require_once __DIR__ . '/composer/wp/vendor/autoload.php';
+require_once __DIR__ . '/class-generate-pattern-translations.php';
+
+$generator = new Generate_Pattern_Translations();
+$source = $generator->generate( dirname( __DIR__ ) );
+
+// This build script writes a local generated file without loading WordPress.
+// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+if ( false === file_put_contents( __DIR__ . '/../i18n/patterns.php', $source ) ) {
+ throw new \RuntimeException( 'Could not write pattern translation strings.' );
+}
diff --git a/plugins/woocommerce/changelog/fix-bundled-pattern-title-translations b/plugins/woocommerce/changelog/fix-bundled-pattern-title-translations
new file mode 100644
index 00000000000..0ca1f67e411
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-bundled-pattern-title-translations
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Make bundled pattern names available for translation in the editor.
diff --git a/plugins/woocommerce/composer.json b/plugins/woocommerce/composer.json
index fbb62799c4a..8c9caa1bf2a 100644
--- a/plugins/woocommerce/composer.json
+++ b/plugins/woocommerce/composer.json
@@ -146,7 +146,11 @@
"phpcbf": [
"phpcbf -p"
],
+ "generate-pattern-translations": [
+ "@php bin/generate-pattern-translations.php"
+ ],
"makepot-audit": [
+ "@generate-pattern-translations",
"wp --allow-root i18n make-pot . --include=\"woocommerce.php,assets,i18n,includes,lib,packages,patterns,src,templates\" --slug=woocommerce"
],
"makepot": [
diff --git a/plugins/woocommerce/i18n/patterns.php b/plugins/woocommerce/i18n/patterns.php
new file mode 100644
index 00000000000..252f3d246f1
--- /dev/null
+++ b/plugins/woocommerce/i18n/patterns.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Pattern metadata strings for translation extraction.
+ *
+ * Generated from pattern headers by `composer generate-pattern-translations`.
+ * This file is not loaded at runtime.
+ *
+ * @package WooCommerce
+ */
+
+defined( 'ABSPATH' ) || exit;
+
+return array(
+ _x( 'Centered Header Menu', 'Pattern title', 'woocommerce' ),
+ _x( 'Coming Soon Entire Site', 'Pattern title', 'woocommerce' ),
+ _x( 'Coming Soon Image Gallery', 'Pattern title', 'woocommerce' ),
+ _x( 'Coming Soon Minimal Left Image', 'Pattern title', 'woocommerce' ),
+ _x( 'Coming Soon Modern Black', 'Pattern title', 'woocommerce' ),
+ _x( 'Coming Soon Split Right Image', 'Pattern title', 'woocommerce' ),
+ _x( 'Coming Soon Store Only', 'Pattern title', 'woocommerce' ),
+ _x( 'Coming Soon with Header and Footer', 'Pattern title', 'woocommerce' ),
+ _x( 'Coming Soon', 'Pattern title', 'woocommerce' ),
+ _x( 'Default Coming Soon', 'Pattern title', 'woocommerce' ),
+ _x( 'Distraction Free Header', 'Pattern title', 'woocommerce' ),
+ _x( 'Essential Header', 'Pattern title', 'woocommerce' ),
+ _x( 'Footer with 3 Menus', 'Pattern title', 'woocommerce' ),
+ _x( 'Footer with Simple Menu', 'Pattern title', 'woocommerce' ),
+ _x( 'Large Footer', 'Pattern title', 'woocommerce' ),
+ _x( 'Large Header', 'Pattern title', 'woocommerce' ),
+ _x( 'Minimal Header', 'Pattern title', 'woocommerce' ),
+ _x( 'No Products Found - Clear Filters', 'Pattern title', 'woocommerce' ),
+ _x( 'No Products Found', 'Pattern title', 'woocommerce' ),
+ _x( 'Product Search', 'Pattern title', 'woocommerce' ),
+ _x( 'Related Products', 'Pattern title', 'woocommerce' ),
+);
diff --git a/plugins/woocommerce/src/Blocks/Patterns/PatternRegistry.php b/plugins/woocommerce/src/Blocks/Patterns/PatternRegistry.php
index 5e813650ab8..5b33f43ff05 100644
--- a/plugins/woocommerce/src/Blocks/Patterns/PatternRegistry.php
+++ b/plugins/woocommerce/src/Blocks/Patterns/PatternRegistry.php
@@ -136,11 +136,11 @@ class PatternRegistry {
}
}
- // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
- $pattern_data['title'] = translate_with_gettext_context( wp_strip_all_tags( html_entity_decode( (string) $pattern_data['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8' ) ), 'Pattern title', 'woocommerce' );
+ // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
+ $pattern_data['title'] = _x( wp_strip_all_tags( html_entity_decode( (string) $pattern_data['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8' ) ), 'Pattern title', 'woocommerce' );
if ( ! empty( $pattern_data['description'] ) ) {
- // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction
- $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', 'woocommerce' );
+ // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
+ $pattern_data['description'] = _x( $pattern_data['description'], 'Pattern description', 'woocommerce' );
}
// A pattern is registrable as long as it provides either inline content
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Patterns/GeneratePatternTranslationsTest.php b/plugins/woocommerce/tests/php/src/Blocks/Patterns/GeneratePatternTranslationsTest.php
new file mode 100644
index 00000000000..9a07e937ca6
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/Patterns/GeneratePatternTranslationsTest.php
@@ -0,0 +1,62 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks\Patterns;
+
+use Automattic\WooCommerce\Tools\Generate_Pattern_Translations;
+use Gettext\Translations;
+use WP_CLI\I18n\PhpCodeExtractor;
+
+/**
+ * Tests extraction independently of the WP-CLI mocks used by other test classes.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class GeneratePatternTranslationsTest extends \WC_Unit_Test_Case {
+ /**
+ * The System Under Test.
+ *
+ * @var Generate_Pattern_Translations
+ */
+ private $sut;
+
+ /**
+ * Loads the translation tooling.
+ */
+ public function setUp(): void {
+ parent::setUp();
+ require_once WC_ABSPATH . 'bin/composer/wp/vendor/autoload.php';
+ require_once WC_ABSPATH . 'bin/class-generate-pattern-translations.php';
+ $this->sut = new Generate_Pattern_Translations();
+ }
+
+ /**
+ * @testdox The generated source is current and exposes every bundled pattern title to plugin string extraction.
+ */
+ public function test_bundled_pattern_titles_are_extractable(): void {
+ $source = $this->sut->generate( WC_ABSPATH );
+ // The generated file must stay in sync so WordPress.org can scan release ZIPs.
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
+ $this->assertSame( $source, file_get_contents( WC_ABSPATH . 'i18n/patterns.php' ), 'Run composer generate-pattern-translations after changing pattern headers.' );
+ $translations = new Translations();
+ $translations->setDomain( 'woocommerce' );
+ PhpCodeExtractor::fromString(
+ $source,
+ $translations,
+ array(
+ 'file' => 'i18n/patterns.php',
+ )
+ );
+
+ $pattern_files = glob( WC_ABSPATH . 'patterns/*.php' );
+ $this->assertNotEmpty( $pattern_files );
+ foreach ( $pattern_files as $pattern_file ) {
+ $metadata = get_file_data( $pattern_file, array( 'title' => 'Title' ) );
+ $this->assertNotFalse(
+ $translations->find( 'Pattern title', $metadata['title'] ),
+ sprintf( 'The title in %s must be available for translation.', basename( $pattern_file ) )
+ );
+ }
+ }
+}