diff --git a/core/lib/Drupal/Core/Config/ConfigEvents.php b/core/lib/Drupal/Core/Config/ConfigEvents.php index 2b2d2c8..7fc0750 100644 --- a/core/lib/Drupal/Core/Config/ConfigEvents.php +++ b/core/lib/Drupal/Core/Config/ConfigEvents.php @@ -23,18 +23,6 @@ const SAVE = 'config.save'; /** - * Name of event fired when saving the configuration override. - * - * This event is not used by the configuration system itself but should be - * used by implementors of configuration overrides. See Language module's - * implementation for an example. - * - * @see \Drupal\Core\Config\ConfigOverrideCrudEvent - * @see \Drupal\language\Config\LanguageConfigOverride::save() - */ - const SAVE_OVERRIDE = 'config.save_override'; - - /** * Name of event fired when deleting the configuration object. * * @see \Drupal\Core\Config\Config::delete() @@ -42,18 +30,6 @@ const DELETE = 'config.delete'; /** - * Name of event fired when deleting the configuration override. - * - * This event is not used by the configuration system itself but should be - * used by implementors of configuration overrides. See Language module's - * implementation for an example. - * - * @see \Drupal\Core\Config\ConfigOverrideCrudEvent - * @see \Drupal\language\Config\LanguageConfigOverride::delete() - */ - const DELETE_OVERRIDE = 'config.delete_override'; - - /** * Name of event fired when renaming a configuration object. * * @see \Drupal\Core\Config\ConfigFactoryInterface::rename(). diff --git a/core/lib/Drupal/Core/Config/ConfigOverrideCrudEvent.php b/core/lib/Drupal/Core/Config/ConfigOverrideCrudEvent.php deleted file mode 100644 index 377221c..0000000 --- a/core/lib/Drupal/Core/Config/ConfigOverrideCrudEvent.php +++ /dev/null @@ -1,45 +0,0 @@ -config = $config; - } - - /** - * Gets configuration object. - * - * @return \Drupal\Core\Config\StorableConfigBase - * The configuration object that caused the event to fire. - */ - public function getConfig() { - return $this->config; - } - -} - diff --git a/core/modules/language/src/Config/LanguageConfigCollectionNameTrait.php b/core/modules/language/src/Config/LanguageConfigCollectionNameTrait.php new file mode 100644 index 0000000..9feaf77 --- /dev/null +++ b/core/modules/language/src/Config/LanguageConfigCollectionNameTrait.php @@ -0,0 +1,53 @@ + $collection))); + } + return $matches[1]; + } + +} diff --git a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php index 9c2e04b..3d0a67b 100644 --- a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php +++ b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php @@ -7,7 +7,6 @@ namespace Drupal\language\Config; -use Drupal\Component\Utility\String; use Drupal\Core\Config\ConfigCollectionInfo; use Drupal\Core\Config\ConfigCrudEvent; use Drupal\Core\Config\ConfigFactoryOverrideBase; @@ -24,6 +23,8 @@ */ class LanguageConfigFactoryOverride extends ConfigFactoryOverrideBase implements LanguageConfigFactoryOverrideInterface, EventSubscriberInterface { + use LanguageConfigCollectionNameTrait; + /** * The configuration storage. * @@ -98,7 +99,6 @@ public function getOverride($langcode, $name) { $override = new LanguageConfigOverride( $name, - $langcode, $storage, $this->typedConfigManager, $this->eventDispatcher @@ -168,42 +168,6 @@ public function createConfigObject($name, $collection = StorageInterface::DEFAUL } /** - * Creates a configuration collection name based on a langcode. - * - * @param string $langcode - * The langcode. - * - * @return string - * The configuration collection name for a langcode. - */ - protected function createConfigCollectionName($langcode) { - return 'language.' . $langcode; - } - - /** - * Converts a configuration collection name to a langcode. - * - * @param string $collection - * The configuration collection name. - * - * @return string - * The langcode of the collection. - * - * @throws \InvalidArgumentException - * Exception thrown if the provided collection name is not in the format - * "language.LANGCODE". - * - * @see self::createConfigCollectionName() - */ - protected function getLangcodeFromCollectionName($collection) { - preg_match('/^language\.(.*)$/', $collection, $matches); - if (!isset($matches[1])) { - throw new \InvalidArgumentException(String::format('!collection is not a valid language override collection', array('!collection' => $collection))); - } - return $matches[1]; - } - - /** * {@inheritdoc} */ public function addCollections(ConfigCollectionInfo $collection_info) { diff --git a/core/modules/language/src/Config/LanguageConfigOverride.php b/core/modules/language/src/Config/LanguageConfigOverride.php index 11a9ed7..d64dda7 100644 --- a/core/modules/language/src/Config/LanguageConfigOverride.php +++ b/core/modules/language/src/Config/LanguageConfigOverride.php @@ -7,8 +7,6 @@ namespace Drupal\language\Config; -use Drupal\Core\Config\ConfigEvents; -use Drupal\Core\Config\ConfigOverrideCrudEvent; use Drupal\Core\Config\StorableConfigBase; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\TypedConfigManagerInterface; @@ -19,12 +17,7 @@ */ class LanguageConfigOverride extends StorableConfigBase { - /** - * The language code of this language override. - * - * @var string - */ - protected $langcode; + use LanguageConfigCollectionNameTrait; /** * The event dispatcher. @@ -38,8 +31,6 @@ class LanguageConfigOverride extends StorableConfigBase { * * @param string $name * The name of the configuration object being overridden. - * @param string $langcode - * The language code of the language of this language override. * @param \Drupal\Core\Config\StorageInterface $storage * A storage controller object to use for reading and writing the * configuration override. @@ -48,9 +39,8 @@ class LanguageConfigOverride extends StorableConfigBase { * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. */ - public function __construct($name, $langcode, StorageInterface $storage, TypedConfigManagerInterface $typed_config, EventDispatcherInterface $event_dispatcher) { + public function __construct($name, StorageInterface $storage, TypedConfigManagerInterface $typed_config, EventDispatcherInterface $event_dispatcher) { $this->name = $name; - $this->langcode = $langcode; $this->storage = $storage; $this->typedConfigManager = $typed_config; $this->eventDispatcher = $event_dispatcher; @@ -68,7 +58,7 @@ public function save() { } $this->storage->write($this->name, $this->data); $this->isNew = FALSE; - $this->eventDispatcher->dispatch(ConfigEvents::SAVE_OVERRIDE, new ConfigOverrideCrudEvent($this)); + $this->eventDispatcher->dispatch(LanguageConfigOverrideEvents::SAVE_OVERRIDE, new LanguageConfigOverrideCrudEvent($this)); $this->originalData = $this->data; return $this; } @@ -80,7 +70,7 @@ public function delete() { $this->data = array(); $this->storage->delete($this->name); $this->isNew = TRUE; - $this->eventDispatcher->dispatch(ConfigEvents::DELETE_OVERRIDE, new ConfigOverrideCrudEvent($this)); + $this->eventDispatcher->dispatch(LanguageConfigOverrideEvents::DELETE_OVERRIDE, new LanguageConfigOverrideCrudEvent($this)); $this->originalData = $this->data; return $this; } @@ -92,7 +82,7 @@ public function delete() { * The language code. */ public function getLangcode() { - return $this->langcode; + return $this->getLangcodeFromCollectionName($this->getStorage()->getCollectionName()); } } diff --git a/core/modules/language/src/Config/LanguageConfigOverrideCrudEvent.php b/core/modules/language/src/Config/LanguageConfigOverrideCrudEvent.php new file mode 100644 index 0000000..3b91d07 --- /dev/null +++ b/core/modules/language/src/Config/LanguageConfigOverrideCrudEvent.php @@ -0,0 +1,46 @@ +override = $override; + } + + /** + * Gets configuration object. + * + * @return \Drupal\language\Config\LanguageConfigOverride + * The configuration object that caused the event to fire. + */ + public function getLanguageConfigOverride() { + return $this->override; + } + +} diff --git a/core/modules/language/src/Config/LanguageConfigOverrideEvents.php b/core/modules/language/src/Config/LanguageConfigOverrideEvents.php new file mode 100644 index 0000000..7770f59 --- /dev/null +++ b/core/modules/language/src/Config/LanguageConfigOverrideEvents.php @@ -0,0 +1,33 @@ +isUpdating = TRUE; $this->languageManager->getLanguageConfigOverride($langcode, $name)->setData($data)->save(); + $this->isUpdating = FALSE; } /** @@ -168,7 +177,9 @@ public function saveTranslationData($name, $langcode, array $data) { * Language code. */ public function deleteTranslationData($name, $langcode) { + $this->isUpdating = TRUE; $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete(); + $this->isUpdating = FALSE; } /** @@ -206,6 +217,7 @@ public function getComponentNames(array $components) { * Array of language codes. */ public function deleteComponentTranslations(array $components, array $langcodes) { + $this->isUpdating = TRUE; $names = $this->getComponentNames($components); if ($names && $langcodes) { foreach ($names as $name) { @@ -214,6 +226,7 @@ public function deleteComponentTranslations(array $components, array $langcodes) } } } + $this->isUpdating = FALSE; } /** @@ -241,10 +254,12 @@ public function getStringNames(array $lids) { * Language code to delete. */ public function deleteLanguageTranslations($langcode) { + $this->isUpdating = TRUE; $storage = $this->languageManager->getLanguageConfigOverrideStorage($langcode); foreach ($storage->listAll() as $name) { $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete(); } + $this->isUpdating = FALSE; } /** @@ -330,4 +345,14 @@ public function hasTranslation($name, LanguageInterface $language) { return !$translation->isNew(); } + /** + * Indicates whether configuration translations are currently being updated. + * + * @return bool + * Whether or not configuration translations are currently being updated. + */ + public function isUpdatingConfigTranslations() { + return $this->isUpdating; + } + } diff --git a/core/modules/locale/src/LocaleConfigSubscriber.php b/core/modules/locale/src/LocaleConfigSubscriber.php index ad372aa..568ae86 100644 --- a/core/modules/locale/src/LocaleConfigSubscriber.php +++ b/core/modules/locale/src/LocaleConfigSubscriber.php @@ -7,11 +7,11 @@ namespace Drupal\locale; use Drupal\Core\Config\Config; -use Drupal\Core\Config\ConfigEvents; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Config\ConfigOverrideCrudEvent; use Drupal\Core\TypedData\TraversableTypedDataInterface; use Drupal\language\Config\LanguageConfigOverride; +use Drupal\language\Config\LanguageConfigOverrideCrudEvent; +use Drupal\language\Config\LanguageConfigOverrideEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -68,98 +68,201 @@ public function __construct(StringStorageInterface $string_storage, ConfigFactor * {@inheritdoc} */ public static function getSubscribedEvents() { - // Instead of deleting the actual translation strings we save empty strings - // when the configuration override gets deleted, so we can re-use the same - // function for both events. - $events[ConfigEvents::SAVE_OVERRIDE] = 'onOverrideUpdate'; - $events[ConfigEvents::DELETE_OVERRIDE] = 'onOverrideUpdate'; + $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onSave'; + $events[LanguageConfigOverrideEvents::DELETE_OVERRIDE] = 'onDelete'; return $events; } /** + * Updates the translation strings when shipped configuration is saved. + * + * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event + */ + public function onSave(LanguageConfigOverrideCrudEvent $event) { + // Do not mark strings as customized when community translations are being + // imported. + if ($this->localeConfigManager->isUpdatingConfigTranslations()) { + $callable = [$this, 'saveTranslation']; + } + else { + $callable = [$this, 'saveCustomizedTranslation']; + } + + $this->onUpdate($event, $callable); + } + + /** + * Updates the translation strings when shipped configuration is deleted. + * + * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event + */ + public function onDelete(LanguageConfigOverrideCrudEvent $event) { + if ($this->localeConfigManager->isUpdatingConfigTranslations()) { + $callable = [$this, 'deleteTranslation']; + } + else { + // Do not delete the string, but save a customized translation with the + // source value so that the deletion will not be reverted by importing + // community translations. + // @see \Drupal\locale\LocaleConfigSubscriber::saveCustomizedTranslation() + $callable = [$this, 'saveCustomizedTranslation']; + } + + $this->onUpdate($event, $callable); + } + + /** * Updates the translation strings of shipped configuration. * - * @param \Drupal\Core\Config\ConfigOverrideCrudEvent $event + * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event */ - public function onOverrideUpdate(ConfigOverrideCrudEvent $event) { - $translation_config = $event->getConfig(); + protected function onUpdate(LanguageConfigOverrideCrudEvent $event, $callable) { + $translation_config = $event->getLanguageConfigOverride(); $name = $translation_config->getName(); - if ( - // Only react to language overrides. - $translation_config instanceof LanguageConfigOverride && - // Only do anything if the configuration was shipped. - $this->stringStorage->getLocations(array( - 'type' => 'configuration', - 'name' => $name, - )) - ) { + // Only do anything if the configuration was shipped. + if ($this->stringStorage->getLocations(['type' => 'configuration', 'name' => $name])) { + $override_state = $this->configFactory->getOverrideState(); + $this->configFactory->setOverrideState(FALSE); + $source_config = $this->configFactory->get($name); $schema = $this->localeConfigManager->get($name)->getTypedConfig(); - $this->saveStrings($source_config, $translation_config, $schema); + + $this->traverseSchema($schema, $source_config, $translation_config, $callable); + + $this->configFactory->setOverrideState($override_state); } } /** - * Updates strings for a certain config element. + * Traverses configuration schema and applies a callback to each leaf element. + * + * It skips leaf elements that are not translatable. * - * @param \Drupal\Core\Config\Config $source_config - * The source configuration. - * @param \Drupal\language\Config\LanguageConfigOverride $translation_config - * The language configuration override. * @param \Drupal\Core\TypedData\TraversableTypedDataInterface $schema * The respective configuration schema. + * @param callable $callable + * The callable to apply to each leaf element. The callable will be called + * with the leaf element and the element key as arguments. * @param string|null $base_key - * (optional) The base key that the schema and the configuration values - * belong to. This should be NULL for the top-level configuration object and - * be populated consecutively when recursing into the configuration - * structure. + * (optional) The base key that the schema belongs to. This should be NULL + * for the top-level schema and be populated consecutively when recursing + * into the schema structure. */ - protected function saveStrings(Config $source_config, LanguageConfigOverride $translation_config, TraversableTypedDataInterface $schema, $base_key = NULL) { + protected function traverseSchema(TraversableTypedDataInterface $schema, Config $source_config, LanguageConfigOverride $translation_config, $callable, $base_key = NULL) { foreach ($schema as $key => $element) { - $element_key = implode('.', array_filter(array($base_key, $key))); + $element_key = implode('.', array_filter([$base_key, $key])); // We only care for strings here, so traverse the schema further in the - // case of array elements. + // case of traversable elements. if ($element instanceof TraversableTypedDataInterface) { - $this->saveStrings($source_config, $translation_config, $element, $element_key); + $this->traverseSchema($element, $source_config, $translation_config, $callable, $element_key); + } + // Skip elements which are not translatable. + elseif (!empty($element->getDataDefinition()['translatable'])) { + $callable( + $source_config->get($element_key), + $translation_config->getLangcode(), + $translation_config->get($element_key) + ); + } + } + } + + /** + * Saves a translation string. + * + * @param string $source_value + * The source string value. + * @param string $langcode + * The language code of the translation. + * @param string|null $translation_value + * (optional) The translation string value. If omitted, no translation will + * be saved. + */ + protected function saveTranslation($source_value, $langcode, $translation_value = NULL) { + if ($translation_value && ($translation = $this->getTranslation($source_value, $langcode, TRUE))) { + if ($translation->isNew() || $translation->getString() != $translation_value) { + $translation + ->setString($translation_value) + ->save(); + } + } + } + + /** + * Saves a translation string and marks it as customized. + * + * @param string $source_value + * The source string value. + * @param string $langcode + * The language code of the translation. + * @param string|null $translation_value + * (optional) The translation string value. If omitted, a customized string + * with the source value will be saved. + * + * @see \Drupal\locale\LocaleConfigSubscriber::onDelete() + */ + protected function saveCustomizedTranslation($source_value, $langcode, $translation_value = NULL) { + if ($translation = $this->getTranslation($source_value, $langcode, TRUE)) { + if (!isset($translation_value)) { + $translation_value = $source_value; + } + if ($translation->isNew() || $translation->getString() != $translation_value) { + $translation + ->setString($translation_value) + ->setCustomized(TRUE) + ->save(); + } + } + } + + /** + * Deletes a translation string, if it exists. + * + * @param string $source_value + * The source string value. + * @param string $langcode + * The language code of the translation. + * + * @see \Drupal\locale\LocaleConfigSubscriber::onDelete() + */ + protected function deleteTranslation($source_value, $langcode) { + if ($translation = $this->getTranslation($source_value, $langcode, FALSE)) { + $translation->delete(); + } + } + + /** + * Gets a translation string. + * + * @param string $source_value + * The source string value. + * @param string $langcode + * The language code of the translation. + * @param bool $create_fallback + * (optional) By default if a source string could be found and no + * translation in the given language exists yet, a translation object is + * created. This can be circumvented by passing FALSE. + * + * @return \Drupal\locale\TranslationString|null + * The translation string if one was found or created. + */ + protected function getTranslation($source_value, $langcode, $create_fallback = TRUE) { + // There is no point in creating a translation without a source. + if ($source_string = $this->stringStorage->findString(['source' => $source_value])) { + // Get the translation for this original source string from locale. + $conditions = [ + 'lid' => $source_string->lid, + 'language' => $langcode, + ]; + $translations = $this->stringStorage->getTranslations($conditions + ['translated' => TRUE]); + if ($translations) { + return reset($translations); } - else { - $definition = $element->getDataDefinition(); - $source_value = $source_config->get($element_key); - - // Ignore this value if it is not translatable or if no source string - // can be found. - if ( - !empty($definition['translatable']) && - $source_string = $this->stringStorage->findString(array('source' => $source_value)) - ) { - // Get the translation for this original source string from locale. - $conditions = array( - 'lid' => $source_string->lid, - 'language' => $translation_config->getLangcode(), - ); - $translations = $this->stringStorage->getTranslations($conditions + array('translated' => TRUE)); - // If we got a translation, take that, otherwise create a new one. - $translation = reset($translations) ?: $this->stringStorage->createTranslation($conditions); - - $value = $translation_config->get($element_key); - // If there is no value, save the source value as the translation. - // This has the same effect as deleting the string wholesale (which - // would be more correct) but ensures that the translation does not - // get re-imported when updating translations. - if (!isset($value)) { - $value = $source_value; - } - // If we have a new translation or one that is different from what is - // stored, update the translation and mark it as customized. - if ($translation->isNew() || $translation->getString() != $value) { - $translation->setString($value) - ->setCustomized() - ->save(); - } - } + elseif ($create_fallback) { + return $translation = $this->stringStorage->createTranslation($conditions); } } } diff --git a/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php b/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php new file mode 100644 index 0000000..ea012b3 --- /dev/null +++ b/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php @@ -0,0 +1,397 @@ +languageManager = $this->container->get('language_manager'); + $this->configFactory = $this->container->get('config.factory'); + $this->stringStorage = $this->container->get('locale.storage'); + $this->localeConfigManager = $this->container->get('locale.config.typed'); + + $this->installSchema('locale', ['locales_source', 'locales_target', 'locales_location']); + + $this->installConfig(['locale_test']); + ConfigurableLanguage::createFromLangcode($this->langcode)->save(); + } + + /** + * Tests creating translations of shipped configuration. + */ + public function testCreateTranslation() { + $config_name = 'locale_test.no_translation'; + + $this->setUpNoTranslation($config_name, 'test', 'Test'); + $this->saveLanguageOverride($config_name, 'test', 'Test (German)'); + $this->assertTranslation($config_name, 'Test (German)'); + } + + /** + * Tests importing community translations of shipped configuration. + */ + public function testLocaleCreateTranslation() { + $config_name = 'locale_test.no_translation'; + + $this->setUpNoTranslation($config_name, 'test', 'Test'); + $this->saveLocaleTranslationData($config_name, 'test', 'Test (German)'); + $this->assertTranslation($config_name, 'Test (German)', FALSE); + } + + /** + * Tests updating translations of shipped configuration. + */ + public function testUpdateTranslation() { + $config_name = 'locale_test.translation'; + + $this->setUpTranslation($config_name, 'test', 'English test', 'German test'); + $this->saveLanguageOverride($config_name, 'test', 'Updated German test'); + $this->assertTranslation($config_name, 'Updated German test'); + } + + /** + * Tests updating translations of shipped configuration. + */ + public function testLocaleUpdateTranslation() { + $config_name = 'locale_test.translation'; + + $this->setUpTranslation($config_name, 'test', 'English test', 'German test'); + $this->saveLocaleTranslationData($config_name, 'test', 'Updated German test'); + $this->assertTranslation($config_name, 'Updated German test', FALSE); + } + + /** + * Tests deleting translations of shipped configuration. + */ + public function testDeleteTranslation() { + $config_name = 'locale_test.translation'; + + $this->setUpTranslation($config_name, 'test', 'English test', 'German test'); + $this->deleteLanguageOverride($config_name, 'test', 'English test'); + // Instead of deleting the translation, we need to keep a translation with + // the source value and mark it as customized to prevent the deletion being + // reverted by importing community translations. + $this->assertTranslation($config_name, 'English test'); + } + + /** + * Tests deleting translations of shipped configuration. + */ + public function testLocaleDeleteTranslation() { + $config_name = 'locale_test.translation'; + + $this->setUpTranslation($config_name, 'test', 'English test', 'German test'); + $this->deleteLocaleTranslationData($config_name, 'test', 'English test'); + $this->assertNoTranslation($config_name, 'English test', FALSE); + } + + /** + * Sets up a configuration string without a translation. + * + * The actual configuration is already available by installing locale_test + * module, as it is done in LocaleConfigSubscriberTest::setUp(). This sets up + * the necessary source string and verifies that everything is as expected to + * avoid false positives. + * + * @param string $config_name + * The configuration name. + * @param string $key + * The configuration key. + * @param string $source + * The source string. + */ + protected function setUpNoTranslation($config_name, $key, $source) { + // Add a source string with the configuration name as a location. This gets + // called from locale_config_update_multiple() normally. + $this->localeConfigManager->translateString($config_name, $this->langcode, $source, ''); + $this->languageManager + ->setConfigOverrideLanguage(ConfigurableLanguage::load($this->langcode)); + + $this->assertConfigValue($config_name, $key, $source); + $this->assertNoTranslation($config_name); + } + + + /** + * Sets up a configuration string with a translation. + * + * The actual configuration is already available by installing locale_test + * module, as it is done in LocaleConfigSubscriberTest::setUp(). This sets up + * the necessary source and translation strings and verifies that everything + * is as expected to avoid false positives. + * + * @param string $config_name + * The configuration name. + * @param string $key + * The configuration key. + * @param string $source + * The source string. + * @param string $translation + * The translation string. + */ + protected function setUpTranslation($config_name, $key, $source, $translation) { + // Create source and translation strings for the configuration value and add + // the configuration name as a location. This would be performed by + // locale_translate_batch_import() and locale_config_update_multiple() + // normally. + $source_object = $this->stringStorage->createString([ + 'source' => $source, + 'context' => '', + ])->save(); + $this->stringStorage->createTranslation([ + 'lid' => $source_object->getId(), + 'language' => $this->langcode, + 'translation' => $translation, + ])->save(); + $this->localeConfigManager->translateString($config_name, $this->langcode, $source, ''); + $this->languageManager + ->setConfigOverrideLanguage(ConfigurableLanguage::load($this->langcode)); + + $this->assertConfigValue($config_name, $key, $translation); + $this->assertTranslation($config_name, $translation, FALSE); + } + + /** + * Saves a language override. + * + * This will invoke LocaleConfigSubscriber through the event dispatcher. To + * make sure the configuration was persisted correctly, the configuration + * value is checked. Because LocaleConfigSubscriber temporarily disables the + * override state of the configuration factory we check that the correct value + * is restored afterwards. + * + * @param string $config_name + * The configuration name. + * @param string $key + * The configuration key. + * @param string $value + * The configuration value to save. + */ + protected function saveLanguageOverride($config_name, $key, $value) { + $translation_override = $this->languageManager + ->getLanguageConfigOverride($this->langcode, $config_name); + $translation_override + ->set($key, $value) + ->save(); + $this->configFactory->reset($config_name); + + $this->assertConfigValue($config_name, $key, $value); + } + + /** + * Saves translation data from locale module. + * + * This will invoke LocaleConfigSubscriber through the event dispatcher. To + * make sure the configuration was persisted correctly, the configuration + * value is checked. Because LocaleConfigSubscriber temporarily disables the + * override state of the configuration factory we check that the correct value + * is restored afterwards. + * + * @param string $config_name + * The configuration name. + * @param string $key + * The configuration key. + * @param string $value + * The configuration value to save. + */ + protected function saveLocaleTranslationData($config_name, $key, $value) { + $this->localeConfigManager + ->saveTranslationData($config_name, $this->langcode, [$key => $value]); + $this->configFactory->reset($config_name); + + $this->assertConfigValue($config_name, $key, $value); + } + + /** + * Deletes a language override. + * + * This will invoke LocaleConfigSubscriber through the event dispatcher. To + * make sure the configuration was persisted correctly, the configuration + * value is checked. Because LocaleConfigSubscriber temporarily disables the + * override state of the configuration factory we check that the correct value + * is restored afterwards. + * + * @param string $config_name + * The configuration name. + * @param string $key + * The configuration key. + * @param string $source_value + * The source configuration value to verify the correct value is returned + * from the configuration factory after the deletion. + */ + protected function deleteLanguageOverride($config_name, $key, $source_value) { + $translation_override = $this->languageManager + ->getLanguageConfigOverride($this->langcode, $config_name); + $translation_override + ->clear($key) + ->save(); + $this->configFactory->reset($config_name); + + $this->assertConfigValue($config_name, $key, $source_value); + } + + /** + * Deletes translation data from locale module. + * + * This will invoke LocaleConfigSubscriber through the event dispatcher. To + * make sure the configuration was persisted correctly, the configuration + * value is checked. Because LocaleConfigSubscriber temporarily disables the + * override state of the configuration factory we check that the correct value + * is restored afterwards. + * + * @param string $config_name + * The configuration name. + * @param string $key + * The configuration key. + * @param string $source_value + * The source configuration value to verify the correct value is returned + * from the configuration factory after the deletion. + */ + protected function deleteLocaleTranslationData($config_name, $key, $source_value) { + $this->localeConfigManager->deleteTranslationData($config_name, $this->langcode); + $this->configFactory->reset($config_name); + + $this->assertConfigValue($config_name, $key, $source_value); + } + + /** + * Ensures configuration was saved correctly. + * + * @param $config_name + * The configuration name. + * @param $key + * The configuration key. + * @param $value + * The configuration value. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertConfigValue($config_name, $key, $value) { + // Make sure the configuration was translated correctly. + $translation_config = $this->configFactory->get($config_name); + $passed = $this->assertIdentical($value, $translation_config->get($key)); + + // Make sure the override state of the configuration factory was not + // modified. + return $passed && $this->assertIdentical(TRUE, $this->configFactory->getOverrideState()); + } + + /** + * Ensures no translation exists. + * + * @param string $config_name + * The configuration name. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertNoTranslation($config_name) { + $strings = $this->stringStorage->getTranslations([ + 'type' => 'configuration', + 'name' => $config_name, + 'language' => $this->langcode, + 'translated' => TRUE, + ]); + return $this->assertIdentical([], $strings); + } + + /** + * Ensures a translation exists and is marked as customized. + * + * @param string $config_name + * The configuration name. + * @param string $translation + * The translation. + * @param bool $customized + * Whether or not the string should be asserted to be customized or not + * customized. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertTranslation($config_name, $translation, $customized = TRUE) { + // Make sure a string exists. + $strings = $this->stringStorage->getTranslations([ + 'type' => 'configuration', + 'name' => $config_name, + 'language' => $this->langcode, + 'translated' => TRUE, + ]); + $pass = $this->assertIdentical(1, count($strings)); + $string = reset($strings); + if ($this->assertTrue($string instanceof StringInterface)) { + /** @var \Drupal\locale\StringInterface $string */ + $pass = $pass && $this->assertIdentical($translation, $string->getString()); + $pass = $pass && $this->assertTrue($string->isTranslation()); + if ($this->assertTrue($string instanceof TranslationString)) { + /** @var \Drupal\locale\TranslationString $string */ + // Make sure the string is marked as customized so that it does not get + // overridden when the string translations are updated. + return $pass && $this->assertEqual($customized, $string->customized); + } + } + return FALSE; + } + +} diff --git a/core/modules/locale/tests/modules/locale_test/config/schema/locale_test.schema.yml b/core/modules/locale/tests/modules/locale_test/config/schema/locale_test.schema.yml index dd722be..5e7e056 100644 --- a/core/modules/locale/tests/modules/locale_test/config/schema/locale_test.schema.yml +++ b/core/modules/locale/tests/modules/locale_test/config/schema/locale_test.schema.yml @@ -7,6 +7,8 @@ locale_test.no_translation: test: type: string label: 'Test' + # See \Drupal\locale\Tests\LocaleConfigSubscriberTest + translatable: true locale_test.translation: type: mapping @@ -15,3 +17,5 @@ locale_test.translation: test: type: string label: 'Test' + # See \Drupal\locale\Tests\LocaleConfigSubscriberTest + translatable: true