Same name and namespace in other branches
  1. 8.9.x core/modules/locale/src/LocaleConfigSubscriber.php \Drupal\locale\LocaleConfigSubscriber
  2. 9 core/modules/locale/src/LocaleConfigSubscriber.php \Drupal\locale\LocaleConfigSubscriber

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.

Hierarchy

  • class \Drupal\locale\LocaleConfigSubscriber implements \Symfony\Component\EventDispatcher\EventSubscriberInterface

Expanded class hierarchy of LocaleConfigSubscriber

See also

\Drupal\locale\LocaleConfigManager

1 string reference to 'LocaleConfigSubscriber'
locale.services.yml in core/modules/locale/locale.services.yml
core/modules/locale/locale.services.yml
1 service uses LocaleConfigSubscriber
locale.config_subscriber in core/modules/locale/locale.services.yml
Drupal\locale\LocaleConfigSubscriber

File

core/modules/locale/src/LocaleConfigSubscriber.php, line 34

Namespace

Drupal\locale
View source
class LocaleConfigSubscriber implements EventSubscriberInterface {

  /**
   * The configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The typed configuration manager.
   *
   * @var \Drupal\locale\LocaleConfigManager
   */
  protected $localeConfigManager;

  /**
   * The language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * 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() : array {
    $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 (!InstallerKernel::installationAttempted() && !$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 (!InstallerKernel::installationAttempted() && !$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.
   */
  public 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_item = $reference_config[$key] ?? [];
        $this
          ->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config_item);
      }
      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)) {

      // If this code is triggered during installation never set the translation
      // to the source string.
      if (InstallerKernel::installationAttempted() && $source === $new_translation) {
        return;
      }

      // 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();
      }
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
LocaleConfigSubscriber::$configFactory protected property The configuration factory.
LocaleConfigSubscriber::$languageManager protected property The language manager.
LocaleConfigSubscriber::$localeConfigManager protected property The typed configuration manager.
LocaleConfigSubscriber::getSubscribedEvents public static function
LocaleConfigSubscriber::onConfigSave public function Updates the locale strings when a translated active configuration is saved.
LocaleConfigSubscriber::onOverrideChange public function Updates the locale strings when a configuration override is saved/deleted.
LocaleConfigSubscriber::processTranslatableData protected function Process the translatable data array with a given language.
LocaleConfigSubscriber::resetExistingTranslations protected function Reset existing locale translations to their source values.
LocaleConfigSubscriber::saveCustomizedTranslation protected function Saves a translation string and marks it as customized.
LocaleConfigSubscriber::updateLocaleStorage public function Update locale storage based on configuration translations.
LocaleConfigSubscriber::__construct public function Constructs a LocaleConfigSubscriber.