diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index b8e122b..b906f72 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -316,11 +316,28 @@ condition.plugin:
text_format:
type: mapping
label: 'Text with text format'
+ # Even though it is not sensible to translate the text format of a formatted
+ # string, we conceive of the text and its date format as a single composite
+ # object and declare that object (or in other words the entire mapping) as
+ # translatable. This causes the entire mapping to be saved to the language
+ # overrides of the configuration. Storing only the (to be formatted) text
+ # could result in security problems in case the text format of the source
+ # text is changed.
+ translatable: true
mapping:
value:
type: text
label: 'Text'
+ # locale.module integrates the language overrides of shipped configuration
+ # with http://localize.drupal.org. Because it only handles strings and
+ # cannot deal with complex data structures, it parses the configuration
+ # schema until it reaches a primitive and only then checks whether the
+ # element is translatable.
translatable: true
format:
type: string
label: 'Text format'
+ # Even though the entire 'text_format' is marked as translatable for the
+ # sake of language configuration overrides, the ID of the text format of
+ # texts in shipped configuration should not be exposed to
+ # http://localize.drupal.org
diff --git a/core/lib/Drupal/Core/Config/ConfigEvents.php b/core/lib/Drupal/Core/Config/ConfigEvents.php
index 7fa22a5..a4b6446 100644
--- a/core/lib/Drupal/Core/Config/ConfigEvents.php
+++ b/core/lib/Drupal/Core/Config/ConfigEvents.php
@@ -9,6 +9,8 @@
/**
* Defines events for the configuration system.
+ *
+ * @see \Drupal\Core\Config\ConfiguCrudEvent
*/
final class ConfigEvents {
@@ -21,6 +23,18 @@
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()
@@ -28,6 +42,18 @@
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
new file mode 100644
index 0000000..377221c
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/ConfigOverrideCrudEvent.php
@@ -0,0 +1,45 @@
+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/lib/Drupal/Core/Config/TypedConfigManagerInterface.php b/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
index 827363f..c4138ff 100644
--- a/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
+++ b/core/lib/Drupal/Core/Config/TypedConfigManagerInterface.php
@@ -23,7 +23,7 @@
* @param string $name
* Configuration object name.
*
- * @return \Drupal\Core\Config\Schema\Element
+ * @return \Drupal\Core\Config\Schema\ArrayElement
* Typed configuration element.
*/
public function get($name);
diff --git a/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php b/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php
index 115d3bc..a0e20d5 100644
--- a/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php
+++ b/core/modules/config_translation/src/Form/ConfigTranslationFormBase.php
@@ -9,15 +9,14 @@
use Drupal\config_translation\ConfigMapperManagerInterface;
use Drupal\Core\Config\Config;
+use Drupal\Core\Config\Schema\ArrayElement;
use Drupal\Core\Config\Schema\Element;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\BaseFormIdInterface;
use Drupal\Core\Form\FormBase;
-use Drupal\Core\Language\LanguageInterface;
use Drupal\language\Config\LanguageConfigOverride;
use Drupal\language\ConfigurableLanguageManagerInterface;
-use Drupal\locale\StringStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -42,13 +41,6 @@
protected $configMapperManager;
/**
- * The string translation storage object.
- *
- * @var \Drupal\locale\StringStorageInterface
- */
- protected $localeStorage;
-
- /**
* The module handler to invoke the alter hook.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
@@ -91,21 +83,20 @@
protected $baseConfigData = array();
/**
- * Creates manage form object with string translation storage.
+ * Constructs a ConfigTranslationFormBase.
*
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
* The typed configuration manager.
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The configuration mapper manager.
- * @param \Drupal\locale\StringStorageInterface $locale_storage
- * The translation storage object.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook.
+ * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
+ * The configurable language manager.
*/
- public function __construct(TypedConfigManagerInterface $typed_config_manager, ConfigMapperManagerInterface $config_mapper_manager, StringStorageInterface $locale_storage, ModuleHandlerInterface $module_handler, ConfigurableLanguageManagerInterface $language_manager) {
+ public function __construct(TypedConfigManagerInterface $typed_config_manager, ConfigMapperManagerInterface $config_mapper_manager, ModuleHandlerInterface $module_handler, ConfigurableLanguageManagerInterface $language_manager) {
$this->typedConfigManager = $typed_config_manager;
$this->configMapperManager = $config_mapper_manager;
- $this->localeStorage = $locale_storage;
$this->moduleHandler = $module_handler;
$this->languageManager = $language_manager;
}
@@ -117,7 +108,6 @@ public static function create(ContainerInterface $container) {
return new static(
$container->get('config.typed'),
$container->get('plugin.manager.config_translation.mapper'),
- $container->get('locale.storage'),
$container->get('module_handler'),
$container->get('language_manager')
);
@@ -215,7 +205,7 @@ public function buildForm(array $form, array &$form_state, Request $request = NU
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
- $form_values = $form_state['values']['config_names'];
+ $form_values = $form_state['values']['translation']['config_names'];
// For the form submission handling, use the raw data.
$config_factory = $this->configFactory();
@@ -226,9 +216,8 @@ public function submitForm(array &$form, array &$form_state) {
// Set configuration values based on form submission and source values.
$base_config = $config_factory->get($name);
$config_translation = $this->languageManager->getLanguageConfigOverride($this->language->id, $name);
- $locations = $this->localeStorage->getLocations(array('type' => 'configuration', 'name' => $name));
- $this->setConfig($this->language, $base_config, $config_translation, $this->typedConfigManager->get($name), $form_values[$name], !empty($locations));
+ $this->setConfig($base_config, $config_translation, $this->typedConfigManager->get($name), $form_values[$name]);
// If no overrides, delete language specific configuration file.
$saved_config = $config_translation->get();
@@ -252,7 +241,7 @@ public function submitForm(array &$form, array &$form_state) {
*
* @param string $name
* The configuration name.
- * @param \Drupal\Core\Config\Schema\Element $schema
+ * @param \Drupal\Core\Config\Schema\ArrayElement $schema
* Schema definition of configuration.
* @param array|string $config_data
* Configuration object of requested language, a string when done traversing
@@ -264,12 +253,15 @@ public function submitForm(array &$form, array &$form_state) {
* (optional) Whether or not the details element of the form should be open.
* Defaults to TRUE.
* @param string|null $base_key
- * (optional) Base configuration key. Defaults to an empty string.
+ * (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.
*
* @return array
* An associative array containing the structure of the form.
*/
- protected function buildConfigForm($name, Element $schema, $config_data, $base_config_data, $open = TRUE, $base_key = '') {
+ protected function buildConfigForm($name, ArrayElement $schema, $config_data, $base_config_data, $open = TRUE, $base_key = NULL) {
$build = array();
foreach ($schema as $key => $element) {
// Make the specific element key, "$base_key.$key".
@@ -283,16 +275,37 @@ protected function buildConfigForm($name, Element $schema, $config_data, $base_c
);
$this->moduleHandler->alter('config_translation_type_info', $definitions);
- $element_type = $definition['type'];
-
- // When building the form we traverse the schema until we find an element
- // with a form element class. When setting the config values in, we
- // instead traverse the schema until we find a translatable element. This
- // allows for for elements handling multiple schema parts only some of
- // which are translatable. The TextFormat element requires this as the
- // form inherently covers the 'format' property as well, even though that
- // is not translatable.
- if ($element instanceof Element && !isset($definitions[$element_type]['form_element_class'])) {
+
+ // If this element is translatable, show a translation form. This may be a
+ // simple element, as is the case for unformatted text or date formats, or
+ // an array element, as is the case for formatted text. In the latter case
+ // the form element class is responsible for handling input for all
+ // configuration values contained in the element.
+ if (!empty($definition['translatable'])) {
+ /** @var \Drupal\config_translation\FormElement\ElementInterface $form_element */
+ $form_element = new $definition['form_element_class']();
+
+ $build[$element_key] = array(
+ '#theme' => 'config_translation_manage_form_element',
+ );
+ $build[$element_key]['source'] = $form_element->getSourceElement($definition, $this->sourceLanguage, $base_config_data[$key]);
+ $build[$element_key]['translation'] = $form_element->getTranslationElement($definition, $this->language, $config_data[$key]);
+ // For accessibility we make source and translation appear next to each
+ // other in the source for each element, which is why we utilize the
+ // 'source' and 'translation' sub-keys for the form. The form values,
+ // however, should mirror the configuration structure, so that we can
+ // traverse the configuration schema and still access the right
+ // configuration values in ConfigTranslationFormBase::setConfig().
+ // Therefore we make the 'source' and 'translation' keys the top-level
+ // keys in $form_state['values'].
+ $parents = array_merge(array('config_names', $name), explode('.', $element_key));
+ $build[$element_key]['source']['#parents'] = array_merge(array('source'), $parents);
+ $build[$element_key]['translation']['#parents'] = array_merge(array('translation'), $parents);
+
+ }
+ // If this is a non-translatable array element, traverse the schema
+ // further.
+ elseif ($element instanceof ArrayElement) {
// Build sub-structure and include it with a wrapper in the form
// if there are any translatable elements there.
$sub_build = $this->buildConfigForm($name, $element, $config_data[$key], $base_config_data[$key], FALSE, $element_key);
@@ -324,28 +337,8 @@ protected function buildConfigForm($name, Element $schema, $config_data, $base_c
) + $sub_build;
}
}
- elseif (isset($definition['form_element_class'])) {
- /** @var \Drupal\config_translation\FormElement\ElementInterface $form_element */
- $form_element = new $definition['form_element_class']();
-
- $build[$element_key] = array(
- '#theme' => 'config_translation_manage_form_element',
- );
- $build[$element_key]['source'] = $form_element->getSourceElement($definition, $this->sourceLanguage, $base_config_data[$key]);
- // Make sure that the source does not end up in the form values. The
- // 'item' element, for example, generally receives input,
- $build[$element_key]['source']['#input'] = FALSE;
-
- $build[$element_key]['translation'] = $form_element->getTranslationElement($definition, $this->language, $config_data[$key]);
- // For accessibility we make source and translation appear next to each
- // other in the source for each element, which is why we utilize the
- // 'source' and 'translation' sub-keys for the form. The form values,
- // however, should mirror the configuration structure, so that we can
- // traverse the configuration schema in
- // ConfigTranslationFormBase::setConfig(). Therefore, the 'translation'
- // key is removed from the form parents.
- $build[$element_key]['translation']['#parents'] = array_merge(array('config_names', $name), explode('.', $element_key));
- }
+ // If this is a simple, non-translatable element, simply continue with the
+ // next element.
}
return $build;
}
@@ -353,13 +346,11 @@ protected function buildConfigForm($name, Element $schema, $config_data, $base_c
/**
* Sets configuration based on a nested form value array.
*
- * @param \Drupal\Core\Language\LanguageInterface $language
- * Set the configuration in this language.
* @param \Drupal\Core\Config\Config $base_config
* Base configuration values, in the source language.
* @param \Drupal\language\Config\LanguageConfigOverride $config_translation
* Translation configuration override data.
- * @param \Drupal\Core\Config\Schema\Element $schema
+ * @param \Drupal\Core\Config\Schema\ArrayElement $schema
* Schema definition of configuration.
* @param array $config_values
* A simple one dimensional or recursive array:
@@ -372,15 +363,16 @@ protected function buildConfigForm($name, Element $schema, $config_data, $base_c
* );
* Either format is used, the nested arrays are just containers and not
* needed for saving the data.
- * @param bool $shipped_config
- * (optional) Flag to specify whether the configuration had a shipped
- * version and therefore should also be stored in the locale database.
+ * @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.
*
- * @param null $base_key
* @return array
* Translation configuration override data.
*/
- protected function setConfig(LanguageInterface $language, LanguageConfigOverride $base_config, Config $config_translation, Element $schema, array $config_values, $shipped_config = FALSE, $base_key = NULL) {
+ protected function setConfig(Config $base_config, LanguageConfigOverride $config_translation, ArrayElement $schema, array $config_values, $base_key = NULL) {
foreach ($schema as $key => $element) {
if (!isset($config_values[$key])) {
continue;
@@ -395,39 +387,16 @@ protected function setConfig(LanguageInterface $language, LanguageConfigOverride
// the TextFormat element's 'format' value.
if ($element instanceof Element && empty($definition['translatable'])) {
// Traverse into this level in the configuration.
- $this->setConfig($language, $base_config, $config_translation, $element, $value, $shipped_config, $element_key);
+ $this->setConfig($base_config, $config_translation, $element, $value, $element_key);
}
else {
- // If the configuration file being translated was originally shipped, we
- // should update the locale translation storage. The string should
- // already be there, but we make sure to check.
- if ($shipped_config && $source_string = $this->localeStorage->findString(array('source' => $base_config->get($key)))) {
-
- // Get the translation for this original source string from locale.
- $conditions = array(
- 'lid' => $source_string->lid,
- 'language' => $language->id,
- );
- $translations = $this->localeStorage->getTranslations($conditions + array('translated' => TRUE));
- // If we got a translation, take that, otherwise create a new one.
- $translation = reset($translations) ?: $this->localeStorage->createTranslation($conditions);
-
- // If we have a new translation or different from what is stored in
- // locale before, save this as an updated customize translation.
- if ($translation->isNew() || $translation->getString() != $value) {
- $translation->setString($value)
- ->setCustomized()
- ->save();
- }
- }
-
// Save value, if different from the source value in the base
// configuration. If same as original configuration, remove override.
- if ($base_config->get($key) !== $value) {
+ if ($base_config->get($element_key) !== $value) {
$config_translation->set($element_key, $value);
}
else {
- $config_translation->clear($key);
+ $config_translation->clear($element_key);
}
}
}
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/FormElement/FormElementBase.php b/core/modules/config_translation/src/FormElement/FormElementBase.php
similarity index 100%
rename from core/modules/config_translation/lib/Drupal/config_translation/FormElement/FormElementBase.php
rename to core/modules/config_translation/src/FormElement/FormElementBase.php
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/FormElement/TextFormat.php b/core/modules/config_translation/src/FormElement/TextFormat.php
similarity index 76%
rename from core/modules/config_translation/lib/Drupal/config_translation/FormElement/TextFormat.php
rename to core/modules/config_translation/src/FormElement/TextFormat.php
index 4b5d14a..451c204 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/FormElement/TextFormat.php
+++ b/core/modules/config_translation/src/FormElement/TextFormat.php
@@ -24,6 +24,7 @@ public function getTranslationElement(array $definition, LanguageInterface $lang
'#type' => 'text_format',
'#default_value' => $value['value'],
'#format' => $value['format'],
+ '#allowed_formats' => array($value['format']),
) + parent::getTranslationElement($definition, $language, $value);
}
@@ -39,13 +40,6 @@ public function getSourceElement(array $definition, LanguageInterface $source_la
'#disabled' => TRUE,
'#allow_focus' => TRUE,
);
- // Because #input is set to FALSE in
- // ConfigTranslationFormBase::buildConfigForm(),
- // FormBuilder::handleInputElement() is not called on this element.
- // Therefore we need to set the appropriate attribute manually.
- $element['#attributes']['readonly'] = 'readonly';
- // @todo CKEditor does not support the 'readonly' attribute.
- $element['#attributes']['disabled'] = 'disabled';
return $element;
}
diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
index 73f25ba..27422fd 100644
--- a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
@@ -128,8 +128,8 @@ public function testSiteInformationTranslationUi() {
// Update site name and slogan for French.
$edit = array(
- 'config_names[system.site][name]' => $fr_site_name,
- 'config_names[system.site][slogan]' => $fr_site_slogan,
+ 'translation[config_names][system.site][name]' => $fr_site_name,
+ 'translation[config_names][system.site][slogan]' => $fr_site_slogan,
);
$this->drupalPostForm("$translation_base_url/fr/add", $edit, t('Save translation'));
@@ -142,8 +142,8 @@ public function testSiteInformationTranslationUi() {
// Check translation saved proper.
$this->drupalGet("$translation_base_url/fr/edit");
- $this->assertFieldByName('config_names[system.site][name]', $fr_site_name);
- $this->assertFieldByName('config_names[system.site][slogan]', $fr_site_slogan);
+ $this->assertFieldByName('translation[config_names][system.site][name]', $fr_site_name);
+ $this->assertFieldByName('translation[config_names][system.site][slogan]', $fr_site_slogan);
// Check French translation of site name and slogan are in place.
$this->drupalGet('fr');
@@ -171,8 +171,8 @@ public function testSourceValueDuplicateSave() {
// Case 1: Update new value for site slogan and site name.
$edit = array(
- 'config_names[system.site][name]' => 'FR ' . $site_name,
- 'config_names[system.site][slogan]' => 'FR ' . $site_slogan,
+ 'translation[config_names][system.site][name]' => 'FR ' . $site_name,
+ 'translation[config_names][system.site][slogan]' => 'FR ' . $site_slogan,
);
// First time, no overrides, so just Add link.
$this->drupalPostForm("$translation_base_url/fr/add", $edit, t('Save translation'));
@@ -194,8 +194,8 @@ public function testSourceValueDuplicateSave() {
$this->assertNoText('FR ' . $site_name);
$this->assertNoText('FR ' . $site_slogan);
$edit = array(
- 'config_names[system.site][name]' => $site_name,
- 'config_names[system.site][slogan]' => 'FR ' . $site_slogan,
+ 'translation[config_names][system.site][name]' => $site_name,
+ 'translation[config_names][system.site][slogan]' => 'FR ' . $site_slogan,
);
$this->drupalPostForm(NULL, $edit, t('Save translation'));
$this->assertRaw(t('Successfully updated @language translation.', array('@language' => 'French')));
@@ -209,8 +209,8 @@ public function testSourceValueDuplicateSave() {
$this->drupalGet("$translation_base_url/fr/edit");
$this->assertNoText('FR ' . $site_slogan);
$edit = array(
- 'config_names[system.site][name]' => $site_name,
- 'config_names[system.site][slogan]' => $site_slogan,
+ 'translation[config_names][system.site][name]' => $site_name,
+ 'translation[config_names][system.site][slogan]' => $site_slogan,
);
$this->drupalPostForm(NULL, $edit, t('Save translation'));
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.site');
@@ -274,8 +274,8 @@ public function testContactConfigEntityTranslation() {
// Update translatable fields.
$edit = array(
- 'config_names[contact.category.feedback][label]' => 'Website feedback - ' . $langcode,
- 'config_names[contact.category.feedback][reply]' => 'Thank you for your mail - ' . $langcode,
+ 'translation[config_names][contact.category.feedback][label]' => 'Website feedback - ' . $langcode,
+ 'translation[config_names][contact.category.feedback][reply]' => 'Thank you for your mail - ' . $langcode,
);
// Save language specific version of form.
@@ -313,7 +313,7 @@ public function testContactConfigEntityTranslation() {
$langcode_prefixes = array_merge(array(''), $this->langcodes);
foreach ($langcode_prefixes as $langcode_prefix) {
$this->drupalGet(ltrim("$langcode_prefix/$translation_base_url/$langcode/edit"));
- $this->assertFieldByName('config_names[contact.category.feedback][label]', 'Website feedback - ' . $langcode);
+ $this->assertFieldByName('translation[config_names][contact.category.feedback][label]', 'Website feedback - ' . $langcode);
$this->assertText($label);
}
}
@@ -403,8 +403,8 @@ public function testDateFormatTranslation() {
// Update translatable fields.
$edit = array(
- 'config_names[system.date_format.' . $id . '][label]' => $id . ' - FR',
- 'config_names[system.date_format.' . $id . '][pattern]' => 'D',
+ 'translation[config_names][system.date_format.' . $id . '][label]' => $id . ' - FR',
+ 'translation[config_names][system.date_format.' . $id . '][pattern]' => 'D',
);
// Save language specific version of form.
@@ -440,9 +440,9 @@ public function testAccountSettingsConfigurationTranslation() {
// Update account settings fields for French.
$edit = array(
- 'config_names[user.settings][anonymous]' => 'Anonyme',
- 'config_names[user.mail][status_blocked][subject]' => 'Testing, your account is blocked.',
- 'config_names[user.mail][status_blocked][body]' => 'Testing account blocked body.',
+ 'translation[config_names][user.settings][anonymous]' => 'Anonyme',
+ 'translation[config_names][user.mail][status_blocked][subject]' => 'Testing, your account is blocked.',
+ 'translation[config_names][user.mail][status_blocked][body]' => 'Testing account blocked body.',
);
$this->drupalPostForm('admin/config/people/accounts/translate/fr/add', $edit, t('Save translation'));
@@ -538,10 +538,10 @@ public function testViewsTranslationUI() {
// Update Views Fields for French.
$edit = array(
- 'config_names[views.view.frontpage][description]' => $description . " FR",
- 'config_names[views.view.frontpage][label]' => $human_readable_name . " FR",
- 'config_names[views.view.frontpage][display][default][display_title]' => $display_settings_master . " FR",
- 'config_names[views.view.frontpage][display][default][display_options][title]' => $display_options_master . " FR",
+ 'translation[config_names][views.view.frontpage][description]' => $description . " FR",
+ 'translation[config_names][views.view.frontpage][label]' => $human_readable_name . " FR",
+ 'translation[config_names][views.view.frontpage][display][default][display_title]' => $display_settings_master . " FR",
+ 'translation[config_names][views.view.frontpage][display][default][display_options][title]' => $display_options_master . " FR",
);
$this->drupalPostForm("$translation_base_url/fr/add", $edit, t('Save translation'));
$this->assertRaw(t('Successfully saved @language translation.', array('@language' => 'French')));
@@ -553,10 +553,10 @@ public function testViewsTranslationUI() {
// Check translation saved proper.
$this->drupalGet("$translation_base_url/fr/edit");
- $this->assertFieldByName('config_names[views.view.frontpage][description]', $description . " FR");
- $this->assertFieldByName('config_names[views.view.frontpage][label]', $human_readable_name . " FR");
- $this->assertFieldByName('config_names[views.view.frontpage][display][default][display_title]', $display_settings_master . " FR");
- $this->assertFieldByName('config_names[views.view.frontpage][display][default][display_options][title]', $display_options_master . " FR");
+ $this->assertFieldByName('translation[config_names][views.view.frontpage][description]', $description . " FR");
+ $this->assertFieldByName('translation[config_names][views.view.frontpage][label]', $human_readable_name . " FR");
+ $this->assertFieldByName('translation[config_names][views.view.frontpage][display][default][display_title]', $display_settings_master . " FR");
+ $this->assertFieldByName('translation[config_names][views.view.frontpage][display][default][display_options][title]', $display_options_master . " FR");
}
/**
@@ -587,7 +587,7 @@ public function testLocaleDBStorage() {
// Add custom translation.
$edit = array(
- 'config_names[user.settings][anonymous]' => 'Anonyme',
+ 'translation[config_names][user.settings][anonymous]' => 'Anonyme',
);
$this->drupalPostForm('admin/config/people/accounts/translate/fr/add', $edit, t('Save translation'));
@@ -598,7 +598,7 @@ public function testLocaleDBStorage() {
// revert custom translations to base translation.
$edit = array(
- 'config_names[user.settings][anonymous]' => 'Anonymous',
+ 'translation[config_names][user.settings][anonymous]' => 'Anonymous',
);
$this->drupalPostForm('admin/config/people/accounts/translate/fr/edit', $edit, t('Save translation'));
@@ -684,7 +684,7 @@ public function testTextFormatTranslation() {
// Update translatable fields.
$edit = array(
- 'config_names[config_translation_test.content][content][value]' => 'Hello World - FR',
+ 'translation[config_names][config_translation_test.content][content][value]' => 'Hello World - FR',
);
// Save language specific version of form.
diff --git a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php
index 6c71478..3539400 100644
--- a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php
+++ b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php
@@ -93,7 +93,15 @@ public function loadOverrides($names) {
public function getOverride($langcode, $name) {
$storage = $this->getStorage($langcode);
$data = $storage->read($name);
- $override = new LanguageConfigOverride($name, $storage, $this->typedConfigManager);
+
+ $override = new LanguageConfigOverride(
+ $name,
+ $langcode,
+ $storage,
+ $this->typedConfigManager,
+ $this->eventDispatcher
+ );
+
if (!empty($data)) {
$override->initWithData($data);
}
diff --git a/core/modules/language/src/Config/LanguageConfigOverride.php b/core/modules/language/src/Config/LanguageConfigOverride.php
index 19fb549..11a9ed7 100644
--- a/core/modules/language/src/Config/LanguageConfigOverride.php
+++ b/core/modules/language/src/Config/LanguageConfigOverride.php
@@ -7,9 +7,12 @@
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;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Defines language configuration overrides.
@@ -17,20 +20,40 @@
class LanguageConfigOverride extends StorableConfigBase {
/**
+ * The language code of this language override.
+ *
+ * @var string
+ */
+ protected $langcode;
+
+ /**
+ * The event dispatcher.
+ *
+ * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ /**
* Constructs a language override object.
*
* @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.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager service.
+ * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
+ * The event dispatcher.
*/
- public function __construct($name, StorageInterface $storage, TypedConfigManagerInterface $typed_config) {
+ public function __construct($name, $langcode, 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;
}
/**
@@ -45,6 +68,7 @@ public function save() {
}
$this->storage->write($this->name, $this->data);
$this->isNew = FALSE;
+ $this->eventDispatcher->dispatch(ConfigEvents::SAVE_OVERRIDE, new ConfigOverrideCrudEvent($this));
$this->originalData = $this->data;
return $this;
}
@@ -56,8 +80,19 @@ public function delete() {
$this->data = array();
$this->storage->delete($this->name);
$this->isNew = TRUE;
+ $this->eventDispatcher->dispatch(ConfigEvents::DELETE_OVERRIDE, new ConfigOverrideCrudEvent($this));
$this->originalData = $this->data;
return $this;
}
+ /**
+ * Returns the language code of this language override.
+ *
+ * @return string
+ * The language code.
+ */
+ public function getLangcode() {
+ return $this->langcode;
+ }
+
}
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index ba5f497..ba161d6 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -5,9 +5,10 @@
* Mass import-export and batch import functionality for Gettext .po files.
*/
-use Drupal\locale\Gettext;
use Drupal\Core\Language\LanguageInterface;
use Drupal\file\FileInterface;
+use Drupal\locale\Gettext;
+use Drupal\locale\Locale;
/**
* Prepare a batch to import all translations.
@@ -627,18 +628,26 @@ function locale_config_batch_finished($success, array $results) {
* Number of configuration objects retranslated.
*/
function locale_config_update_multiple(array $names, $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 = \Drupal\locale\Locale::config()->get($name);
+ $wrapper = $locale_config_manager->get($name);
foreach ($langcodes as $langcode) {
$translation = $wrapper->getValue() ? $wrapper->getTranslation($langcode)->getValue() : NULL;
if ($translation) {
- \Drupal\locale\Locale::config()->saveTranslationData($name, $langcode, $translation);
+ $locale_config_manager->saveTranslationData($name, $langcode, $translation);
$count++;
}
else {
- \Drupal\locale\Locale::config()->deleteTranslationData($name, $langcode);
+ // 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);
+ }
}
}
}
diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml
index f7794c3..a9ea7ea 100644
--- a/core/modules/locale/locale.services.yml
+++ b/core/modules/locale/locale.services.yml
@@ -11,3 +11,8 @@ services:
tags:
- { name: string_translator }
- { name: needs_destruction }
+ locale.config_subscriber:
+ class: Drupal\locale\LocaleConfigSubscriber
+ arguments: ['@locale.storage', '@config.factory', '@locale.config.typed']
+ tags:
+ - { name: event_subscriber }
diff --git a/core/modules/locale/src/LocaleConfigSubscriber.php b/core/modules/locale/src/LocaleConfigSubscriber.php
new file mode 100644
index 0000000..4d97644
--- /dev/null
+++ b/core/modules/locale/src/LocaleConfigSubscriber.php
@@ -0,0 +1,161 @@
+stringStorage = $string_storage;
+ $this->configFactory = $config_factory;
+ $this->localeConfigManager = $locale_config_manager;
+ }
+
+ /**
+ * {@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';
+ return $events;
+ }
+
+
+ /**
+ * Updates the translation strings of shipped configuration.
+ *
+ * @param \Drupal\Core\Config\ConfigOverrideCrudEvent $event
+ */
+ public function onOverrideUpdate(ConfigOverrideCrudEvent $event) {
+ $translation_config = $event->getConfig();
+ $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,
+ ))
+ ) {
+ $source_config = $this->configFactory->get($name);
+ $schema = $this->localeConfigManager->get($name)->getTypedConfig();
+ $this->saveStrings($source_config, $translation_config, $schema);
+ }
+ }
+
+ /**
+ * Updates strings for a certain config element.
+ *
+ * @param \Drupal\Core\Config\Config $source_config
+ * The source configuration.
+ * @param \Drupal\language\Config\LanguageConfigOverride $translation_config
+ * The language configuration override.
+ * @param \Drupal\Core\Config\Schema\ArrayElement $schema
+ * The respective configuration schema.
+ * @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.
+ */
+ protected function saveStrings(Config $source_config, LanguageConfigOverride $translation_config, ArrayElement $schema, $base_key = NULL) {
+ foreach ($schema as $key => $element) {
+
+ $element_key = implode('.', array_filter(array($base_key, $key)));
+
+ // We only care for strings here, so traverse the schema further in the
+ // case of array elements.
+ if ($element instanceof ArrayElement) {
+ $this->saveStrings($source_config, $translation_config, $element, $element_key);
+ }
+ 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);
+
+ // If we have a new translation or different from what is stored in
+ // locale before, save this as an updated customize translation.
+ $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 ($translation->isNew() || $translation->getString() != $value) {
+ $translation->setString($value)
+ ->setCustomized()
+ ->save();
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
index 3abe65a..05094b1 100644
--- a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
+++ b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
@@ -139,8 +139,10 @@ function testConfigTranslation() {
// Check the string is unique and has no translation yet.
$translations = $this->storage->getTranslations(array('language' => $langcode, 'type' => 'configuration', 'name' => 'image.style.medium'));
+ $this->assertEqual(count($translations), 1);
$translation = reset($translations);
- $this->assertTrue(count($translations) == 1 && $translation->source == $string->source && empty($translation->translation), 'Got only one string for image configuration and has no translation.');
+ $this->assertEqual($translation->source, $string->source);
+ $this->assertTrue(empty($translation->translation));
// Translate using the UI so configuration is refreshed.
$image_style_label = $this->randomName(20);