Commit 7ad15ccb8ec for woocommerce
commit 7ad15ccb8ec82fe095739be93ffe4ebc22736fcb
Author: Jay Hemnani <193022578+jayhemnani9910@users.noreply.github.com>
Date: Tue Nov 10 19:37:58 2026 +0530
Fix fatal error when combining translation files made with wp i18n make-json (#70751)
* Combine user translation chunks that use the "messages" domain
`wp i18n make-json` writes its strings under locale_data.messages, not
locale_data.woocommerce. With two or more such files for a locale,
combine_user_translation_chunks() passed null to array_merge(), and the
TypeError ended the request: activating WooCommerce from wp-admin showed
the critical error page, and the combined file was never written. Merge
under the key the files use.
* Read each translation chunk's own key when combining user files
A later file using the other key (make-json vs Loco Translate) merged as
an empty array and its strings were lost. Initialise $domain_key before
the loop, and add tests for make-json files and both mixed orders.
diff --git a/plugins/woocommerce/changelog/fix-user-translation-chunks-messages-domain b/plugins/woocommerce/changelog/fix-user-translation-chunks-messages-domain
new file mode 100644
index 00000000000..e0d43f3756a
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-user-translation-chunks-messages-domain
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Avoid a fatal error when combining translation files made with `wp i18n make-json`, which keep their strings under the "messages" domain.
diff --git a/plugins/woocommerce/src/Internal/Admin/Translations.php b/plugins/woocommerce/src/Internal/Admin/Translations.php
index d2c15e6c3b4..fdc7e727714 100644
--- a/plugins/woocommerce/src/Internal/Admin/Translations.php
+++ b/plugins/woocommerce/src/Internal/Admin/Translations.php
@@ -138,6 +138,7 @@ class Translations {
// the filesystem object should be hooked up.
global $wp_filesystem;
$combined_translation_data = array();
+ $domain_key = 'messages';
foreach ( $json_i18n_filenames as $json_filename ) {
if ( ! $wp_filesystem->is_readable( $json_filename ) ) {
@@ -161,14 +162,18 @@ class Translations {
continue;
}
+ // `wp i18n make-json` keeps strings under "messages"; Loco Translate uses the text domain.
+ $chunk_domain_key = isset( $chunk_data['locale_data']['woocommerce'] ) ? 'woocommerce' : 'messages';
+
if ( empty( $combined_translation_data ) ) {
// Use the first translation file as the base structure.
$combined_translation_data = $chunk_data;
+ $domain_key = $chunk_domain_key;
} else {
// Combine all messages from all chunk files.
- $combined_translation_data['locale_data']['woocommerce'] = array_merge(
- $combined_translation_data['locale_data']['woocommerce'],
- $chunk_data['locale_data']['woocommerce']
+ $combined_translation_data['locale_data'][ $domain_key ] = array_merge(
+ $combined_translation_data['locale_data'][ $domain_key ] ?? array(),
+ $chunk_data['locale_data'][ $chunk_domain_key ] ?? array()
);
}
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/TranslationsTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/TranslationsTest.php
index ce054d580ba..a0d92202675 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/TranslationsTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/TranslationsTest.php
@@ -130,6 +130,96 @@ class TranslationsTest extends WC_Unit_Test_Case {
$this->created_files[] = $path;
}
+ /**
+ * Writes a chunk translation JSON file in a user-generated format: `wp i18n make-json`
+ * keeps strings under "messages", Loco Translate under the text domain.
+ *
+ * @param string $md5_name Fake md5 part of the filename.
+ * @param string $source Source file path stored in the JSON.
+ * @param string $domain_key Key the messages are stored under in locale_data.
+ * @param array $messages Translation messages.
+ */
+ private function create_user_chunk_json( $md5_name, $source, $domain_key, $messages ) {
+ $data = array(
+ 'translation-revision-date' => '2026-01-01 00:00:00+0000',
+ 'generator' => 'WP-CLI/2.12.0',
+ 'source' => $source,
+ 'domain' => $domain_key,
+ 'locale_data' => array(
+ $domain_key => array_merge(
+ array(
+ '' => array(
+ 'domain' => $domain_key,
+ 'lang' => 'de',
+ ),
+ ),
+ $messages
+ ),
+ ),
+ );
+
+ $path = $this->lang_dir . 'woocommerce-de_DE-' . $md5_name . '.json';
+ file_put_contents( $path, wp_json_encode( $data ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
+ $this->created_files[] = $path;
+ }
+
+ /**
+ * Builds the combined translation file and returns its decoded contents.
+ *
+ * @return array Combined translation data.
+ */
+ private function build_combined_file(): array {
+ if ( ! function_exists( 'get_filesystem_method' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ }
+ \WP_Filesystem();
+ $build = new \ReflectionMethod( Translations::class, 'build_and_save_translations' );
+ $build->setAccessible( true );
+ $build->invoke( $this->sut, $this->lang_dir, 'woocommerce', 'de_DE' );
+
+ return json_decode( file_get_contents( $this->lang_dir . 'woocommerce-de_DE-' . WC_ADMIN_APP . '.json' ), true ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
+ }
+
+ /**
+ * Key combinations of the two user-generated files, in the order they are read.
+ *
+ * @return array[]
+ */
+ public function user_chunk_key_provider(): array {
+ return array(
+ 'make-json, make-json' => array( 'messages', 'messages' ),
+ 'Loco, make-json' => array( 'woocommerce', 'messages' ),
+ 'make-json, Loco' => array( 'messages', 'woocommerce' ),
+ );
+ }
+
+ /**
+ * @testdox Should combine user-generated chunk files whichever key each one uses.
+ * @dataProvider user_chunk_key_provider
+ *
+ * @param string $first_key Key used by the file read first.
+ * @param string $second_key Key used by the file read second.
+ */
+ public function test_combines_user_generated_chunks( string $first_key, string $second_key ): void {
+ $this->create_user_chunk_json(
+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
+ WC_ADMIN_DIST_JS_FOLDER . 'app/index.js',
+ $first_key,
+ array( 'Show' => array( 'Anzeigen' ) )
+ );
+ $this->create_user_chunk_json(
+ 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
+ WC_ADMIN_DIST_JS_FOLDER . 'chunks/analytics-report-products.js',
+ $second_key,
+ array( 'Compare Products' => array( 'Produkte vergleichen' ) )
+ );
+
+ $messages = $this->build_combined_file()['locale_data'][ $first_key ];
+
+ $this->assertSame( array( 'Anzeigen' ), $messages['Show'], 'Strings from the first file should be kept' );
+ $this->assertSame( array( 'Produkte vergleichen' ), $messages['Compare Products'], 'Strings from the second file should be merged in' );
+ }
+
/**
* @testdox Should leave the translation file untouched for other script handles.
*/