From e029e6cdb521ade09f9be6556ecf926e1b33f293 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Fri, 17 Mar 2023 17:35:34 +0000
Subject: [PATCH] =?UTF-8?q?Issue=20#2806009=20by=20alexpott,=20JvE,=20Berd?=
 =?UTF-8?q?ir,=20Dmitriy.trt,=20jhodgdon,=20lokapujya,=20VladimirAus,=20G?=
 =?UTF-8?q?=C3=A1bor=20Hojtsy,=20Jose=20Reyero,=20Anybody,=20kristiaanvand?=
 =?UTF-8?q?eneynde,=20Sutharsan,=20casey,=20smustgrave,=20nod=5F:=20Instal?=
 =?UTF-8?q?ling=20a=20module=20causes=20translations=20to=20be=20overwritt?=
 =?UTF-8?q?en?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 core/includes/install.core.inc                |  2 +-
 core/modules/locale/locale.bulk.inc           | 33 +++++++++-
 core/modules/locale/locale.module             | 29 +--------
 .../locale/src/LocaleConfigManager.php        | 30 +++++++++
 .../schema/locale_test_translate.schema.yml   |  2 +
 .../locale_test_translate.module              | 12 ++++
 .../LocaleConfigTranslationImportTest.php     | 62 +++++++++++++++++++
 7 files changed, 139 insertions(+), 31 deletions(-)

diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 6584680de656..e3136e755eb8 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -1841,7 +1841,7 @@ function install_finish_translations(&$install_state) {
   }
 
   // Creates configuration translations.
-  $batches[] = locale_config_batch_update_components([], array_keys($languages));
+  $batches[] = locale_config_batch_update_components([], array_keys($languages), [], TRUE);
   return $batches;
 }
 
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 6c7a986bcd01..eb68d9d32c24 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -538,14 +538,19 @@ function locale_translate_delete_translation_files(array $projects = [], array $
  * @param array $components
  *   (optional) Array of component lists indexed by type. If not present or it
  *   is an empty array, it will update all components.
+ * @param bool $update_default_config_langcodes
+ *   Determines whether default configuration langcodes should be updated. This
+ *   Should only happen during site and extension install.
  *
  * @return array
  *   The batch definition.
  */
-function locale_config_batch_update_components(array $options, array $langcodes = [], array $components = []) {
+function locale_config_batch_update_components(array $options, array $langcodes = [], array $components = [], bool $update_default_config_langcodes = FALSE) {
   $langcodes = $langcodes ? $langcodes : array_keys(\Drupal::languageManager()->getLanguages());
   if ($langcodes && $names = Locale::config()->getComponentNames($components)) {
-    return locale_config_batch_build($names, $langcodes, $options);
+    // If the component list is empty we need to ensure that all configuration
+    // in the default collection is using the site's default langcode.
+    return locale_config_batch_build($names, $langcodes, $options, $update_default_config_langcodes);
   }
 }
 
@@ -560,19 +565,27 @@ function locale_config_batch_update_components(array $options, array $langcodes
  *   (optional) An array with options that can have the following elements:
  *   - 'finish_feedback': Whether or not to give feedback to the user when the
  *     batch is finished. Defaults to TRUE.
+ * @param bool $update_default_config_langcodes
+ *   Determines whether default configuration langcodes should be updated. This
+ *   Should only happen during site and extension install.
  *
  * @return array
  *   The batch definition.
  *
  * @see locale_config_batch_refresh_name()
  */
-function locale_config_batch_build(array $names, array $langcodes, array $options = []) {
+function locale_config_batch_build(array $names, array $langcodes, array $options = [], bool $update_default_config_langcodes = FALSE) {
   $options += ['finish_feedback' => TRUE];
   $batch_builder = (new BatchBuilder())
     ->setFile(\Drupal::service('extension.list.module')->getPath('locale') . '/locale.bulk.inc')
     ->setTitle(t('Updating configuration translations'))
     ->setInitMessage(t('Starting configuration update'))
     ->setErrorMessage(t('Error updating configuration translations'));
+
+  if ($update_default_config_langcodes && \Drupal::languageManager()->getDefaultLanguage()->getId() !== 'en') {
+    $batch_builder->addOperation('locale_config_batch_set_config_langcodes');
+  }
+
   $i = 0;
   $batch_names = [];
   foreach ($names as $name) {
@@ -596,6 +609,20 @@ function locale_config_batch_build(array $names, array $langcodes, array $option
   return $batch_builder->toArray();
 }
 
+/**
+ * Implements callback_batch_operation().
+ *
+ * Updates default configuration when new modules or themes are installed.
+ *
+ * @param array|\ArrayAccess $context
+ *   The batch context.
+ */
+function locale_config_batch_set_config_langcodes(&$context) {
+  Locale::config()->updateDefaultConfigLangcodes();
+  $context['finished'] = 1;
+  $context['message'] = t('Updated default configuration to %langcode', ['%langcode' => \Drupal::languageManager()->getDefaultLanguage()->getId()]);
+}
+
 /**
  * Implements callback_batch_operation().
  *
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 021af78b59ad..c06ea62bb237 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -319,8 +319,6 @@ function locale_get_plural($count, $langcode = NULL) {
  * Implements hook_modules_installed().
  */
 function locale_modules_installed($modules) {
-  locale_system_set_config_langcodes();
-
   $components['module'] = $modules;
   locale_system_update($components);
 }
@@ -337,8 +335,6 @@ function locale_module_preuninstall($module) {
  * Implements hook_themes_installed().
  */
 function locale_themes_installed($themes) {
-  locale_system_set_config_langcodes();
-
   $components['theme'] = $themes;
   locale_system_update($components);
 }
@@ -370,28 +366,7 @@ function locale_cron() {
  * Updates default configuration when new modules or themes are installed.
  */
 function locale_system_set_config_langcodes() {
-  // Need to rewrite some default configuration language codes if the default
-  // site language is not English.
-  $default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
-  if ($default_langcode != 'en') {
-    // Update active configuration copies of all prior shipped configuration if
-    // they are still English. It is not enough to change configuration shipped
-    // with the components just installed, because installing a component such
-    // as views or tour module may bring in default configuration from prior
-    // components.
-    $names = Locale::config()->getComponentNames();
-    foreach ($names as $name) {
-      $config = \Drupal::configFactory()->reset($name)->getEditable($name);
-      // Should only update if still exists in active configuration. If locale
-      // module is enabled later, then some configuration may not exist anymore.
-      if (!$config->isNew()) {
-        $langcode = $config->get('langcode');
-        if (empty($langcode) || $langcode == 'en') {
-          $config->set('langcode', $default_langcode)->save();
-        }
-      }
-    }
-  }
+  Locale::config()->updateDefaultConfigLangcodes();
 }
 
 /**
@@ -435,7 +410,7 @@ function locale_system_update(array $components) {
     // components. Do this even if import is not enabled because parsing new
     // configuration may expose new source strings.
     $module_handler->loadInclude('locale', 'inc', 'locale.bulk');
-    if ($batch = locale_config_batch_update_components([])) {
+    if ($batch = locale_config_batch_update_components([], [], [], TRUE)) {
       batch_set($batch);
     }
   }
diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php
index 2ebdd735c938..bb265cbc8b0c 100644
--- a/core/modules/locale/src/LocaleConfigManager.php
+++ b/core/modules/locale/src/LocaleConfigManager.php
@@ -649,4 +649,34 @@ protected function filterOverride(array $override_data, array $translatable) {
     return $filtered_data;
   }
 
+  /**
+   * Updates default configuration when new modules or themes are installed.
+   */
+  public function updateDefaultConfigLangcodes() {
+    $this->isUpdatingFromLocale = TRUE;
+    // Need to rewrite some default configuration language codes if the default
+    // site language is not English.
+    $default_langcode = $this->languageManager->getDefaultLanguage()->getId();
+    if ($default_langcode != 'en') {
+      // Update active configuration copies of all prior shipped configuration if
+      // they are still English. It is not enough to change configuration shipped
+      // with the components just installed, because installing a component such
+      // as views or tour module may bring in default configuration from prior
+      // components.
+      $names = $this->getComponentNames();
+      foreach ($names as $name) {
+        $config = $this->configFactory->reset($name)->getEditable($name);
+        // Should only update if still exists in active configuration. If locale
+        // module is enabled later, then some configuration may not exist anymore.
+        if (!$config->isNew()) {
+          $langcode = $config->get('langcode');
+          if (empty($langcode) || $langcode == 'en') {
+            $config->set('langcode', $default_langcode)->save();
+          }
+        }
+      }
+    }
+    $this->isUpdatingFromLocale = FALSE;
+  }
+
 }
diff --git a/core/modules/locale/tests/modules/locale_test_translate/config/schema/locale_test_translate.schema.yml b/core/modules/locale/tests/modules/locale_test_translate/config/schema/locale_test_translate.schema.yml
index 93e57c698dc8..df9fd824a251 100644
--- a/core/modules/locale/tests/modules/locale_test_translate/config/schema/locale_test_translate.schema.yml
+++ b/core/modules/locale/tests/modules/locale_test_translate/config/schema/locale_test_translate.schema.yml
@@ -8,3 +8,5 @@ locale_test_translate.settings:
       type: label
     translatable_default_with_no_translation:
       type: label
+    key_set_during_install:
+      type: boolean
diff --git a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module
index e55d4dc486eb..ed7911df9ae0 100644
--- a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module
+++ b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module
@@ -20,3 +20,15 @@ function locale_test_translate_system_info_alter(&$info, Extension $file, $type)
     $info['hidden'] = FALSE;
   }
 }
+
+/**
+ * Implements hook_modules_installed().
+ *
+ * @see \Drupal\Tests\locale\Functional\LocaleConfigTranslationImportTest::testConfigTranslationWithForeignLanguageDefault
+ */
+function locale_test_translate_modules_installed($modules, $is_syncing) {
+  // Ensure that writing to configuration during install does not cause
+  // \Drupal\locale\LocaleConfigSubscriber to create incorrect translations due
+  // the configuration langcode and data being out-of-sync.
+  \Drupal::configFactory()->getEditable('locale_test_translate.settings')->set('key_set_during_install', TRUE)->save();
+}
diff --git a/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php b/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php
index 72db6ef1b5e7..de506587d3f9 100644
--- a/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php
+++ b/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php
@@ -305,4 +305,66 @@ public function testLocaleRemovalAndConfigOverridePreserve() {
     $this->assertEquals($expected, $override->get());
   }
 
+  /**
+   * Tests setting a non-English language as default and importing configuration.
+   */
+  public function testConfigTranslationWithNonEnglishLanguageDefault() {
+    /** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */
+    $module_installer = $this->container->get('module_installer');
+    ConfigurableLanguage::createFromLangcode('af')->save();
+
+    $module_installer->install(['locale']);
+    $this->resetAll();
+    /** @var \Drupal\locale\StringStorageInterface $local_storage */
+    $local_storage = $this->container->get('locale.storage');
+
+    $source_string = 'Locale can translate';
+    $translation_string = 'Locale can translate Afrikaans';
+
+    // Create a translation for the "Locale can translate" string, this string
+    // can be found in the "locale_test_translate" module's install config.
+    $source = $local_storage->createString([
+      'source' => $source_string,
+    ])->save();
+    $local_storage->createTranslation([
+      'lid' => $source->getId(),
+      'language' => 'af',
+      'translation' => $translation_string,
+    ])->save();
+
+    // Verify that we can find the newly added string translation, it is not a
+    // customized translation.
+    $translation = $local_storage->findTranslation([
+      'source' => $source_string,
+      'language' => 'af',
+    ]);
+    $this->assertEquals($translation_string, $translation->getString());
+    $this->assertFalse((bool) $translation->customized);
+
+    // Uninstall the "locale_test_translate" module, verify that we can still
+    // find the string translation.
+    $module_installer->uninstall(['locale_test_translate']);
+    $this->resetAll();
+    $translation = $local_storage->findTranslation([
+      'source' => $source_string,
+      'language' => 'af',
+    ]);
+    $this->assertEquals($translation_string, $translation->getString());
+
+    // Set the default language to "Afrikaans" and re-enable the
+    // "locale_test_translate" module.
+    $this->config('system.site')->set('default_langcode', 'af')->save();
+    $module_installer->install(['locale_test_translate']);
+    $this->resetAll();
+
+    // Verify that enabling the "locale_test_translate" module didn't cause
+    // the string translation to be overwritten.
+    $translation = $local_storage->findTranslation([
+      'source' => $source_string,
+      'language' => 'af',
+    ]);
+    $this->assertEquals($translation_string, $translation->getString());
+    $this->assertFalse((bool) $translation->customized);
+  }
+
 }
-- 
GitLab