3 namespace Drupal\locale;
5 use Drupal\Core\Config\ConfigCrudEvent;
6 use Drupal\Core\Config\ConfigEvents;
7 use Drupal\Core\Config\ConfigFactoryInterface;
8 use Drupal\Core\Config\StorableConfigBase;
9 use Drupal\language\Config\LanguageConfigOverrideCrudEvent;
10 use Drupal\language\Config\LanguageConfigOverrideEvents;
11 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
14 * Updates strings translation when configuration translations change.
16 * This reacts to the updates of translated active configuration and
17 * configuration language overrides. When those updates involve configuration
18 * which was available as default configuration, we need to feed back changes
19 * to any item which was originally part of that configuration to the interface
20 * translation storage. Those updated translations are saved as customized, so
21 * further community translation updates will not undo user changes.
23 * This subscriber does not respond to deleting active configuration or deleting
24 * configuration translations. The locale storage is additive and we cannot be
25 * sure that only a given configuration translation used a source string. So
26 * we should not remove the translations from locale storage in these cases. The
27 * configuration or override would itself be deleted either way.
29 * By design locale module only deals with sources in English.
31 * @see \Drupal\locale\LocaleConfigManager
33 class LocaleConfigSubscriber implements EventSubscriberInterface {
36 * The configuration factory.
38 * @var \Drupal\Core\Config\ConfigFactoryInterface
40 protected $configFactory;
43 * The typed configuration manager.
45 * @var \Drupal\locale\LocaleConfigManager
47 protected $localeConfigManager;
50 * Constructs a LocaleConfigSubscriber.
52 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
53 * The configuration factory.
54 * @param \Drupal\locale\LocaleConfigManager $locale_config_manager
55 * The typed configuration manager.
57 public function __construct(ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager) {
58 $this->configFactory = $config_factory;
59 $this->localeConfigManager = $locale_config_manager;
65 public static function getSubscribedEvents() {
66 $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onOverrideChange';
67 $events[LanguageConfigOverrideEvents::DELETE_OVERRIDE] = 'onOverrideChange';
68 $events[ConfigEvents::SAVE] = 'onConfigSave';
73 * Updates the locale strings when a translated active configuration is saved.
75 * @param \Drupal\Core\Config\ConfigCrudEvent $event
76 * The configuration event.
78 public function onConfigSave(ConfigCrudEvent $event) {
79 // Only attempt to feed back configuration translation changes to locale if
80 // the update itself was not initiated by locale data changes.
81 if (!drupal_installation_attempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {
82 $config = $event->getConfig();
83 $langcode = $config->get('langcode') ?: 'en';
84 $this->updateLocaleStorage($config, $langcode);
89 * Updates the locale strings when a configuration override is saved/deleted.
91 * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event
92 * The language configuration event.
94 public function onOverrideChange(LanguageConfigOverrideCrudEvent $event) {
95 // Only attempt to feed back configuration override changes to locale if
96 // the update itself was not initiated by locale data changes.
97 if (!drupal_installation_attempted() && !$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {
98 $translation_config = $event->getLanguageConfigOverride();
99 $langcode = $translation_config->getLangcode();
100 $reference_config = $this->configFactory->getEditable($translation_config->getName())->get();
101 $this->updateLocaleStorage($translation_config, $langcode, $reference_config);
106 * Update locale storage based on configuration translations.
108 * @param \Drupal\Core\Config\StorableConfigBase $config
109 * Active configuration or configuration translation override.
110 * @param string $langcode
111 * The language code of $config.
112 * @param array $reference_config
113 * (Optional) Reference configuration to check against if $config was an
114 * override. This allows us to update locale keys for data not in the
115 * override but still in the active configuration.
117 protected function updateLocaleStorage(StorableConfigBase $config, $langcode, array $reference_config = []) {
118 $name = $config->getName();
119 if ($this->localeConfigManager->isSupported($name) && locale_is_translatable($langcode)) {
120 $translatables = $this->localeConfigManager->getTranslatableDefaultConfig($name);
121 $this->processTranslatableData($name, $config->get(), $translatables, $langcode, $reference_config);
126 * Process the translatable data array with a given language.
128 * @param string $name
129 * The configuration name.
130 * @param array $config
131 * The active configuration data or override data.
132 * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup[] $translatable
133 * The translatable array structure.
134 * @see \Drupal\locale\LocaleConfigManager::getTranslatableData()
135 * @param string $langcode
136 * The language code to process the array with.
137 * @param array $reference_config
138 * (Optional) Reference configuration to check against if $config was an
139 * override. This allows us to update locale keys for data not in the
140 * override but still in the active configuration.
142 protected function processTranslatableData($name, array $config, array $translatable, $langcode, array $reference_config = []) {
143 foreach ($translatable as $key => $item) {
144 if (!isset($config[$key])) {
145 if (isset($reference_config[$key])) {
146 $this->resetExistingTranslations($name, $translatable[$key], $reference_config[$key], $langcode);
150 if (is_array($item)) {
151 $reference_config = isset($reference_config[$key]) ? $reference_config[$key] : [];
152 $this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config);
155 $this->saveCustomizedTranslation($name, $item->getUntranslatedString(), $item->getOption('context'), $config[$key], $langcode);
161 * Reset existing locale translations to their source values.
163 * Goes through $translatable to reset any existing translations to the source
164 * string, so prior translations would not reappear in the configuration.
166 * @param string $name
167 * The configuration name.
168 * @param array|\Drupal\Core\StringTranslation\TranslatableMarkup $translatable
169 * Either a possibly nested array with TranslatableMarkup objects at the
170 * leaf items or a TranslatableMarkup object directly.
171 * @param array|string $reference_config
172 * Either a possibly nested array with strings at the leaf items or a string
173 * directly. Only those $translatable items that are also present in
174 * $reference_config will get translations reset.
175 * @param string $langcode
176 * The language code of the translation being processed.
178 protected function resetExistingTranslations($name, $translatable, $reference_config, $langcode) {
179 if (is_array($translatable)) {
180 foreach ($translatable as $key => $item) {
181 if (isset($reference_config[$key])) {
182 // Process further if the key still exists in the reference active
183 // configuration and the default translation but not the current
184 // configuration override.
185 $this->resetExistingTranslations($name, $item, $reference_config[$key], $langcode);
189 elseif (!is_array($reference_config)) {
190 $this->saveCustomizedTranslation($name, $translatable->getUntranslatedString(), $translatable->getOption('context'), $reference_config, $langcode);
195 * Saves a translation string and marks it as customized.
197 * @param string $name
198 * The configuration name.
199 * @param string $source
200 * The source string value.
201 * @param string $context
202 * The source string context.
203 * @param string $new_translation
204 * The translation string.
205 * @param string $langcode
206 * The language code of the translation.
208 protected function saveCustomizedTranslation($name, $source, $context, $new_translation, $langcode) {
209 $locale_translation = $this->localeConfigManager->getStringTranslation($name, $langcode, $source, $context);
210 if (!empty($locale_translation)) {
211 // Save this translation as custom if it was a new translation and not the
212 // same as the source. (The interface prefills translation values with the
213 // source). Or if there was an existing (non-empty) translation and the
214 // user changed it (even if it was changed back to the original value).
215 // Otherwise the translation file would be overwritten with the locale
217 $existing_translation = $locale_translation->getString();
218 if (($locale_translation->isNew() && $source != $new_translation) ||
219 (!$locale_translation->isNew() && ((empty($existing_translation) && $source != $new_translation) || ((!empty($existing_translation) && $new_translation != $existing_translation))))) {
221 ->setString($new_translation)
222 ->setCustomized(TRUE)