| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 | <?phpnamespace Drupal\locale;use Drupal\Core\Config\ConfigCrudEvent;use Drupal\Core\Config\ConfigEvents;use Drupal\Core\Config\ConfigFactoryInterface;use Drupal\Core\Config\StorableConfigBase;use Drupal\language\Config\LanguageConfigOverrideCrudEvent;use Drupal\language\Config\LanguageConfigOverrideEvents;use Symfony\Component\EventDispatcher\EventSubscriberInterface;/** * Updates strings translation when configuration translations change. * * This reacts to the updates of translated active configuration and * configuration language overrides. When those updates involve configuration * which was available as default configuration, we need to feed back changes * to any item which was originally part of that configuration to the interface * translation storage. Those updated translations are saved as customized, so * further community translation updates will not undo user changes. * * This subscriber does not respond to deleting active configuration or deleting * configuration translations. The locale storage is additive and we cannot be * sure that only a given configuration translation used a source string. So * we should not remove the translations from locale storage in these cases. The * configuration or override would itself be deleted either way. * * By design locale module only deals with sources in English. * * @see \Drupal\locale\LocaleConfigManager */class LocaleConfigSubscriber implements EventSubscriberInterface {  /**   * The configuration factory.   *   * @var \Drupal\Core\Config\ConfigFactoryInterface   */  protected $configFactory;  /**   * The typed configuration manager.   *   * @var \Drupal\locale\LocaleConfigManager   */  protected $localeConfigManager;  /**   * Constructs a LocaleConfigSubscriber.   *   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory   *   The configuration factory.   * @param \Drupal\locale\LocaleConfigManager $locale_config_manager   *   The typed configuration manager.   */  public function __construct(ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager) {    $this->configFactory = $config_factory;    $this->localeConfigManager = $locale_config_manager;  }  /**   * {@inheritdoc}   */  public static function getSubscribedEvents() {    $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onOverrideChange';    $events[LanguageConfigOverrideEvents::DELETE_OVERRIDE] = 'onOverrideChange';    $events[ConfigEvents::SAVE] = 'onConfigSave';    return $events;  }  /**   * Updates the locale strings when a translated active configuration is saved.   *   * @param \Drupal\Core\Config\ConfigCrudEvent $event   *   The configuration event.   */  public function onConfigSave(ConfigCrudEvent $event) {    // Only attempt to feed back configuration translation changes to locale if    // the update itself was not initiated by locale data changes.    if (!drupal_installation_attempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {      $config = $event->getConfig();      $langcode = $config->get('langcode') ?: 'en';      $this->updateLocaleStorage($config, $langcode);    }  }  /**   * Updates the locale strings when a configuration override is saved/deleted.   *   * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event   *   The language configuration event.   */  public function onOverrideChange(LanguageConfigOverrideCrudEvent $event) {    // Only attempt to feed back configuration override changes to locale if    // the update itself was not initiated by locale data changes.    if (!drupal_installation_attempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {      $translation_config = $event->getLanguageConfigOverride();      $langcode = $translation_config->getLangcode();      $reference_config = $this->configFactory->getEditable($translation_config->getName())->get();      $this->updateLocaleStorage($translation_config, $langcode, $reference_config);    }  }  /**   * Update locale storage based on configuration translations.   *   * @param \Drupal\Core\Config\StorableConfigBase $config   *   Active configuration or configuration translation override.   * @param string $langcode   *   The language code of $config.   * @param array $reference_config   *   (Optional) Reference configuration to check against if $config was an   *   override. This allows us to update locale keys for data not in the   *   override but still in the active configuration.   */  protected function updateLocaleStorage(StorableConfigBase $config, $langcode, array $reference_config = []) {    $name = $config->getName();    if ($this->localeConfigManager->isSupported($name) && locale_is_translatable($langcode)) {      $translatables = $this->localeConfigManager->getTranslatableDefaultConfig($name);      $this->processTranslatableData($name, $config->get(), $translatables, $langcode, $reference_config);    }  }  /**   * Process the translatable data array with a given language.   *   * @param string $name   *   The configuration name.   * @param array $config   *   The active configuration data or override data.   * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup[] $translatable   *   The translatable array structure.   *   @see \Drupal\locale\LocaleConfigManager::getTranslatableData()   * @param string $langcode   *   The language code to process the array with.   * @param array $reference_config   *   (Optional) Reference configuration to check against if $config was an   *   override. This allows us to update locale keys for data not in the   *   override but still in the active configuration.   */  protected function processTranslatableData($name, array $config, array $translatable, $langcode, array $reference_config = []) {    foreach ($translatable as $key => $item) {      if (!isset($config[$key])) {        if (isset($reference_config[$key])) {          $this->resetExistingTranslations($name, $translatable[$key], $reference_config[$key], $langcode);        }        continue;      }      if (is_array($item)) {        $reference_config = isset($reference_config[$key]) ? $reference_config[$key] : [];        $this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config);      }      else {        $this->saveCustomizedTranslation($name, $item->getUntranslatedString(), $item->getOption('context'), $config[$key], $langcode);      }    }  }  /**   * Reset existing locale translations to their source values.   *   * Goes through $translatable to reset any existing translations to the source   * string, so prior translations would not reappear in the configuration.   *   * @param string $name   *   The configuration name.   * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup $translatable   *   Either a possibly nested array with TranslatableMarkup objects at the   *   leaf items or a TranslatableMarkup object directly.   * @param array|string $reference_config   *   Either a possibly nested array with strings at the leaf items or a string   *   directly. Only those $translatable items that are also present in   *   $reference_config will get translations reset.   * @param string $langcode   *   The language code of the translation being processed.   */  protected function resetExistingTranslations($name, $translatable, $reference_config, $langcode) {    if (is_array($translatable)) {      foreach ($translatable as $key => $item) {        if (isset($reference_config[$key])) {          // Process further if the key still exists in the reference active          // configuration and the default translation but not the current          // configuration override.          $this->resetExistingTranslations($name, $item, $reference_config[$key], $langcode);        }      }    }    elseif (!is_array($reference_config)) {      $this->saveCustomizedTranslation($name, $translatable->getUntranslatedString(), $translatable->getOption('context'), $reference_config, $langcode);    }  }  /**   * Saves a translation string and marks it as customized.   *   * @param string $name   *   The configuration name.   * @param string $source   *   The source string value.   * @param string $context   *   The source string context.   * @param string $new_translation   *   The translation string.   * @param string $langcode   *   The language code of the translation.   */  protected function saveCustomizedTranslation($name, $source, $context, $new_translation, $langcode) {    $locale_translation = $this->localeConfigManager->getStringTranslation($name, $langcode, $source, $context);    if (!empty($locale_translation)) {      // Save this translation as custom if it was a new translation and not the      // same as the source. (The interface prefills translation values with the      // source). Or if there was an existing (non-empty) translation and the      // user changed it (even if it was changed back to the original value).      // Otherwise the translation file would be overwritten with the locale      // copy again later.      $existing_translation = $locale_translation->getString();      if (($locale_translation->isNew() && $source != $new_translation) ||        (!$locale_translation->isNew() && ((empty($existing_translation) && $source != $new_translation) || ((!empty($existing_translation) && $new_translation != $existing_translation))))) {        $locale_translation          ->setString($new_translation)          ->setCustomized(TRUE)          ->save();      }    }  }}
 |