From 8b6460639f3b047f10e85332fcb2bbf89bc41d94 Mon Sep 17 00:00:00 2001 From: Saskia Teichmann Date: Tue, 21 Apr 2026 00:07:35 +0200 Subject: [PATCH 1/2] Upgrade/Install: List available translation updates on Updates screen. --- src/wp-admin/includes/update.php | 130 ++++++++++++ src/wp-admin/update-core.php | 34 +++- tests/phpunit/tests/admin/includesUpdate.php | 196 +++++++++++++++++++ 3 files changed, 355 insertions(+), 5 deletions(-) create mode 100644 tests/phpunit/tests/admin/includesUpdate.php diff --git a/src/wp-admin/includes/update.php b/src/wp-admin/includes/update.php index f5aeea835bd12..b2813b36bfe6f 100644 --- a/src/wp-admin/includes/update.php +++ b/src/wp-admin/includes/update.php @@ -643,6 +643,136 @@ function get_theme_updates() { return $update_themes; } +/** + * Gets the display name for a translation update. + * + * @since 6.7.0 + * + * @param object $update Translation update object. + * @return string The translation update name. + */ +function wp_get_translation_update_name( $update ) { + $type = isset( $update->type ) ? $update->type : ''; + $slug = isset( $update->slug ) ? $update->slug : ''; + + switch ( $type ) { + case 'core': + return 'WordPress'; // Not translated. + + case 'theme': + $theme = wp_get_theme( $slug ); + if ( $theme->exists() ) { + return $theme->get( 'Name' ); + } + break; + + case 'plugin': + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + + $plugin_names = array(); + $all_plugins = get_plugins(); + $plugin_update = get_site_transient( 'update_plugins' ); + + if ( is_object( $plugin_update ) ) { + $plugin_updates = array(); + + if ( ! empty( $plugin_update->response ) && is_array( $plugin_update->response ) ) { + $plugin_updates = array_merge( $plugin_updates, $plugin_update->response ); + } + + if ( ! empty( $plugin_update->no_update ) && is_array( $plugin_update->no_update ) ) { + $plugin_updates = array_merge( $plugin_updates, $plugin_update->no_update ); + } + + foreach ( $plugin_updates as $plugin_file => $plugin_data ) { + $plugin_data = (object) $plugin_data; + + if ( ! empty( $plugin_data->slug ) && ! empty( $all_plugins[ $plugin_file ]['Name'] ) ) { + $plugin_names[ $plugin_data->slug ] = $all_plugins[ $plugin_file ]['Name']; + } + } + } + + foreach ( $all_plugins as $plugin_file => $plugin_data ) { + $plugin_basename = dirname( $plugin_file ); + + if ( isset( $plugin_data['Name'] ) ) { + $plugin_names[ $plugin_file ] = $plugin_data['Name']; + $plugin_names[ basename( $plugin_file, '.php' ) ] = $plugin_data['Name']; + + if ( '.' !== $plugin_basename ) { + $plugin_names[ $plugin_basename ] = $plugin_data['Name']; + } + } + } + + if ( ! empty( $plugin_names[ $slug ] ) ) { + return $plugin_names[ $slug ]; + } + break; + } + + return $slug; +} + +/** + * Gets the display language for a translation update. + * + * @since 6.7.0 + * + * @param string $locale Translation update locale. + * @return string The translation update language. + */ +function wp_get_translation_update_language( $locale ) { + $translations = get_site_transient( 'available_translations' ); + + if ( is_array( $translations ) && ! empty( $translations[ $locale ]['native_name'] ) ) { + return sprintf( + /* translators: 1: Native language name, 2: Locale. */ + __( '%1$s (%2$s)' ), + $translations[ $locale ]['native_name'], + $locale + ); + } + + return $locale; +} + +/** + * Gets display data for available translation updates. + * + * @since 6.7.0 + * + * @return array[] { + * An array of translation update display data. + * + * @type string $language Translation update language. + * @type string $language_code Translation update locale. + * @type string $name Translation update name. + * @type string $slug Translation update slug. + * @type string $type Translation update type. + * @type string $version Translation update version. + * } + */ +function wp_get_translation_update_data() { + $translation_updates = array(); + + foreach ( wp_get_translation_updates() as $update ) { + $language = isset( $update->language ) ? $update->language : ''; + + $translation_updates[] = array( + 'language' => wp_get_translation_update_language( $language ), + 'language_code' => $language, + 'name' => wp_get_translation_update_name( $update ), + 'slug' => isset( $update->slug ) ? $update->slug : '', + 'type' => isset( $update->type ) ? $update->type : '', + 'version' => isset( $update->version ) ? $update->version : '', + ); + } + + return $translation_updates; +} + /** * Adds a callback to display update information for themes with updates available. * diff --git a/src/wp-admin/update-core.php b/src/wp-admin/update-core.php index f1cd0c0a66132..b396bc14ce5d2 100644 --- a/src/wp-admin/update-core.php +++ b/src/wp-admin/update-core.php @@ -813,8 +813,8 @@ function list_theme_updates() { * @since 3.7.0 */ function list_translation_updates() { - $updates = wp_get_translation_updates(); - if ( ! $updates ) { + $translation_updates = wp_get_translation_update_data(); + if ( ! $translation_updates ) { if ( 'en_US' !== get_locale() ) { echo '

' . __( 'Translations' ) . '

'; echo '

' . __( 'Your translations are all up to date.' ) . '

'; @@ -822,11 +822,35 @@ function list_translation_updates() { return; } - $form_action = 'update-core.php?action=do-translation-upgrade'; + $form_action = 'update-core.php?action=do-translation-upgrade'; + $updates_count = count( $translation_updates ); ?> -

+

+ (%d)', + __( 'Translations' ), + number_format_i18n( $updates_count ) + ); + ?> +

-

+

+

diff --git a/tests/phpunit/tests/admin/includesUpdate.php b/tests/phpunit/tests/admin/includesUpdate.php new file mode 100644 index 0000000000000..029b170ab0f9f --- /dev/null +++ b/tests/phpunit/tests/admin/includesUpdate.php @@ -0,0 +1,196 @@ + array( + 'native_name' => 'Deutsch', + ), + ) + ); + + set_site_transient( + 'update_core', + (object) array( + 'translations' => array( + array( + 'type' => 'core', + 'slug' => 'default', + 'language' => 'de_DE', + 'version' => '6.7-beta3', + ), + ), + ) + ); + + set_site_transient( + 'update_plugins', + (object) array( + 'translations' => array( + array( + 'type' => 'plugin', + 'slug' => 'custom-internationalized-plugin', + 'language' => 'de_DE', + 'version' => '1.0.0', + ), + ), + ) + ); + + set_site_transient( + 'update_themes', + (object) array( + 'translations' => array( + array( + 'type' => 'theme', + 'slug' => 'custom-internationalized-theme', + 'language' => 'de_DE', + 'version' => '1.0.0', + ), + ), + ) + ); + + $this->assertSame( + array( + array( + 'language' => 'Deutsch (de_DE)', + 'language_code' => 'de_DE', + 'name' => 'WordPress', + 'slug' => 'default', + 'type' => 'core', + 'version' => '6.7-beta3', + ), + array( + 'language' => 'Deutsch (de_DE)', + 'language_code' => 'de_DE', + 'name' => 'Custom Dummy Plugin', + 'slug' => 'custom-internationalized-plugin', + 'type' => 'plugin', + 'version' => '1.0.0', + ), + array( + 'language' => 'Deutsch (de_DE)', + 'language_code' => 'de_DE', + 'name' => 'Custom Internationalized Theme', + 'slug' => 'custom-internationalized-theme', + 'type' => 'theme', + 'version' => '1.0.0', + ), + ), + wp_get_translation_update_data() + ); + } + + /** + * @ticket 42281 + */ + public function test_wp_get_translation_update_data_falls_back_to_locale_and_slug() { + set_site_transient( + 'update_plugins', + (object) array( + 'translations' => array( + array( + 'type' => 'plugin', + 'slug' => 'missing-plugin', + 'language' => 'it_IT', + 'version' => '2.0.0', + ), + ), + ) + ); + + $this->assertSame( + array( + array( + 'language' => 'it_IT', + 'language_code' => 'it_IT', + 'name' => 'missing-plugin', + 'slug' => 'missing-plugin', + 'type' => 'plugin', + 'version' => '2.0.0', + ), + ), + wp_get_translation_update_data() + ); + } + + /** + * @ticket 42281 + */ + public function test_wp_get_translation_update_data_matches_plugin_update_slug_to_plugin_file() { + add_filter( + 'all_plugins', + static function () { + return array( + 'hello.php' => array( + 'Name' => 'Hello Dolly', + ), + ); + } + ); + + set_site_transient( + 'update_plugins', + (object) array( + 'translations' => array( + array( + 'type' => 'plugin', + 'slug' => 'hello-dolly', + 'language' => 'de_DE', + 'version' => '1.7.2', + ), + ), + 'no_update' => array( + 'hello.php' => (object) array( + 'slug' => 'hello-dolly', + ), + ), + ) + ); + + $this->assertSame( + array( + array( + 'language' => 'de_DE', + 'language_code' => 'de_DE', + 'name' => 'Hello Dolly', + 'slug' => 'hello-dolly', + 'type' => 'plugin', + 'version' => '1.7.2', + ), + ), + wp_get_translation_update_data() + ); + } +} From c5077172d55bc75238b92dca3d6f77e568be747e Mon Sep 17 00:00:00 2001 From: Saskia Teichmann Date: Tue, 21 Apr 2026 12:27:14 +0200 Subject: [PATCH 2/2] Administration: Allow deferring individual translation updates --- .../includes/class-language-pack-upgrader.php | 4 + .../includes/class-wp-automatic-updater.php | 4 + src/wp-admin/includes/update.php | 22 +- src/wp-admin/update-core.php | 136 ++++++++++-- src/wp-includes/update.php | 138 +++++++++++++ tests/phpunit/tests/admin/includesUpdate.php | 195 +++++++++++++++--- 6 files changed, 445 insertions(+), 54 deletions(-) diff --git a/src/wp-admin/includes/class-language-pack-upgrader.php b/src/wp-admin/includes/class-language-pack-upgrader.php index 733b109183846..9ca02425b0fc6 100644 --- a/src/wp-admin/includes/class-language-pack-upgrader.php +++ b/src/wp-admin/includes/class-language-pack-upgrader.php @@ -70,6 +70,10 @@ public static function async_upgrade( $upgrader = false ) { foreach ( $language_updates as $key => $language_update ) { $update = ! empty( $language_update->autoupdate ); + if ( wp_is_translation_update_deferred( $language_update ) ) { + $update = false; + } + /** * Filters whether to asynchronously update translation for core, a plugin, or a theme. * diff --git a/src/wp-admin/includes/class-wp-automatic-updater.php b/src/wp-admin/includes/class-wp-automatic-updater.php index 2facbeb1d522f..a0297a4b2aa42 100644 --- a/src/wp-admin/includes/class-wp-automatic-updater.php +++ b/src/wp-admin/includes/class-wp-automatic-updater.php @@ -229,6 +229,10 @@ public function should_update( $type, $item, $context ) { } } else { $update = ! empty( $item->autoupdate ); + + if ( wp_is_translation_update_deferred( $item ) ) { + $update = false; + } } // If the `disable_autoupdate` flag is set, override any user-choice, but allow filters. diff --git a/src/wp-admin/includes/update.php b/src/wp-admin/includes/update.php index b2813b36bfe6f..420135c7ad37c 100644 --- a/src/wp-admin/includes/update.php +++ b/src/wp-admin/includes/update.php @@ -646,7 +646,7 @@ function get_theme_updates() { /** * Gets the display name for a translation update. * - * @since 6.7.0 + * @since 7.1.0 * * @param object $update Translation update object. * @return string The translation update name. @@ -718,7 +718,7 @@ function wp_get_translation_update_name( $update ) { /** * Gets the display language for a translation update. * - * @since 6.7.0 + * @since 7.1.0 * * @param string $locale Translation update locale. * @return string The translation update language. @@ -741,11 +741,14 @@ function wp_get_translation_update_language( $locale ) { /** * Gets display data for available translation updates. * - * @since 6.7.0 + * @since 7.1.0 * * @return array[] { * An array of translation update display data. * + * @type bool $checked Whether the translation update is selected for installation. + * @type bool $deferred Whether the translation update has been deferred. + * @type string $id Translation update identifier. * @type string $language Translation update language. * @type string $language_code Translation update locale. * @type string $name Translation update name. @@ -755,12 +758,19 @@ function wp_get_translation_update_language( $locale ) { * } */ function wp_get_translation_update_data() { - $translation_updates = array(); + $available_updates = wp_get_translation_updates(); + $deferred_translation_updates = wp_get_deferred_translation_updates( $available_updates ); + $translation_updates = array(); - foreach ( wp_get_translation_updates() as $update ) { - $language = isset( $update->language ) ? $update->language : ''; + foreach ( $available_updates as $update ) { + $translation_update_id = wp_get_translation_update_id( $update ); + $language = isset( $update->language ) ? $update->language : ''; + $deferred = isset( $deferred_translation_updates[ $translation_update_id ] ); $translation_updates[] = array( + 'checked' => ! $deferred, + 'deferred' => $deferred, + 'id' => $translation_update_id, 'language' => wp_get_translation_update_language( $language ), 'language_code' => $language, 'name' => wp_get_translation_update_name( $update ), diff --git a/src/wp-admin/update-core.php b/src/wp-admin/update-core.php index b396bc14ce5d2..81db47ed0c135 100644 --- a/src/wp-admin/update-core.php +++ b/src/wp-admin/update-core.php @@ -835,24 +835,66 @@ function list_translation_updates() { ?>
-

-
    +

    +

    + +

    + + + + + + + + + -
  • - : - -
  • + + + + + - - -

    + + + + + + + + +
    + + /> + +

    + + +

    +

    ' . __( 'Themes and Plugins — To update individual themes or plugins from this screen, use the checkboxes to make your selection, then click on the appropriate “Update” button. To update all of your themes or plugins at once, you can check the box at the top of the section to select all before clicking the update button.' ) . '

    '; if ( 'en_US' !== get_locale() ) { - $updates_howto .= '

    ' . __( 'Translations — The files translating WordPress into your language are updated for you whenever any other updates occur. But if these files are out of date, you can click the “Update Translations” button.' ) . '

    '; + $updates_howto .= '

    ' . __( 'Translations — Translation updates are selected for installation by default. Leave any translation updates unchecked if you want to install them later, then click the “Update Translations” button. Translation updates you leave unchecked will remain available until you select them.' ) . '

    '; } get_current_screen()->add_help_tab( @@ -1116,6 +1158,15 @@ function do_undismiss_core_update() { } } + if ( isset( $_GET['translation_updates'] ) && 'deferred' === $_GET['translation_updates'] ) { + wp_admin_notice( + __( 'The unchecked translation updates will remain available until you select them.' ), + array( + 'type' => 'success', + ) + ); + } + $last_update_check = false; $current = get_site_transient( 'update_core' ); @@ -1299,6 +1350,55 @@ function do_undismiss_core_update() { check_admin_referer( 'upgrade-translations' ); + if ( empty( $_POST['translations'] ) ) { + wp_redirect( self_admin_url( 'update-core.php' ) ); + exit; + } + + $translation_updates = wp_get_translation_updates_by_id(); + + $translation_update_ids = array_unique( + array_map( + 'sanitize_text_field', + wp_unslash( (array) $_POST['translations'] ) + ) + ); + + $translation_updates = array_intersect_key( $translation_updates, array_flip( $translation_update_ids ) ); + + if ( empty( $translation_updates ) ) { + wp_redirect( self_admin_url( 'update-core.php' ) ); + exit; + } + + $selected_translation_updates = array(); + + if ( ! empty( $_POST['checked'] ) ) { + $selected_translation_update_ids = array_unique( + array_map( + 'sanitize_text_field', + wp_unslash( (array) $_POST['checked'] ) + ) + ); + + $selected_translation_updates = array_intersect_key( $translation_updates, array_flip( $selected_translation_update_ids ) ); + } + + $current_translation_updates = wp_get_translation_updates_by_id(); + $deferred_translation_updates = array_intersect_key( + $current_translation_updates, + wp_get_deferred_translation_updates( $current_translation_updates ) + ); + $deferred_translation_updates = array_diff_key( $deferred_translation_updates, $translation_updates ); + $deferred_translation_updates += array_diff_key( $translation_updates, $selected_translation_updates ); + + wp_set_deferred_translation_updates( $deferred_translation_updates ); + + if ( empty( $selected_translation_updates ) ) { + wp_redirect( add_query_arg( 'translation_updates', 'deferred', self_admin_url( 'update-core.php' ) ) ); + exit; + } + require_once ABSPATH . 'wp-admin/admin-header.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; @@ -1308,7 +1408,7 @@ function do_undismiss_core_update() { $context = WP_LANG_DIR; $upgrader = new Language_Pack_Upgrader( new Language_Pack_Upgrader_Skin( compact( 'url', 'nonce', 'title', 'context' ) ) ); - $result = $upgrader->bulk_upgrade(); + $result = $upgrader->bulk_upgrade( array_values( $selected_translation_updates ) ); wp_localize_script( 'updates', diff --git a/src/wp-includes/update.php b/src/wp-includes/update.php index b7bf5a03780e7..fb277f778a333 100644 --- a/src/wp-includes/update.php +++ b/src/wp-includes/update.php @@ -920,6 +920,144 @@ function wp_get_translation_updates() { return $updates; } +/** + * Gets a stable identifier for a translation update. + * + * @since 7.1.0 + * + * @param array|object $update Translation update data. + * @return string Translation update identifier. + */ +function wp_get_translation_update_id( $update ) { + $update = (object) $update; + + return md5( + wp_json_encode( + array( + 'type' => $update->type ?? '', + 'slug' => $update->slug ?? '', + 'language' => $update->language ?? '', + 'version' => $update->version ?? '', + ) + ) + ); +} + +/** + * Gets translation updates keyed by their stable identifiers. + * + * @since 7.1.0 + * + * @param object[]|null $updates Optional. Translation update objects. Default null. + * @return object[] Translation updates keyed by identifier. + */ +function wp_get_translation_updates_by_id( $updates = null ) { + if ( null === $updates ) { + $updates = wp_get_translation_updates(); + } + + $translation_updates = array(); + + foreach ( (array) $updates as $update ) { + $translation_updates[ wp_get_translation_update_id( $update ) ] = (object) $update; + } + + return $translation_updates; +} + +/** + * Gets deferred translation updates. + * + * Deferred translation updates remain available for later installation and are + * skipped by background translation updates until they are selected again. + * + * @since 7.1.0 + * + * @param object[]|null $updates Optional. Translation update objects used to filter deferred updates + * to currently available updates. Default null. + * @return array[] Deferred translation updates keyed by identifier. + */ +function wp_get_deferred_translation_updates( $updates = null ) { + $stored_updates = get_site_option( 'deferred_translation_updates', array() ); + + if ( ! is_array( $stored_updates ) ) { + return array(); + } + + $deferred_updates = array(); + + foreach ( $stored_updates as $stored_update ) { + if ( ! is_array( $stored_update ) ) { + continue; + } + + $stored_update = (object) array( + 'type' => $stored_update['type'] ?? '', + 'slug' => $stored_update['slug'] ?? '', + 'language' => $stored_update['language'] ?? '', + 'version' => $stored_update['version'] ?? '', + ); + + $deferred_updates[ wp_get_translation_update_id( $stored_update ) ] = (array) $stored_update; + } + + if ( null !== $updates ) { + $deferred_updates = array_intersect_key( $deferred_updates, wp_get_translation_updates_by_id( $updates ) ); + } + + return $deferred_updates; +} + +/** + * Determines whether a translation update has been deferred. + * + * @since 7.1.0 + * + * @param array|object $update Translation update data. + * @param array[]|null $deferred_updates Optional. Deferred translation updates keyed by identifier. + * Default null. + * @return bool Whether the translation update has been deferred. + */ +function wp_is_translation_update_deferred( $update, $deferred_updates = null ) { + if ( null === $deferred_updates ) { + $deferred_updates = wp_get_deferred_translation_updates(); + } + + return isset( $deferred_updates[ wp_get_translation_update_id( $update ) ] ); +} + +/** + * Stores deferred translation updates. + * + * @since 7.1.0 + * + * @param object[]|array[] $updates Translation updates to defer. + * @return void + */ +function wp_set_deferred_translation_updates( $updates ) { + $deferred_updates = array(); + + foreach ( (array) $updates as $update ) { + $update = (object) $update; + + $translation_update = array( + 'type' => $update->type ?? '', + 'slug' => $update->slug ?? '', + 'language' => $update->language ?? '', + 'version' => $update->version ?? '', + ); + + $deferred_updates[ wp_get_translation_update_id( $translation_update ) ] = $translation_update; + } + + if ( empty( $deferred_updates ) ) { + delete_site_option( 'deferred_translation_updates' ); + return; + } + + update_site_option( 'deferred_translation_updates', $deferred_updates ); +} + /** * Collects counts and UI strings for available updates. * diff --git a/tests/phpunit/tests/admin/includesUpdate.php b/tests/phpunit/tests/admin/includesUpdate.php index 029b170ab0f9f..f94be8a866ade 100644 --- a/tests/phpunit/tests/admin/includesUpdate.php +++ b/tests/phpunit/tests/admin/includesUpdate.php @@ -4,9 +4,14 @@ * @group admin * @group upgrade * + * @covers ::wp_get_deferred_translation_updates * @covers ::wp_get_translation_update_data + * @covers ::wp_get_translation_update_id * @covers ::wp_get_translation_update_language * @covers ::wp_get_translation_update_name + * @covers ::wp_get_translation_updates_by_id + * @covers ::wp_is_translation_update_deferred + * @covers ::wp_set_deferred_translation_updates */ class Tests_Admin_IncludesUpdate extends WP_UnitTestCase { /** @@ -17,6 +22,7 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { } public function tear_down() { + delete_site_option( 'deferred_translation_updates' ); delete_site_transient( 'available_translations' ); delete_site_transient( 'update_core' ); delete_site_transient( 'update_plugins' ); @@ -30,6 +36,27 @@ public function tear_down() { * @ticket 42281 */ public function test_wp_get_translation_update_data_returns_display_ready_translation_updates() { + $core_update = (object) array( + 'type' => 'core', + 'slug' => 'default', + 'language' => 'de_DE', + 'version' => '6.7-beta3', + ); + + $plugin_update = (object) array( + 'type' => 'plugin', + 'slug' => 'custom-internationalized-plugin', + 'language' => 'de_DE', + 'version' => '1.0.0', + ); + + $theme_update = (object) array( + 'type' => 'theme', + 'slug' => 'custom-internationalized-theme', + 'language' => 'de_DE', + 'version' => '1.0.0', + ); + set_site_transient( 'available_translations', array( @@ -43,12 +70,7 @@ public function test_wp_get_translation_update_data_returns_display_ready_transl 'update_core', (object) array( 'translations' => array( - array( - 'type' => 'core', - 'slug' => 'default', - 'language' => 'de_DE', - 'version' => '6.7-beta3', - ), + (array) $core_update, ), ) ); @@ -57,12 +79,7 @@ public function test_wp_get_translation_update_data_returns_display_ready_transl 'update_plugins', (object) array( 'translations' => array( - array( - 'type' => 'plugin', - 'slug' => 'custom-internationalized-plugin', - 'language' => 'de_DE', - 'version' => '1.0.0', - ), + (array) $plugin_update, ), ) ); @@ -71,12 +88,7 @@ public function test_wp_get_translation_update_data_returns_display_ready_transl 'update_themes', (object) array( 'translations' => array( - array( - 'type' => 'theme', - 'slug' => 'custom-internationalized-theme', - 'language' => 'de_DE', - 'version' => '1.0.0', - ), + (array) $theme_update, ), ) ); @@ -84,6 +96,9 @@ public function test_wp_get_translation_update_data_returns_display_ready_transl $this->assertSame( array( array( + 'checked' => true, + 'deferred' => false, + 'id' => wp_get_translation_update_id( $core_update ), 'language' => 'Deutsch (de_DE)', 'language_code' => 'de_DE', 'name' => 'WordPress', @@ -92,6 +107,9 @@ public function test_wp_get_translation_update_data_returns_display_ready_transl 'version' => '6.7-beta3', ), array( + 'checked' => true, + 'deferred' => false, + 'id' => wp_get_translation_update_id( $plugin_update ), 'language' => 'Deutsch (de_DE)', 'language_code' => 'de_DE', 'name' => 'Custom Dummy Plugin', @@ -100,6 +118,9 @@ public function test_wp_get_translation_update_data_returns_display_ready_transl 'version' => '1.0.0', ), array( + 'checked' => true, + 'deferred' => false, + 'id' => wp_get_translation_update_id( $theme_update ), 'language' => 'Deutsch (de_DE)', 'language_code' => 'de_DE', 'name' => 'Custom Internationalized Theme', @@ -116,16 +137,18 @@ public function test_wp_get_translation_update_data_returns_display_ready_transl * @ticket 42281 */ public function test_wp_get_translation_update_data_falls_back_to_locale_and_slug() { + $plugin_update = (object) array( + 'type' => 'plugin', + 'slug' => 'missing-plugin', + 'language' => 'it_IT', + 'version' => '2.0.0', + ); + set_site_transient( 'update_plugins', (object) array( 'translations' => array( - array( - 'type' => 'plugin', - 'slug' => 'missing-plugin', - 'language' => 'it_IT', - 'version' => '2.0.0', - ), + (array) $plugin_update, ), ) ); @@ -133,6 +156,9 @@ public function test_wp_get_translation_update_data_falls_back_to_locale_and_slu $this->assertSame( array( array( + 'checked' => true, + 'deferred' => false, + 'id' => wp_get_translation_update_id( $plugin_update ), 'language' => 'it_IT', 'language_code' => 'it_IT', 'name' => 'missing-plugin', @@ -149,6 +175,13 @@ public function test_wp_get_translation_update_data_falls_back_to_locale_and_slu * @ticket 42281 */ public function test_wp_get_translation_update_data_matches_plugin_update_slug_to_plugin_file() { + $plugin_update = (object) array( + 'type' => 'plugin', + 'slug' => 'hello-dolly', + 'language' => 'de_DE', + 'version' => '1.7.2', + ); + add_filter( 'all_plugins', static function () { @@ -164,12 +197,7 @@ static function () { 'update_plugins', (object) array( 'translations' => array( - array( - 'type' => 'plugin', - 'slug' => 'hello-dolly', - 'language' => 'de_DE', - 'version' => '1.7.2', - ), + (array) $plugin_update, ), 'no_update' => array( 'hello.php' => (object) array( @@ -182,6 +210,9 @@ static function () { $this->assertSame( array( array( + 'checked' => true, + 'deferred' => false, + 'id' => wp_get_translation_update_id( $plugin_update ), 'language' => 'de_DE', 'language_code' => 'de_DE', 'name' => 'Hello Dolly', @@ -193,4 +224,108 @@ static function () { wp_get_translation_update_data() ); } + + /** + * @ticket 42281 + */ + public function test_wp_get_translation_updates_by_id_keys_updates_by_identifier() { + $core_update = (object) array( + 'type' => 'core', + 'slug' => 'default', + 'language' => 'de_DE', + 'version' => '6.7-beta3', + ); + + $this->assertSame( + array( + wp_get_translation_update_id( $core_update ) => $core_update, + ), + wp_get_translation_updates_by_id( array( $core_update ) ) + ); + } + + /** + * @ticket 42281 + */ + public function test_wp_get_translation_update_data_marks_deferred_translation_updates() { + $plugin_update = (object) array( + 'type' => 'plugin', + 'slug' => 'deferred-plugin', + 'language' => 'de_DE', + 'version' => '1.0.0', + ); + + set_site_transient( + 'update_plugins', + (object) array( + 'translations' => array( + (array) $plugin_update, + ), + ) + ); + + wp_set_deferred_translation_updates( array( $plugin_update ) ); + + $this->assertSame( + array( + array( + 'checked' => false, + 'deferred' => true, + 'id' => wp_get_translation_update_id( $plugin_update ), + 'language' => 'de_DE', + 'language_code' => 'de_DE', + 'name' => 'deferred-plugin', + 'slug' => 'deferred-plugin', + 'type' => 'plugin', + 'version' => '1.0.0', + ), + ), + wp_get_translation_update_data() + ); + } + + /** + * @ticket 42281 + */ + public function test_wp_get_deferred_translation_updates_filters_to_available_updates() { + $available_update = (object) array( + 'type' => 'plugin', + 'slug' => 'available-plugin', + 'language' => 'de_DE', + 'version' => '1.0.0', + ); + + $stale_update = (object) array( + 'type' => 'plugin', + 'slug' => 'stale-plugin', + 'language' => 'de_DE', + 'version' => '1.0.0', + ); + + wp_set_deferred_translation_updates( array( $available_update, $stale_update ) ); + + $this->assertSame( + array( + wp_get_translation_update_id( $available_update ) => array( + 'type' => 'plugin', + 'slug' => 'available-plugin', + 'language' => 'de_DE', + 'version' => '1.0.0', + ), + ), + wp_get_deferred_translation_updates( array( $available_update ) ) + ); + + $this->assertTrue( wp_is_translation_update_deferred( $available_update ) ); + $this->assertFalse( + wp_is_translation_update_deferred( + (object) array( + 'type' => 'plugin', + 'slug' => 'different-plugin', + 'language' => 'de_DE', + 'version' => '1.0.0', + ) + ) + ); + } }