diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc index 5c84402268d..9cdadb31544 100644 --- a/core/modules/locale/locale.batch.inc +++ b/core/modules/locale/locale.batch.inc @@ -23,6 +23,43 @@ use Psr\Http\Message\UriInterface; // Follow-up issue: https://www.drupal.org/node/1834298. require_once __DIR__ . '/locale.translation.inc'; +/** + * Implements callback_batch_operation(). + * + * Checks for changed project versions, and cleans-up data from the old version. + * For example when a module is updated. This will make the translation import + * system use translations that match the current version. + * + * @param string $project + * Machine name of the project for which to check the translation status. + * @param string $langcode + * Language code of the language for which to check the translation. + * @param array|\ArrayAccess $context + * The batch context. + */ +function locale_translation_batch_version_check(string $project, string $langcode, array|\ArrayAccess &$context) { + $locale_project = \Drupal::service('locale.project')->get($project); + + if (empty($locale_project)) { + return; + } + + $status = \Drupal::keyValue('locale.translation_status')->get($project); + if (!isset($status[$langcode])) { + return; + } + + if ($locale_project['version'] == $status[$langcode]->version) { + return; + } + + \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc'); + locale_translation_status_delete_projects([$project]); + locale_translate_delete_translation_files([$project]); + + $context['message'] = t('Checked version of %project.', ['%project' => $project]); +} + /** * Implements callback_batch_operation(). * diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc index 5a5a9c165ad..634679e9fbb 100644 --- a/core/modules/locale/locale.compare.inc +++ b/core/modules/locale/locale.compare.inc @@ -267,7 +267,8 @@ function _locale_translation_batch_status_operations($projects, $langcodes, $opt foreach ($projects as $project) { foreach ($langcodes as $langcode) { - // Check status of local and remote translation sources. + // Check version and status translation sources. + $operations[] = ['locale_translation_batch_version_check', [$project, $langcode]]; $operations[] = ['locale_translation_batch_status_check', [$project, $langcode, $options]]; } } diff --git a/core/modules/locale/tests/src/Functional/LocaleTranslationChangeProjectVersionTest.php b/core/modules/locale/tests/src/Functional/LocaleTranslationChangeProjectVersionTest.php new file mode 100644 index 00000000000..46bbb1246ec --- /dev/null +++ b/core/modules/locale/tests/src/Functional/LocaleTranslationChangeProjectVersionTest.php @@ -0,0 +1,86 @@ +loadInclude('locale', 'inc', 'locale.batch'); + ConfigurableLanguage::createFromLangcode('de')->save(); + + \Drupal::state()->set('locale.test_projects_alter', TRUE); + \Drupal::state()->set('locale.remove_core_project', TRUE); + + // Setup the environment. + $config = $this->config('locale.settings'); + $public_path = PublicStream::basePath(); + $this->setTranslationsDirectory($public_path . '/local'); + $config + ->set('translation.default_filename', '%project-%version.%language._po') + ->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL) + ->save(); + + // This test uses .po files for the old translation file instead of the ._po + // files because locale_translate_get_interface_translation_files() (used to + // delete old translation files) only works with .po files. + // The new translation file uses _po. + // Old version: 8.x-1.0; New version: 8.x-1.1. + $this->makePoFile('remote/all/contrib_module_one', 'contrib_module_one-8.x-1.0.de.po', $this->timestampOld, []); + $this->makePoFile('remote/all/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestampNew, []); + $this->makePoFile('local', 'contrib_module_one-8.x-1.0.de.po', $this->timestampOld, []); + + // Initialize the projects status and change the project version to the old + // version. This makes the code update the module translation to the new + // version when the (batch) update script is triggered. + $status = locale_translation_get_status(); + $status['contrib_module_one']['de']->version = '8.x-1.0'; + \Drupal::keyValue('locale.translation_status')->setMultiple($status); + } + + /** + * Tests update translations when project version changes. + */ + public function testUpdateImportSourceRemote() { + + // Verify that the project status has the old version. + $status = locale_translation_get_status(['contrib_module_one']); + $this->assertEquals('8.x-1.0', $status['contrib_module_one']['de']->version); + + // Verify that the old translation file exists and the new does not exist. + $this->assertFileExists('translations://contrib_module_one-8.x-1.0.de.po'); + $this->assertFileDoesNotExist('translations://contrib_module_one-8.x-1.1.de._po'); + + // Run batch tasks. + $context = []; + locale_translation_batch_version_check('contrib_module_one', 'de', $context); + locale_translation_batch_status_check('contrib_module_one', 'de', [], $context); + locale_translation_batch_fetch_download('contrib_module_one', 'de', $context); + + // Verify that the project status has the new version. + $status = locale_translation_get_status(['contrib_module_one']); + $this->assertEquals('8.x-1.1', $status['contrib_module_one']['de']->version); + + // Verify that the old translation file was removed and the new was + // downloaded. + $this->assertFileDoesNotExist('translations://contrib_module_one-8.x-1.0.de.po'); + $this->assertFileExists('translations://contrib_module_one-8.x-1.1.de._po'); + } + +} diff --git a/core/modules/locale/tests/src/Functional/LocaleUpdateBase.php b/core/modules/locale/tests/src/Functional/LocaleUpdateBase.php index bfb2d0fe4bc..c4174caf26b 100644 --- a/core/modules/locale/tests/src/Functional/LocaleUpdateBase.php +++ b/core/modules/locale/tests/src/Functional/LocaleUpdateBase.php @@ -135,10 +135,11 @@ EOF; } \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY); + $fileUri = $path . '/' . $filename; $file = File::create([ 'uid' => 1, 'filename' => $filename, - 'uri' => $path . '/' . $filename, + 'uri' => $fileUri, 'filemime' => 'text/x-gettext-translation', 'timestamp' => $timestamp, ]); @@ -146,6 +147,9 @@ EOF; file_put_contents($file->getFileUri(), $po_header . $text); touch(\Drupal::service('file_system')->realpath($file->getFileUri()), $timestamp); $file->save(); + + $this->assertTrue(file_exists($fileUri)); + $this->assertEquals($timestamp, filemtime($fileUri)); } /** diff --git a/core/modules/locale/tests/src/Functional/LocaleUpdateCronTest.php b/core/modules/locale/tests/src/Functional/LocaleUpdateCronTest.php index e59732f336d..22c3c69d71d 100644 --- a/core/modules/locale/tests/src/Functional/LocaleUpdateCronTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleUpdateCronTest.php @@ -98,8 +98,12 @@ class LocaleUpdateCronTest extends LocaleUpdateBase { locale_cron(); // Check whether tasks are added to the queue. + // Expected tasks: + // - locale_translation_batch_version_check + // - locale_translation_batch_status_check + // - locale_translation_batch_status_finished. $queue = \Drupal::queue('locale_translation', TRUE); - $this->assertEquals(2, $queue->numberOfItems(), 'Queue holds tasks for one project.'); + $this->assertEquals(3, $queue->numberOfItems(), 'Queue holds tasks for one project.'); $item = $queue->claimItem(); $queue->releaseItem($item); $this->assertEquals('contrib_module_two', $item->data[1][0], 'Queue holds tasks for contrib module one.'); @@ -110,7 +114,7 @@ class LocaleUpdateCronTest extends LocaleUpdateBase { // Check whether no more tasks are added to the queue. $queue = \Drupal::queue('locale_translation', TRUE); - $this->assertEquals(2, $queue->numberOfItems(), 'Queue holds tasks for one project.'); + $this->assertEquals(3, $queue->numberOfItems(), 'Queue holds tasks for one project.'); // Ensure last checked is updated to a greater time than the initial value. sleep(1);