diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php index 1285cb7..c69b705 100644 --- a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php +++ b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php @@ -60,6 +60,29 @@ public function __construct($string, array $arguments = array(), array $options } /** + * Get the string value stored in this translation wrapper. + * + * @return string + * The string stored in this wrapper. + */ + public function getString() { + return $this->string; + } + + /** + * Get a specific option from this translation wrapper. + * + * @param $name + * Option name. + * + * @return mixed + * The value of this option or empty string of option is not set. + */ + public function getOption($name) { + return isset($this->options[$name]) ? $this->options[$name] : ''; + } + + /** * Implements the magic __toString() method. */ public function __toString() { diff --git a/core/modules/config/src/Tests/ConfigSchemaTest.php b/core/modules/config/src/Tests/ConfigSchemaTest.php index 2bbfee5..4a65e62 100644 --- a/core/modules/config/src/Tests/ConfigSchemaTest.php +++ b/core/modules/config/src/Tests/ConfigSchemaTest.php @@ -26,7 +26,7 @@ class ConfigSchemaTest extends KernelTestBase { * * @var array */ - public static $modules = array('system', 'language', 'locale', 'field', 'image', 'config_test', 'config_schema_test'); + public static $modules = array('system', 'language', 'field', 'image', 'config_test', 'config_schema_test'); /** * {@inheritdoc} diff --git a/core/modules/config_translation/src/ConfigEntityMapper.php b/core/modules/config_translation/src/ConfigEntityMapper.php index 08ce8a6..fe3da3c 100644 --- a/core/modules/config_translation/src/ConfigEntityMapper.php +++ b/core/modules/config_translation/src/ConfigEntityMapper.php @@ -98,7 +98,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_definition, $container->get('config.factory'), $container->get('config.typed'), - $container->get('locale.config.typed'), + $container->get('locale.config_manager'), $container->get('plugin.manager.config_translation.mapper'), $container->get('router.route_provider'), $container->get('string_translation'), diff --git a/core/modules/config_translation/src/ConfigNamesMapper.php b/core/modules/config_translation/src/ConfigNamesMapper.php index b681b33..ee1d0ac 100644 --- a/core/modules/config_translation/src/ConfigNamesMapper.php +++ b/core/modules/config_translation/src/ConfigNamesMapper.php @@ -150,7 +150,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_definition, $container->get('config.factory'), $container->get('config.typed'), - $container->get('locale.config.typed'), + $container->get('locale.config_manager'), $container->get('plugin.manager.config_translation.mapper'), $container->get('router.route_provider'), $container->get('string_translation'), @@ -465,7 +465,7 @@ public function hasTranslatable() { */ public function hasTranslation(LanguageInterface $language) { foreach ($this->getConfigNames() as $name) { - if ($this->localeConfigManager->hasTranslation($name, $language)) { + if ($this->localeConfigManager->hasTranslation($name, $language->getId())) { return TRUE; } } diff --git a/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php b/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php index 59c1469..3a602e1 100644 --- a/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php +++ b/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php @@ -593,7 +593,7 @@ public function testHasTranslation(array $mock_return_values, $expected) { $map = array(); foreach ($config_names as $i => $config_name) { - $map[] = array($config_name, $language, $mock_return_values[$i]); + $map[] = array($config_name, $language->getId(), $mock_return_values[$i]); } $this->localeConfigManager ->expects($this->any()) diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index 357aaf8..a4677ca 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -324,7 +324,7 @@ function locale_translate_batch_refresh(array &$context) { } elseif ($name = array_shift($context['sandbox']['refresh']['names'])) { // Refresh all languages for one object at a time. - $count = locale_config_update_multiple(array($name), $context['sandbox']['refresh']['languages']); + $count = Locale::config()->updateConfigTranslations(array($name), $context['sandbox']['refresh']['languages']); $context['results']['stats']['config'] += $count; // Inherit finished information from the "parent" string lookup step so // visual display of status will make sense. @@ -532,7 +532,7 @@ function locale_translate_delete_translation_files(array $projects = array(), ar * The batch definition. */ function locale_config_batch_update_components(array $options, array $langcodes = array(), array $components = array()) { - $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); + $langcodes = $langcodes ? $langcodes : array_keys(\Drupal::languageManager()->getLanguages()); if ($langcodes && $names = \Drupal\locale\Locale::config()->getComponentNames($components)) { return locale_config_batch_build($names, $langcodes, $options); } @@ -606,7 +606,7 @@ function locale_config_batch_refresh_name(array $names, array $langcodes, array if (!isset($context['result']['stats']['config'])) { $context['result']['stats']['config'] = 0; } - $context['result']['stats']['config'] += locale_config_update_multiple($names, $langcodes); + $context['result']['stats']['config'] += Locale::config()->updateConfigTranslations($names, $langcodes); foreach ($names as $name) { $context['result']['names'][] = $name; } @@ -639,41 +639,3 @@ function locale_config_batch_finished($success, array $results) { } } } - -/** - * Updates all configuration for names / languages. - * - * @param array $names - * Array of names of configuration objects to update. - * @param array $langcodes - * (optional) Array of language codes to update. Defaults to all languages. - * - * @return int - * Number of configuration objects retranslated. - */ -function locale_config_update_multiple(array $names, array $langcodes = array()) { - /** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */ - $language_manager = \Drupal::languageManager(); - $locale_config_manager = Locale::config(); - - $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); - $count = 0; - foreach ($names as $name) { - $wrapper = $locale_config_manager->get($name); - foreach ($langcodes as $langcode) { - $translation = $wrapper->getValue() ? $wrapper->getTranslation($langcode)->getValue() : NULL; - if ($translation) { - $locale_config_manager->saveTranslationData($name, $langcode, $translation); - $count++; - } - else { - // Do not bother deleting language overrides which do not exist in the - // first place. - if (!$language_manager->getLanguageConfigOverride($langcode, $name)->isNew()) { - $locale_config_manager->deleteTranslationData($name, $langcode); - } - } - } - } - return $count; -} diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 971b47d..66557f3 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -24,6 +24,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\language\ConfigurableLanguageInterface; use Drupal\Component\Utility\Crypt; +use Drupal\locale\Locale; /** * Regular expression pattern used to localize JavaScript strings. @@ -226,7 +227,7 @@ function locale_configurable_language_delete(ConfigurableLanguageInterface $lang locale_translate_delete_translation_files(array(), array($language->id())); // Remove translated configuration objects. - \Drupal\locale\Locale::config()->deleteLanguageTranslations($language->id()); + Locale::config()->deleteLanguageTranslations($language->id()); // Changing the language settings impacts the interface: _locale_invalidate_js($language->id()); @@ -246,7 +247,7 @@ function locale_configurable_language_delete(ConfigurableLanguageInterface $lang */ function locale_translatable_language_list() { $languages = \Drupal::languageManager()->getLanguages(); - if (!locale_translate_english()) { + if (!locale_is_translatable('en')) { unset($languages['en']); } return $languages; @@ -313,6 +314,7 @@ function locale_get_plural($count, $langcode = NULL) { */ function locale_modules_installed($modules) { $components['module'] = $modules; + locale_system_set_config_langcodes($components); locale_system_update($components); } @@ -329,6 +331,7 @@ function locale_module_preuninstall($module) { */ function locale_themes_installed($themes) { $components['theme'] = $themes; + locale_system_set_config_langcodes($components); locale_system_update($components); } @@ -356,6 +359,41 @@ function locale_cron() { } /** + * Update default configuration when new modules or themes are installed. + * + * @param array $components + * An array of arrays of component (theme and/or module) names to import + * translations for, indexed by type. + */ +function locale_system_set_config_langcodes(array $components) { + // Need to rewrite some default configuration language codes if the default + // site language is not English. + $default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId(); + if ($default_langcode != 'en') { + // If just installed the locale module, we need to update all prior + // shipped configuration to the foreign site language. Otherwise keep the + // components list received to just update the shipped configuration just + // imported. + if (isset($components['module']) && in_array('locale', $components['module'])) { + $components = array(); + } + $names = Locale::config()->getComponentNames($components); + + foreach ($names as $name) { + $config = \Drupal::configFactory()->getEditable($name); + // Should only update if still exists in active configuration. If locale + // module is enabled later, then some configuration may not exist anymore. + if (!$config->isNew()) { + $langcode = $config->get('langcode'); + if (empty($langcode) || $langcode == 'en') { + $config->set('langcode', $default_langcode)->save(); + } + } + } + } +} + +/** * Imports translations when new modules or themes are installed. * * This function will start a batch to import translations for the added @@ -411,8 +449,6 @@ function locale_system_remove($components) { if ($language_list = locale_translatable_language_list()) { module_load_include('compare.inc', 'locale'); \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc'); - // Delete configuration translations. - \Drupal\locale\Locale::config()->deleteComponentTranslations($components, array_keys($language_list)); // Only when projects are removed, the translation files and records will be // deleted. Not each disabled module will remove a project, e.g., sub @@ -606,7 +642,7 @@ function locale_form_language_admin_overview_form_alter(&$form, FormStateInterfa 'translated' => 0, 'ratio' => 0, ); - if (!$language->isLocked() && ($langcode != 'en' || locale_translate_english())) { + if (!$language->isLocked() && locale_is_translatable($langcode)) { $form['languages'][$langcode]['locale_statistics'] = array( '#markup' => \Drupal::l( t('@translated/@total (@ratio%)', array( @@ -689,13 +725,15 @@ function locale_form_language_admin_edit_form_alter_submit($form, FormStateInter } /** - * Checks whether locale translates to English. + * Checks whether $langcode is a language supported as a locale target. * + * @param string $langcode + * The language code. * @return bool - * Returns TRUE if content should be translated to English, FALSE otherwise. + * Whether $langcode can be translated to in locale. */ -function locale_translate_english() { - return \Drupal::config('locale.settings')->get('translate_english'); +function locale_is_translatable($langcode) { + return $langcode != 'en' || \Drupal::config('locale.settings')->get('translate_english'); } /** @@ -1020,19 +1058,14 @@ function _locale_refresh_translations($langcodes, $lids = array()) { /** * Refreshes configuration after string translations have been updated. * - * The information that will be refreshed includes: - * - JavaScript translations. - * - Locale cache. - * * @param array $langcodes * Language codes for updated translations. * @param array $lids * List of string identifiers that have been updated / created. */ function _locale_refresh_configuration(array $langcodes, array $lids) { - if ($lids && $langcodes && $names = \Drupal\locale\Locale::config()->getStringNames($lids)) { - \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc'); - locale_config_update_multiple($names, $langcodes); + if ($lids && $langcodes && $names = Locale::config()->getStringNames($lids)) { + Locale::config()->updateConfigTranslations($names, $langcodes); } } diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml index 27cef2c..da3cf77 100644 --- a/core/modules/locale/locale.services.yml +++ b/core/modules/locale/locale.services.yml @@ -1,5 +1,5 @@ services: - locale.config.typed: + locale.config_manager: class: Drupal\locale\LocaleConfigManager arguments: ['@config.storage', '@config.storage.installer', '@locale.storage', '@config.factory', '@config.typed', '@language_manager'] locale.storage: @@ -22,6 +22,6 @@ services: - { name: stream_wrapper, scheme: translations } locale.config_subscriber: class: Drupal\locale\LocaleConfigSubscriber - arguments: ['@locale.storage', '@config.factory', '@locale.config.typed'] + arguments: ['@config.factory', '@locale.config_manager'] tags: - { name: event_subscriber } diff --git a/core/modules/locale/src/Form/ExportForm.php b/core/modules/locale/src/Form/ExportForm.php index f9b6361..035b2e8 100644 --- a/core/modules/locale/src/Form/ExportForm.php +++ b/core/modules/locale/src/Form/ExportForm.php @@ -61,7 +61,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $languages = $this->languageManager->getLanguages(); $language_options = array(); foreach ($languages as $langcode => $language) { - if ($langcode != 'en' || locale_translate_english()) { + if (locale_is_translatable($langcode)) { $language_options[$langcode] = $language->getName(); } } diff --git a/core/modules/locale/src/Form/ImportForm.php b/core/modules/locale/src/Form/ImportForm.php index e3448b3..ca706ea 100644 --- a/core/modules/locale/src/Form/ImportForm.php +++ b/core/modules/locale/src/Form/ImportForm.php @@ -79,7 +79,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // are to translate Drupal to English as well. $existing_languages = array(); foreach ($languages as $langcode => $language) { - if ($langcode != 'en' || locale_translate_english()) { + if (locale_is_translatable($langcode)) { $existing_languages[$langcode] = $language->getName(); } } diff --git a/core/modules/locale/src/Form/TranslateFormBase.php b/core/modules/locale/src/Form/TranslateFormBase.php index b894f17..59af72b 100644 --- a/core/modules/locale/src/Form/TranslateFormBase.php +++ b/core/modules/locale/src/Form/TranslateFormBase.php @@ -164,7 +164,7 @@ protected function translateFilters() { $languages = $this->languageManager->getLanguages(); $language_options = array(); foreach ($languages as $langcode => $language) { - if ($langcode != 'en' || locale_translate_english()) { + if (locale_is_translatable($langcode)) { $language_options[$langcode] = $language->getName(); } } diff --git a/core/modules/locale/src/Locale.php b/core/modules/locale/src/Locale.php index f4afaed..9235a9c 100644 --- a/core/modules/locale/src/Locale.php +++ b/core/modules/locale/src/Locale.php @@ -23,6 +23,6 @@ class Locale { * @return \Drupal\locale\LocaleConfigManager */ public static function config() { - return \Drupal::service('locale.config.typed'); + return \Drupal::service('locale.config_manager'); } } diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php index aee2d59..16bb970 100644 --- a/core/modules/locale/src/LocaleConfigManager.php +++ b/core/modules/locale/src/LocaleConfigManager.php @@ -7,33 +7,53 @@ namespace Drupal\locale; -use Drupal\Core\Config\TypedConfigManagerInterface; -use Drupal\Core\Config\StorageInterface; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Config\StorageInterface; +use Drupal\Core\Config\TypedConfigManagerInterface; +use Drupal\Core\StringTranslation\TranslationWrapper; +use Drupal\Core\TypedData\TraversableTypedDataInterface; +use Drupal\Core\TypedData\TypedDataInterface; use Drupal\language\ConfigurableLanguageManagerInterface; /** - * Manages localized configuration type plugins. + * Manages configuration supported in part by interface translation. + * + * This manager is responsible to update configuration overrides and active + * translations when interface translation data changes. This allows Drupal to + * translate user roles, views, blocks, etc. after Drupal has been installed + * using the locale module's storage. When translations change in locale, + * LocaleConfigManager::updateConfigTranslations() is invoked to update the + * corresponding storage of the translation (in active storage or overrides). + * + * In turn when translated configuration or configuration language overrides are + * changed, it is the responsibility of LocaleConfigSubscriber to update locale + * storage. + * + * By design locale module only deals with sources in English. + * + * @see \Drupal\locale\LocaleConfigSubscriber */ class LocaleConfigManager { /** - * A storage instance for reading configuration data. + * The storage instance for reading configuration data. * * @var \Drupal\Core\Config\StorageInterface */ protected $configStorage; /** - * A storage instance for reading default configuration data. + * The storage instance for reading default configuration data. * * @var \Drupal\Core\Config\StorageInterface */ protected $installStorage; /** - * A string storage for reading and writing translations. + * The string storage for reading and writing translations. + * + * @var \Drupal\locale\StringStorageInterface; */ protected $localeStorage; @@ -66,11 +86,13 @@ class LocaleConfigManager { protected $typedConfigManager; /** - * Whether or not configuration translations are currently being updated. + * Whether or not configuration translations are being updated from locale. + * + * @see self::isUpdatingFromLocale() * * @var bool */ - protected $isUpdating = FALSE; + protected $isUpdatingFromLocale = FALSE; /** * Creates a new typed configuration manager. @@ -99,61 +121,119 @@ public function __construct(StorageInterface $config_storage, StorageInterface $ } /** - * Gets locale wrapper with typed configuration data. + * Gets array of translation wrappers for translatable configuration. * * @param string $name * Configuration object name. * - * @return \Drupal\locale\LocaleTypedConfig - * Locale-wrapped configuration element. - */ - public function get($name) { - // Read default and current configuration data. - $default = $this->installStorageRead($name); - $updated = $this->configStorage->read($name); - // We get only the data that didn't change from default. - $data = $this->compareConfigData($default, $updated); - $definition = $this->typedConfigManager->getDefinition($name); - $data_definition = $this->typedConfigManager->buildDataDefinition($definition, $data); - // Unless the configuration has a explicit language code we assume English. - $langcode = isset($default['langcode']) ? $default['langcode'] : 'en'; - $wrapper = new LocaleTypedConfig($data_definition, $name, $langcode, $this, $this->typedConfigManager, $this->languageManager); - $wrapper->setValue($data); - return $wrapper; + * @return array + * Array of translatable elements of the default configuration in $name. + */ + public function getTranslatableDefaultConfig($name) { + if ($this->isSupported($name)) { + // Create typed configuration wrapper based on install storage data. + $data = $this->installStorageRead($name); + $type_definition = $this->typedConfigManager->getDefinition($name); + $data_definition = $this->typedConfigManager->buildDataDefinition($type_definition, $data); + $typed_config = $this->typedConfigManager->create($data_definition, $data); + if ($typed_config instanceof TraversableTypedDataInterface) { + return $this->getTranslatableData($typed_config); + } + } + return array(); } /** - * Compares default configuration with updated data. + * Gets translatable configuration data for a typed configuration element. * - * @param array $default - * Default configuration data. - * @param array|false $updated - * Current configuration data, or FALSE if no configuration data existed. + * @param \Drupal\Core\TypedData\TypedDataInterface $element + * Typed configuration element. * - * @return array - * The elements of default configuration that haven't changed. + * @return array|\Drupal\Core\StringTranslation\TranslationWrapper + * A nested array matching the exact structure under $element with only the + * elements that are translatable wrapped into a TranslationWrapper. If the + * provided $element is not traversable, the return value is a single + * TranslationWrapper. */ - protected function compareConfigData(array $default, $updated) { - // Speed up comparison, specially for install operations. - if ($default === $updated) { - return $default; + protected function getTranslatableData(TypedDataInterface $element) { + $translatable = array(); + if ($element instanceof TraversableTypedDataInterface) { + foreach ($element as $key => $property) { + $value = $this->getTranslatableData($property); + if (!empty($value)) { + $translatable[$key] = $value; + } + } } - $result = array(); - foreach ($default as $key => $value) { - if (isset($updated[$key])) { - if (is_array($value)) { - $result[$key] = $this->compareConfigData($value, $updated[$key]); + else { + $definition = $element->getDataDefinition(); + if (!empty($definition['translatable'])) { + $options = array(); + if (isset($definition['translation context'])) { + $options['context'] = $definition['translation context']; + } + return new TranslationWrapper($element->getValue(), array(), $options); + } + } + return $translatable; + } + + /** + * Process the translatable data array with a given language. + * + * If the given language is translatable, will return the translated copy + * which will only contain strings that had translations. If the given + * language is English and is not translatable, will return a simplified + * array of the English source strings only. + * + * @param string $name + * The configuration name. + * @param array $active + * The active configuration data. + * @param array $translatable + * The translatable array structure. A nested array matching the exact + * structure under of the default configuration for $name with only the + * elements that are translatable wrapped into a TranslationWrapper. + * @see self::getTranslatableData(). + * @param string $langcode + * The language code to process the array with + * + * @return array + * Processed translatable data array. Will only contain translations + * different from source strings or in case of untranslatable English, the + * source strings themselves. + */ + protected function processTranslatableData($name, array $active, array $translatable, $langcode) { + $translated = array(); + foreach ($translatable as $key => $item) { + if (!isset($active[$key])) { + continue; + } + if (is_array($item)) { + // Only add this key if there was a translated value underneath. + $value = $this->processTranslatableData($name, $active[$key], $item, $langcode); + if (!empty($value)) { + $translated[$key] = $value; + } + } + else { + /** @var \Drupal\Core\StringTranslation\TranslationWrapper $item */ + if (locale_is_translatable($langcode)) { + $value = $this->translateString($name, $langcode, $item->getString(), $item->getOption('context')); + } + else { + $value = $item->getString(); } - elseif ($value === $updated[$key]) { - $result[$key] = $value; + if (!empty($value)) { + $translated[$key] = $value; } } } - return $result; + return $translated; } /** - * Saves translated configuration data. + * Saves translated configuration override. * * @param string $name * Configuration object name. @@ -162,10 +242,24 @@ protected function compareConfigData(array $default, $updated) { * @param array $data * Configuration data to be saved, that will be only the translated values. */ - public function saveTranslationData($name, $langcode, array $data) { - $this->isUpdating = TRUE; + public function saveTranslationOverride($name, $langcode, array $data) { + $this->isUpdatingFromLocale = TRUE; $this->languageManager->getLanguageConfigOverride($langcode, $name)->setData($data)->save(); - $this->isUpdating = FALSE; + $this->isUpdatingFromLocale = FALSE; + } + + /** + * Saves translated configuration data. + * + * @param string $name + * Configuration object name. + * @param array $data + * Configuration data to be saved with translations merged in. + */ + public function saveTranslationActive($name, array $data) { + $this->isUpdatingFromLocale = TRUE; + $this->configFactory->getEditable($name)->setData($data)->save(); + $this->isUpdatingFromLocale = FALSE; } /** @@ -176,10 +270,10 @@ public function saveTranslationData($name, $langcode, array $data) { * @param string $langcode * Language code. */ - public function deleteTranslationData($name, $langcode) { - $this->isUpdating = TRUE; + public function deleteTranslationOverride($name, $langcode) { + $this->isUpdatingFromLocale = TRUE; $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete(); - $this->isUpdating = FALSE; + $this->isUpdatingFromLocale = FALSE; } /** @@ -192,7 +286,7 @@ public function deleteTranslationData($name, $langcode) { * @return array * Array of configuration object names. */ - public function getComponentNames(array $components) { + public function getComponentNames(array $components = array()) { $components = array_filter($components); if ($components) { $names = array(); @@ -209,27 +303,6 @@ public function getComponentNames(array $components) { } /** - * Deletes configuration translations for uninstalled components. - * - * @param array $components - * Array with string identifiers. - * @param array $langcodes - * 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) { - foreach ($langcodes as $langcode) { - $this->deleteTranslationData($name, $langcode); - } - } - } - $this->isUpdating = FALSE; - } - - /** * Gets configuration names associated with strings. * * @param array $lids @@ -254,12 +327,12 @@ public function getStringNames(array $lids) { * Language code to delete. */ public function deleteLanguageTranslations($langcode) { - $this->isUpdating = TRUE; + $this->isUpdatingFromLocale = TRUE; $storage = $this->languageManager->getLanguageConfigOverrideStorage($langcode); foreach ($storage->listAll() as $name) { $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete(); } - $this->isUpdating = FALSE; + $this->isUpdatingFromLocale = FALSE; } /** @@ -330,29 +403,234 @@ public function translateString($name, $langcode, $source, $context) { } /** + * Reset static cache of configuration string translations. + * + * @return $this + */ + public function reset() { + $this->translations = array(); + return $this; + } + + /** + * Get the translation object for the given source/context and language. + * + * @param string $name + * Name of the configuration location. + * @param string $langcode + * Language code to translate to. + * @param string $source + * The source string, should be English. + * @param string $context + * The string context. + * + * @return \Drupal\locale\TranslationString|FALSE + * The translation object if the string was not empty or FALSE otherwise. + */ + public function getStringTranslation($name, $langcode, $source, $context) { + if ($source) { + $this->translateString($name, $langcode, $source, $context); + if ($string = $this->translations[$name][$langcode][$context][$source]) { + if (!$string->isTranslation()) { + $conditions = array('lid' => $string->lid, 'language' => $langcode); + return $this->localeStorage->createTranslation($conditions); + } + else { + return $string; + } + } + } + return FALSE; + } + + /** * Checks whether a language has configuration translation. * * @param string $name * Configuration name. - * @param \Drupal\Core\Language\LanguageInterface $language - * A language object. + * @param string $langcode + * A language code. * * @return bool * A boolean indicating if a language has configuration translations. */ - public function hasTranslation($name, LanguageInterface $language) { - $translation = $this->languageManager->getLanguageConfigOverride($language->getId(), $name); + public function hasTranslation($name, $langcode) { + $translation = $this->languageManager->getLanguageConfigOverride($langcode, $name); return !$translation->isNew(); } /** - * Indicates whether configuration translations are currently being updated. + * Returns the original language code for this shipped configuration. + * + * @param $name + * The configuration name. + * + * @return null|string + * Language code of the default configuration for $name. If the default + * configuration data for $name did not contain a language code, it is + * assumed to be English. The return value is NULL if no such default + * configuration exists. + */ + public function getDefaultConfigLangcode($name) { + $shipped = $this->installStorageRead($name); + if (!empty($shipped)) { + return !empty($shipped['langcode']) ? $shipped['langcode'] : 'en'; + } + } + + /** + * Returns the current language code for this active configuration. + * + * @param $name + * The configuration name. + * + * @return null|string + * Language code of the current active configuration for $name. If the + * configuration data for $name did not contain a language code, it is + * assumed to be English. The return value is NULL if no such active + * configuration exists. + */ + public function getActiveConfigLangcode($name) { + $active = $this->configStorage->read($name); + if (!empty($active)) { + return !empty($active['langcode']) ? $active['langcode'] : 'en'; + } + } + + /** + * Whether the given configuration is supported for interface translation. + * + * @param $name + * The configuration name. + * + * @return bool + * TRUE if interface translation is supported. + */ + public function isSupported($name) { + return $this->getDefaultConfigLangcode($name) == 'en' && $this->configStorage->read($name); + } + + /** + * Indicates whether configuration translations are being updated from locale. * * @return bool * Whether or not configuration translations are currently being updated. + * If TRUE, LocaleConfigManager is in control of the process and the + * reference data is locale's storage. Changes made to active configuration + * and overrides in this case should not feed back to locale storage. + * On the other hand, when not updating from locale and configuration + * translations change, we need to feed back to the locale storage. + */ + public function isUpdatingTranslationsFromLocale() { + return $this->isUpdatingFromLocale; + } + + /** + * Updates all configuration translations for the names / languages provided. + * + * To be used when interface translation changes result in the need to update + * configuration translations to keep them in sync. + * + * @param array $names + * Array of names of configuration objects to update. + * @param array $langcodes + * (optional) Array of language codes to update. Defaults to all + * configurable languages. + * + * @return int + * Total number of configuration override and active configuration objects + * updated (saved or removed). */ - public function isUpdatingConfigTranslations() { - return $this->isUpdating; + public function updateConfigTranslations(array $names, array $langcodes = array()) { + $langcodes = $langcodes ? $langcodes : array_keys($this->languageManager->getLanguages()); + $count = 0; + foreach ($names as $name) { + $translatable = $this->getTranslatableDefaultConfig($name); + if (empty($translatable)) { + // If there is nothing translatable in this configuration or not + // supported, skip it. + continue; + } + + $active_langcode = $this->getActiveConfigLangcode($name); + $active = $this->configStorage->read($name); + + foreach ($langcodes as $langcode) { + $processed = $this->processTranslatableData($name, $active, $translatable, $langcode); + if ($langcode != $active_langcode) { + // If the language code is not the same as the active storage + // language, we should update a configuration override. + if (!empty($processed)) { + // Update translation data in configuration override. + $this->saveTranslationOverride($name, $langcode, $processed); + $count++; + } + else { + $override = $this->languageManager->getLanguageConfigOverride($langcode, $name); + if (!$override->isNew()) { + $data = $this->filterOverride($override->get(), $translatable); + if (empty($data)) { + // Delete language override if there is no data left at all. + // This means all prior translations in the override were locale + // managed. + $this->deleteTranslationOverride($name, $langcode); + $count++; + } + else { + // If there were translatable elements besides locale managed + // items, save with only those, and remove the ones managed + // by locale only. + $this->saveTranslationOverride($name, $langcode, $data); + $count++; + } + } + } + } + elseif (locale_is_translatable($langcode)) { + // If the language code is the active storage language, we should + // update. If it is English, we should only update if English is also + // translatable. + $active = NestedArray::mergeDeepArray(array($active, $processed), TRUE); + $this->saveTranslationActive($name, $active); + $count++; + } + } + } + return $count; + } + + /** + * Filters override data based on default translatable items. + * + * @param array $override_data + * Configuration override data. + * @param array $translatable + * Translatable data array. @see self::getTranslatableData() + * @return array + * Nested array of any items of $override_data which did not have keys in + * $translatable. May be empty if $override_data only had items which were + * also in $translatable. + */ + protected function filterOverride(array $override_data, array $translatable) { + $filtered_data = array(); + foreach ($override_data as $key => $value) { + if (isset($translatable[$key])) { + // If the translatable default configuration has this key, look further + // for subkeys or ignore this element for scalar values. + if (is_array($value)) { + $value = $this->filterOverride($value, $translatable[$key]); + if (!empty($value)) { + $filtered_data[$key] = $value; + } + } + } + else { + // If this key was not in the translatable default configuration, + // keep it. + $filtered_data[$key] = $value; + } + } + return $filtered_data; } /** diff --git a/core/modules/locale/src/LocaleConfigSubscriber.php b/core/modules/locale/src/LocaleConfigSubscriber.php index 6c42d50..ffce94f 100644 --- a/core/modules/locale/src/LocaleConfigSubscriber.php +++ b/core/modules/locale/src/LocaleConfigSubscriber.php @@ -6,35 +6,37 @@ namespace Drupal\locale; -use Drupal\Core\Config\Config; +use Drupal\Core\Config\ConfigCrudEvent; +use Drupal\Core\Config\ConfigEvents; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\TypedData\TraversableTypedDataInterface; -use Drupal\language\Config\LanguageConfigOverride; +use Drupal\Core\Config\StorableConfigBase; use Drupal\language\Config\LanguageConfigOverrideCrudEvent; use Drupal\language\Config\LanguageConfigOverrideEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** - * Updates corresponding string translation when language overrides change. + * Updates strings translation when configuration translations change. * - * This reacts to the updating or deleting of configuration language overrides. - * It checks whether there are string translations associated with the - * configuration that is being saved and, if so, updates those string - * translations with the new configuration values and marks them as customized. - * That way manual updates to configuration will not be inadvertently reverted - * when updated translations from https://localize.drupal.org are being - * imported. + * 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 string storage. - * - * @var \Drupal\locale\StringStorageInterface; - */ - protected $stringStorage; - - /** * The configuration factory. * * @var \Drupal\Core\Config\ConfigFactoryInterface @@ -51,15 +53,12 @@ class LocaleConfigSubscriber implements EventSubscriberInterface { /** * Constructs a LocaleConfigSubscriber. * - * @param \Drupal\locale\StringStorageInterface $string_storage - * The string storage. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration factory. * @param \Drupal\locale\LocaleConfigManager $locale_config_manager * The typed configuration manager. */ - public function __construct(StringStorageInterface $string_storage, ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager) { - $this->stringStorage = $string_storage; + public function __construct(ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager) { $this->configFactory = $config_factory; $this->localeConfigManager = $locale_config_manager; } @@ -68,202 +67,165 @@ public function __construct(StringStorageInterface $string_storage, ConfigFactor * {@inheritdoc} */ public static function getSubscribedEvents() { - $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onSave'; - $events[LanguageConfigOverrideEvents::DELETE_OVERRIDE] = 'onDelete'; + $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onOverrideChange'; + $events[LanguageConfigOverrideEvents::DELETE_OVERRIDE] = 'onOverrideChange'; + $events[ConfigEvents::SAVE] = 'onConfigSave'; return $events; } - /** - * Updates the translation strings when shipped configuration is saved. + * Updates the locale strings when a translated active configuration is saved. * - * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event - * The language configuration event. + * @param \Drupal\Core\Config\ConfigCrudEvent $event + * The configuration 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']; + 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 (!$this->localeConfigManager->isUpdatingTranslationsFromLocale()) { + $config = $event->getConfig(); + $langcode = $config->get('langcode') ?: 'en'; + $this->updateLocaleStorage($config, $langcode); } - - $this->updateTranslationStrings($event, $callable); } /** - * Updates the translation strings when shipped configuration is deleted. + * Updates the locale strings when a configuration override is saved/deleted. * * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event * The language configuration 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']; + 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 (!$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); } - - $this->updateTranslationStrings($event, $callable); } /** - * Updates the translation strings of shipped configuration. + * Update locale storage based on configuration translations. * - * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event - * The language configuration event. - * @param $callable - * A callable to apply to each translatable string of the configuration. + * @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 updateTranslationStrings(LanguageConfigOverrideCrudEvent $event, $callable) { - $translation_config = $event->getLanguageConfigOverride(); - $name = $translation_config->getName(); - - // Only do anything if the configuration was shipped. - if ($this->stringStorage->getLocations(['type' => 'configuration', 'name' => $name])) { - $source_config = $this->configFactory->getEditable($name); - $schema = $this->localeConfigManager->get($name)->getTypedConfig(); - $this->traverseSchema($schema, $source_config, $translation_config, $callable); + protected function updateLocaleStorage(StorableConfigBase $config, $langcode, array $reference_config = array()) { + $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); } } /** - * Traverses configuration schema and applies a callback to each leaf element. - * - * It skips leaf elements that are not translatable. - * - * @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 belongs to. This should be NULL - * for the top-level schema and be populated consecutively when recursing - * into the schema structure. + * 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 $translatable + * The translatable array structure, see this::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 traverseSchema(TraversableTypedDataInterface $schema, Config $source_config, LanguageConfigOverride $translation_config, $callable, $base_key = NULL) { - foreach ($schema as $key => $element) { - $element_key = implode('.', array_filter([$base_key, $key])); - - // We only care for strings here, so traverse the schema further in the - // case of traversable elements. - if ($element instanceof TraversableTypedDataInterface) { - $this->traverseSchema($element, $source_config, $translation_config, $callable, $element_key); + protected function processTranslatableData($name, array $config, array $translatable, $langcode, array $reference_config = array()) { + 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] : array(); + $this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config); } - // 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) - ); + else { + /** @var \Drupal\Core\StringTranslation\TranslationWrapper $item */ + $this->saveCustomizedTranslation($name, $item->getString(), $item->getOption('context'), $config[$key], $langcode); } } } /** - * 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. + * 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\TranslationWrapper $translatable + * Either a possibly nested array with TranslationWrapper objects at the + * leaf items or a TranslationWrapper 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 $langcode + * The language code of the translation being processed. */ - 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(); + 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)) { + /** @var \Drupal\Core\StringTranslation\TranslationWrapper $translatable */ + $this->saveCustomizedTranslation($name, $translatable->getString(), $translatable->getOption('context'), $reference_config, $langcode); + } } /** * Saves a translation string and marks it as customized. * - * @param string $source_value + * @param string $name + * The configuration name. + * @param string $source * The source string value. + * @param string $context + * The source string context. + * @param string $translation + * The translation string. * @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) + protected function saveCustomizedTranslation($name, $source, $context, $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 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. + if (($locale_translation->isNew() && $source != $translation) || + (!$locale_translation->isNew() && $translation != $locale_translation->getString())) { + $locale_translation + ->setString($translation) ->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); - } - elseif ($create_fallback) { - return $this->stringStorage->createTranslation($conditions); - } - } - } - } diff --git a/core/modules/locale/src/LocaleTypedConfig.php b/core/modules/locale/src/LocaleTypedConfig.php deleted file mode 100644 index ca31a38..0000000 --- a/core/modules/locale/src/LocaleTypedConfig.php +++ /dev/null @@ -1,209 +0,0 @@ -langcode = $langcode; - $this->localeConfig = $locale_config; - $this->typedConfigManager = $typed_config; - $this->languageManager = $language_manager; - } - - /** - * Gets wrapped typed config object. - */ - public function getTypedConfig() { - return $this->typedConfigManager->create($this->definition, $this->value); - } - - /** - * {@inheritdoc} - */ - public function getTranslation($langcode) { - $options = array( - 'source' => $this->langcode, - 'target' => $langcode, - ); - $data = $this->getElementTranslation($this->getTypedConfig(), $options); - return $this->typedConfigManager->create($this->definition, $data); - } - - /** - * {@inheritdoc} - */ - public function language() { - return $this->languageManager->getLanguage($this->langcode); - } - - /** - * Checks whether we can translate these languages. - * - * @param string $from_langcode - * Source language code. - * @param string $to_langcode - * Destination language code. - * - * @return bool - * TRUE if this translator supports translations for these languages. - */ - protected function canTranslate($from_langcode, $to_langcode) { - if ($from_langcode == 'en') { - return TRUE; - } - return FALSE; - } - - /** - * Gets translated configuration data for a typed configuration element. - * - * @param \Drupal\Core\TypedData\TypedDataInterface $element - * Typed configuration element. - * @param array $options - * Array with translation options that must contain the keys defined in - * \Drupal\locale\LocaleTypedConfig::translateElement(). - * - * @return array - * Configuration data translated to the requested language if available, - * an empty array otherwise. - */ - protected function getElementTranslation(TypedDataInterface $element, array $options) { - $translation = array(); - if ($element instanceof TraversableTypedDataInterface) { - $translation = $this->getArrayTranslation($element, $options); - } - elseif ($this->translateElement($element, $options)) { - $translation = $element->getValue(); - } - return $translation; - } - - /** - * Gets translated configuration data for a traversable element. - * - * @param \Drupal\Core\TypedData\TraversableTypedDataInterface $element - * Typed configuration array element. - * @param array $options - * Array with translation options that must contain the keys defined in - * \Drupal\locale\LocaleTypedConfig::translateElement(). - * - * @return array - * Configuration data translated to the requested language. - */ - protected function getArrayTranslation(TraversableTypedDataInterface $element, array $options) { - $translation = array(); - foreach ($element as $key => $property) { - $value = $this->getElementTranslation($property, $options); - if (!empty($value)) { - $translation[$key] = $value; - } - } - return $translation; - } - - /** - * Translates element's value if it fits our translation criteria. - * - * For an element to be translatable by locale module it needs to be of base - * type 'string' and have 'translatable = TRUE' in the element's definition. - * Translatable elements may use these additional keys in their data - * definition: - * - 'translatable', FALSE to opt out of translation. - * - 'translation context', to define the string context. - * - * @param \Drupal\Core\TypedData\TypedDataInterface $element - * Configuration element. - * @param array $options - * Array with translation options that must contain the following keys: - * - 'source', Source language code. - * - 'target', Target language code. - * - * @return bool - * Whether the element fits the translation criteria. - */ - protected function translateElement(TypedDataInterface $element, array $options) { - if ($this->canTranslate($options['source'], $options['target'])) { - $definition = $element->getDataDefinition(); - $value = $element->getValue(); - if ($value && !empty($definition['translatable'])) { - $context = isset($definition['translation context']) ? $definition['translation context'] : ''; - if ($translation = $this->localeConfig->translateString($this->name, $options['target'], $value, $context)) { - $element->setValue($translation); - return TRUE; - } - } - } - // The element does not have a translation. - return FALSE; - } - -} diff --git a/core/modules/locale/src/Tests/LocaleConfigManagerTest.php b/core/modules/locale/src/Tests/LocaleConfigManagerTest.php index 4cc9fb2..58d63cc 100644 --- a/core/modules/locale/src/Tests/LocaleConfigManagerTest.php +++ b/core/modules/locale/src/Tests/LocaleConfigManagerTest.php @@ -28,16 +28,16 @@ class LocaleConfigManagerTest extends KernelTestBase { * Tests hasTranslation(). */ public function testHasTranslation() { - $this->installSchema('locale', array('locales_location')); + $this->installSchema('locale', array('locales_location', 'locales_source', 'locales_target')); $this->installConfig(array('locale_test')); - $locale_config_manager = \Drupal::service('locale.config.typed'); + $locale_config_manager = \Drupal::service('locale.config_manager'); $language = ConfigurableLanguage::createFromLangcode('de'); $language->save(); - $result = $locale_config_manager->hasTranslation('locale_test.no_translation', $language); + $result = $locale_config_manager->hasTranslation('locale_test.no_translation', $language->getId()); $this->assertFalse($result, 'There is no translation for locale_test.no_translation configuration.'); - $result = $locale_config_manager->hasTranslation('locale_test.translation', $language); + $result = $locale_config_manager->hasTranslation('locale_test.translation', $language->getId()); $this->assertTrue($result, 'There is a translation for locale_test.translation configuration.'); } } diff --git a/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php b/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php index f3f7415..d95458c 100644 --- a/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php +++ b/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php @@ -68,12 +68,16 @@ protected function setUp() { $this->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->localeConfigManager = $this->container->get('locale.config_manager'); $this->installSchema('locale', ['locales_source', 'locales_target', 'locales_location']); $this->installConfig(['locale_test']); ConfigurableLanguage::createFromLangcode($this->langcode)->save(); + + // Set up the locale database the same way we have in the config samples. + $this->setUpNoTranslation('locale_test.no_translation', 'test', 'Test'); + $this->setUpTranslation('locale_test.translation', 'test', 'English test', 'German test'); } /** @@ -82,7 +86,6 @@ protected function setUp() { 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)'); } @@ -93,8 +96,7 @@ public function testCreateTranslation() { public function testLocaleCreateTranslation() { $config_name = 'locale_test.no_translation'; - $this->setUpNoTranslation($config_name, 'test', 'Test'); - $this->saveLocaleTranslationData($config_name, 'test', 'Test (German)'); + $this->saveLocaleTranslationData($config_name, 'test', 'Test', 'Test (German)'); $this->assertTranslation($config_name, 'Test (German)', FALSE); } @@ -104,7 +106,6 @@ public function testLocaleCreateTranslation() { 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'); } @@ -115,8 +116,7 @@ public function testUpdateTranslation() { 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->saveLocaleTranslationData($config_name, 'test', 'English test', 'Updated German test'); $this->assertTranslation($config_name, 'Updated German test', FALSE); } @@ -126,7 +126,6 @@ public function testLocaleUpdateTranslation() { 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 @@ -140,9 +139,8 @@ public function testDeleteTranslation() { 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); + $this->assertNoTranslation($config_name); } /** @@ -161,9 +159,7 @@ public function testLocaleDeleteTranslation() { * 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->localeConfigManager->updateConfigTranslations(array($config_name), array($this->langcode)); $this->languageManager ->setConfigOverrideLanguage(ConfigurableLanguage::load($this->langcode)); @@ -192,18 +188,14 @@ protected function setUpNoTranslation($config_name, $key, $source) { 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, ''); + // locale_translate_batch_import() invoking + // LocaleConfigManager::updateConfigTranslations() normally. + $this->localeConfigManager->reset(); + $this->localeConfigManager + ->getStringTranslation($config_name, $this->langcode, $source, '') + ->setString($translation) + ->setCustomized(FALSE) + ->save(); $this->languageManager ->setConfigOverrideLanguage(ConfigurableLanguage::load($this->langcode)); @@ -254,12 +246,16 @@ protected function saveLanguageOverride($config_name, $key, $value) { * @param string $value * The configuration value to save. */ - protected function saveLocaleTranslationData($config_name, $key, $value) { + protected function saveLocaleTranslationData($config_name, $key, $source, $translation) { $this->localeConfigManager - ->saveTranslationData($config_name, $this->langcode, [$key => $value]); + ->getStringTranslation($config_name, $this->langcode, $source, '') + ->setString($translation) + ->save(); + $this->localeConfigManager->reset(); + $this->localeConfigManager->updateConfigTranslations(array($config_name), array($this->langcode)); $this->configFactory->reset($config_name); - $this->assertConfigValue($config_name, $key, $value); + $this->assertConfigValue($config_name, $key, $translation); } /** @@ -308,7 +304,11 @@ protected function deleteLanguageOverride($config_name, $key, $source_value) { * from the configuration factory after the deletion. */ protected function deleteLocaleTranslationData($config_name, $key, $source_value) { - $this->localeConfigManager->deleteTranslationData($config_name, $this->langcode); + $this->localeConfigManager + ->getStringTranslation($config_name, $this->langcode, $source_value, '') + ->delete(); + $this->localeConfigManager->reset(); + $this->localeConfigManager->updateConfigTranslations(array($config_name), array($this->langcode)); $this->configFactory->reset($config_name); $this->assertConfigValue($config_name, $key, $source_value); diff --git a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php index 27f120d..d5a7c21 100644 --- a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php +++ b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php @@ -79,12 +79,10 @@ public function testConfigTranslation() { ); $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations')); - $wrapper = $this->container->get('locale.config.typed')->get('system.site'); - // Get translation and check we've only got the site name. - $translation = $wrapper->getTranslation($langcode); - $properties = $translation->getElements(); - $this->assertEqual(count($properties), 1, 'Got the right number of properties after translation'); + $translation = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'system.site')->get(); + $this->assertEqual(count($translation), 1, 'Got the right number of properties after translation.'); + $this->assertEqual($translation['name'], $site_name, 'Got the right translation for the site name.'); // Check the translated site name is displayed. $this->drupalGet($langcode); @@ -109,12 +107,8 @@ public function testConfigTranslation() { ); $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations')); - $wrapper = $this->container->get('locale.config.typed')->get('core.date_format.medium'); - - // Get translation and check we've only got the site name. - $translation = $wrapper->getTranslation($langcode); - $format = $translation->get('pattern')->getValue(); - $this->assertEqual($format, 'D', 'Got the right date format pattern after translation.'); + $translation = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'core.date_format.medium')->get(); + $this->assertEqual($translation['pattern'], 'D', 'Got the right date format pattern after translation.'); // Formatting the date 8 / 27 / 1985 @ 13:37 EST with pattern D should // display "Tue". @@ -162,15 +156,8 @@ public function testConfigTranslation() { $this->assertTrue(count($translations) == 1 && $translation->source == $string->source && $translation->translation == $image_style_label, 'Got only one translation for image configuration.'); // Try more complex configuration data. - $wrapper = $this->container->get('locale.config.typed')->get('image.style.medium'); - - $translation = $wrapper->getTranslation($langcode); - $property = $translation->get('label'); - $this->assertEqual($property->getValue(), $image_style_label, 'Got the right translation for image style name after translation'); - - // Quick test to ensure translation file exists. - $override = \Drupal::languageManager()->getLanguageConfigOverride('xx', 'image.style.medium'); - $this->assertEqual($override->get('label'), $image_style_label); + $translation = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'image.style.medium')->get(); + $this->assertEqual($translation['label'], $image_style_label, 'Got the right translation for image style name after translation'); // Uninstall the module. $this->drupalPostForm('admin/modules/uninstall', array('uninstall[image]' => "image"), t('Uninstall')); diff --git a/core/modules/system/src/Tests/Installer/InstallerTranslationMultipleLanguageTest.php b/core/modules/system/src/Tests/Installer/InstallerTranslationMultipleLanguageTest.php index 94759e4..588f9ca 100644 --- a/core/modules/system/src/Tests/Installer/InstallerTranslationMultipleLanguageTest.php +++ b/core/modules/system/src/Tests/Installer/InstallerTranslationMultipleLanguageTest.php @@ -66,8 +66,15 @@ public function testTranslationsLoaded() { } } - $config = \Drupal::languageManager()->getLanguageConfigOverride('de', 'user.settings'); + // German may be in the active config or an override if not set as the + // default language. + $config = \Drupal::config('user.settings'); + if ($this->langcode != 'de') { + $config = \Drupal::languageManager()->getLanguageConfigOverride('de', 'user.settings'); + } $this->assertEqual($config->get('anonymous'), 'Anonymous German'); + + // Spanish is always an override (never used as default language). $config = \Drupal::languageManager()->getLanguageConfigOverride('es', 'user.settings'); $this->assertEqual($config->get('anonymous'), 'Anonymous Spanish'); }