annotate core/modules/locale/src/LocaleConfigSubscriber.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 4c8ae668cc8c
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\locale;
Chris@0 4
Chris@0 5 use Drupal\Core\Config\ConfigCrudEvent;
Chris@0 6 use Drupal\Core\Config\ConfigEvents;
Chris@0 7 use Drupal\Core\Config\ConfigFactoryInterface;
Chris@0 8 use Drupal\Core\Config\StorableConfigBase;
Chris@0 9 use Drupal\language\Config\LanguageConfigOverrideCrudEvent;
Chris@0 10 use Drupal\language\Config\LanguageConfigOverrideEvents;
Chris@0 11 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
Chris@0 12
Chris@0 13 /**
Chris@0 14 * Updates strings translation when configuration translations change.
Chris@0 15 *
Chris@0 16 * This reacts to the updates of translated active configuration and
Chris@0 17 * configuration language overrides. When those updates involve configuration
Chris@0 18 * which was available as default configuration, we need to feed back changes
Chris@0 19 * to any item which was originally part of that configuration to the interface
Chris@0 20 * translation storage. Those updated translations are saved as customized, so
Chris@0 21 * further community translation updates will not undo user changes.
Chris@0 22 *
Chris@0 23 * This subscriber does not respond to deleting active configuration or deleting
Chris@0 24 * configuration translations. The locale storage is additive and we cannot be
Chris@0 25 * sure that only a given configuration translation used a source string. So
Chris@0 26 * we should not remove the translations from locale storage in these cases. The
Chris@0 27 * configuration or override would itself be deleted either way.
Chris@0 28 *
Chris@0 29 * By design locale module only deals with sources in English.
Chris@0 30 *
Chris@0 31 * @see \Drupal\locale\LocaleConfigManager
Chris@0 32 */
Chris@0 33 class LocaleConfigSubscriber implements EventSubscriberInterface {
Chris@0 34
Chris@0 35 /**
Chris@0 36 * The configuration factory.
Chris@0 37 *
Chris@0 38 * @var \Drupal\Core\Config\ConfigFactoryInterface
Chris@0 39 */
Chris@0 40 protected $configFactory;
Chris@0 41
Chris@0 42 /**
Chris@0 43 * The typed configuration manager.
Chris@0 44 *
Chris@0 45 * @var \Drupal\locale\LocaleConfigManager
Chris@0 46 */
Chris@0 47 protected $localeConfigManager;
Chris@0 48
Chris@0 49 /**
Chris@0 50 * Constructs a LocaleConfigSubscriber.
Chris@0 51 *
Chris@0 52 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
Chris@0 53 * The configuration factory.
Chris@0 54 * @param \Drupal\locale\LocaleConfigManager $locale_config_manager
Chris@0 55 * The typed configuration manager.
Chris@0 56 */
Chris@0 57 public function __construct(ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager) {
Chris@0 58 $this->configFactory = $config_factory;
Chris@0 59 $this->localeConfigManager = $locale_config_manager;
Chris@0 60 }
Chris@0 61
Chris@0 62 /**
Chris@0 63 * {@inheritdoc}
Chris@0 64 */
Chris@0 65 public static function getSubscribedEvents() {
Chris@0 66 $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onOverrideChange';
Chris@0 67 $events[LanguageConfigOverrideEvents::DELETE_OVERRIDE] = 'onOverrideChange';
Chris@0 68 $events[ConfigEvents::SAVE] = 'onConfigSave';
Chris@0 69 return $events;
Chris@0 70 }
Chris@0 71
Chris@0 72 /**
Chris@0 73 * Updates the locale strings when a translated active configuration is saved.
Chris@0 74 *
Chris@0 75 * @param \Drupal\Core\Config\ConfigCrudEvent $event
Chris@0 76 * The configuration event.
Chris@0 77 */
Chris@0 78 public function onConfigSave(ConfigCrudEvent $event) {
Chris@0 79 // Only attempt to feed back configuration translation changes to locale if
Chris@0 80 // the update itself was not initiated by locale data changes.
Chris@0 81 if (!drupal_installation_attempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {
Chris@0 82 $config = $event->getConfig();
Chris@0 83 $langcode = $config->get('langcode') ?: 'en';
Chris@0 84 $this->updateLocaleStorage($config, $langcode);
Chris@0 85 }
Chris@0 86 }
Chris@0 87
Chris@0 88 /**
Chris@0 89 * Updates the locale strings when a configuration override is saved/deleted.
Chris@0 90 *
Chris@0 91 * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event
Chris@0 92 * The language configuration event.
Chris@0 93 */
Chris@0 94 public function onOverrideChange(LanguageConfigOverrideCrudEvent $event) {
Chris@0 95 // Only attempt to feed back configuration override changes to locale if
Chris@0 96 // the update itself was not initiated by locale data changes.
Chris@0 97 if (!drupal_installation_attempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {
Chris@0 98 $translation_config = $event->getLanguageConfigOverride();
Chris@0 99 $langcode = $translation_config->getLangcode();
Chris@0 100 $reference_config = $this->configFactory->getEditable($translation_config->getName())->get();
Chris@0 101 $this->updateLocaleStorage($translation_config, $langcode, $reference_config);
Chris@0 102 }
Chris@0 103 }
Chris@0 104
Chris@0 105 /**
Chris@0 106 * Update locale storage based on configuration translations.
Chris@0 107 *
Chris@0 108 * @param \Drupal\Core\Config\StorableConfigBase $config
Chris@0 109 * Active configuration or configuration translation override.
Chris@0 110 * @param string $langcode
Chris@0 111 * The language code of $config.
Chris@0 112 * @param array $reference_config
Chris@0 113 * (Optional) Reference configuration to check against if $config was an
Chris@0 114 * override. This allows us to update locale keys for data not in the
Chris@0 115 * override but still in the active configuration.
Chris@0 116 */
Chris@0 117 protected function updateLocaleStorage(StorableConfigBase $config, $langcode, array $reference_config = []) {
Chris@0 118 $name = $config->getName();
Chris@0 119 if ($this->localeConfigManager->isSupported($name) && locale_is_translatable($langcode)) {
Chris@0 120 $translatables = $this->localeConfigManager->getTranslatableDefaultConfig($name);
Chris@0 121 $this->processTranslatableData($name, $config->get(), $translatables, $langcode, $reference_config);
Chris@0 122 }
Chris@0 123 }
Chris@0 124
Chris@0 125 /**
Chris@0 126 * Process the translatable data array with a given language.
Chris@0 127 *
Chris@0 128 * @param string $name
Chris@0 129 * The configuration name.
Chris@0 130 * @param array $config
Chris@0 131 * The active configuration data or override data.
Chris@0 132 * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup[] $translatable
Chris@0 133 * The translatable array structure.
Chris@0 134 * @see \Drupal\locale\LocaleConfigManager::getTranslatableData()
Chris@0 135 * @param string $langcode
Chris@0 136 * The language code to process the array with.
Chris@0 137 * @param array $reference_config
Chris@0 138 * (Optional) Reference configuration to check against if $config was an
Chris@0 139 * override. This allows us to update locale keys for data not in the
Chris@0 140 * override but still in the active configuration.
Chris@0 141 */
Chris@0 142 protected function processTranslatableData($name, array $config, array $translatable, $langcode, array $reference_config = []) {
Chris@0 143 foreach ($translatable as $key => $item) {
Chris@0 144 if (!isset($config[$key])) {
Chris@0 145 if (isset($reference_config[$key])) {
Chris@0 146 $this->resetExistingTranslations($name, $translatable[$key], $reference_config[$key], $langcode);
Chris@0 147 }
Chris@0 148 continue;
Chris@0 149 }
Chris@0 150 if (is_array($item)) {
Chris@0 151 $reference_config = isset($reference_config[$key]) ? $reference_config[$key] : [];
Chris@0 152 $this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config);
Chris@0 153 }
Chris@0 154 else {
Chris@0 155 $this->saveCustomizedTranslation($name, $item->getUntranslatedString(), $item->getOption('context'), $config[$key], $langcode);
Chris@0 156 }
Chris@0 157 }
Chris@0 158 }
Chris@0 159
Chris@0 160 /**
Chris@0 161 * Reset existing locale translations to their source values.
Chris@0 162 *
Chris@0 163 * Goes through $translatable to reset any existing translations to the source
Chris@0 164 * string, so prior translations would not reappear in the configuration.
Chris@0 165 *
Chris@0 166 * @param string $name
Chris@0 167 * The configuration name.
Chris@0 168 * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup $translatable
Chris@0 169 * Either a possibly nested array with TranslatableMarkup objects at the
Chris@0 170 * leaf items or a TranslatableMarkup object directly.
Chris@0 171 * @param array|string $reference_config
Chris@0 172 * Either a possibly nested array with strings at the leaf items or a string
Chris@0 173 * directly. Only those $translatable items that are also present in
Chris@0 174 * $reference_config will get translations reset.
Chris@0 175 * @param string $langcode
Chris@0 176 * The language code of the translation being processed.
Chris@0 177 */
Chris@0 178 protected function resetExistingTranslations($name, $translatable, $reference_config, $langcode) {
Chris@0 179 if (is_array($translatable)) {
Chris@0 180 foreach ($translatable as $key => $item) {
Chris@0 181 if (isset($reference_config[$key])) {
Chris@0 182 // Process further if the key still exists in the reference active
Chris@0 183 // configuration and the default translation but not the current
Chris@0 184 // configuration override.
Chris@0 185 $this->resetExistingTranslations($name, $item, $reference_config[$key], $langcode);
Chris@0 186 }
Chris@0 187 }
Chris@0 188 }
Chris@0 189 elseif (!is_array($reference_config)) {
Chris@0 190 $this->saveCustomizedTranslation($name, $translatable->getUntranslatedString(), $translatable->getOption('context'), $reference_config, $langcode);
Chris@0 191 }
Chris@0 192 }
Chris@0 193
Chris@0 194 /**
Chris@0 195 * Saves a translation string and marks it as customized.
Chris@0 196 *
Chris@0 197 * @param string $name
Chris@0 198 * The configuration name.
Chris@0 199 * @param string $source
Chris@0 200 * The source string value.
Chris@0 201 * @param string $context
Chris@0 202 * The source string context.
Chris@0 203 * @param string $new_translation
Chris@0 204 * The translation string.
Chris@0 205 * @param string $langcode
Chris@0 206 * The language code of the translation.
Chris@0 207 */
Chris@0 208 protected function saveCustomizedTranslation($name, $source, $context, $new_translation, $langcode) {
Chris@0 209 $locale_translation = $this->localeConfigManager->getStringTranslation($name, $langcode, $source, $context);
Chris@0 210 if (!empty($locale_translation)) {
Chris@0 211 // Save this translation as custom if it was a new translation and not the
Chris@0 212 // same as the source. (The interface prefills translation values with the
Chris@0 213 // source). Or if there was an existing (non-empty) translation and the
Chris@0 214 // user changed it (even if it was changed back to the original value).
Chris@0 215 // Otherwise the translation file would be overwritten with the locale
Chris@0 216 // copy again later.
Chris@0 217 $existing_translation = $locale_translation->getString();
Chris@0 218 if (($locale_translation->isNew() && $source != $new_translation) ||
Chris@0 219 (!$locale_translation->isNew() && ((empty($existing_translation) && $source != $new_translation) || ((!empty($existing_translation) && $new_translation != $existing_translation))))) {
Chris@0 220 $locale_translation
Chris@0 221 ->setString($new_translation)
Chris@0 222 ->setCustomized(TRUE)
Chris@0 223 ->save();
Chris@0 224 }
Chris@0 225 }
Chris@0 226 }
Chris@0 227
Chris@0 228 }