diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index fc534df..0295bf0 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -214,3 +214,16 @@ config_dependencies: label: 'Theme dependencies' sequence: - type: string + +# Human readable string that is associated with a format. +text_with_format: + type: mapping + label: 'Text with format' + mapping: + value: + type: text + label: 'Content' + translatable: true + format: + type: string + label: 'Text format' diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module index 74047f7..d608b0f 100644 --- a/core/modules/config_translation/config_translation.module +++ b/core/modules/config_translation/config_translation.module @@ -160,12 +160,30 @@ function config_translation_entity_operation(EntityInterface $entity) { /** * Implements hook_config_translation_type_info_alter(). + * + * @todo Convert this hook into a real typed data alter hook, + * https://drupal.org/node/2145633. */ function config_translation_config_translation_type_info_alter(&$definitions) { + $map = array( + 'label' => '\Drupal\config_translation\FormElement\Textfield', + 'text' => '\Drupal\config_translation\FormElement\Textarea', + 'date_format' => '\Drupal\config_translation\FormElement\DateFormat', + 'text_with_format' => '\Drupal\config_translation\FormElement\TextFormat', + ); + // Enhance the text and date type definitions with classes to generate proper // form elements in ConfigTranslationFormBase. Other translatable types will // appear as a one line textfield. - $definitions['text']['form_element_class'] = '\Drupal\config_translation\FormElement\Textarea'; - $definitions['date_format']['form_element_class'] = '\Drupal\config_translation\FormElement\DateFormat'; + foreach ($definitions as $type => &$definition) { + if (!isset($definition['form_element_class'])) { + if (isset($map[$definition['type']])) { + $definition['form_element_class'] = $map[$definition['type']]; + } + elseif (!empty($definition['translatable'])) { + $definition['form_element_class'] = '\Drupal\config_translation\FormElement\Textfield'; + } + } + } } diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Form/ConfigTranslationFormBase.php b/core/modules/config_translation/lib/Drupal/config_translation/Form/ConfigTranslationFormBase.php index 4d2bd68..8d60281 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/Form/ConfigTranslationFormBase.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/Form/ConfigTranslationFormBase.php @@ -198,7 +198,7 @@ public function buildForm(array $form, array &$form_state, Request $request = NU ); foreach ($this->mapper->getConfigNames() as $name) { $form['config_names'][$name] = array('#type' => 'container'); - $form['config_names'][$name] += $this->buildConfigForm($this->typedConfigManager->get($name), $this->config($name)->get(), $this->baseConfigData[$name]); + $form['config_names'][$name] += $this->buildConfigForm($name, $this->typedConfigManager->get($name), $this->config($name)->get(), $this->baseConfigData[$name]); } $form['actions']['#type'] = 'actions'; @@ -230,7 +230,7 @@ public function submitForm(array &$form, array &$form_state) { $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, $form_values[$name], !empty($locations)); + $this->setConfig($this->language, $base_config, $config_translation, $form_values[$name], $this->typedConfigManager->get($name), !empty($locations)); // If no overrides, delete language specific configuration file. $saved_config = $config_translation->get(); @@ -252,6 +252,8 @@ public function submitForm(array &$form, array &$form_state) { /** * Formats configuration schema as a form tree. * + * @param string $name + * The configuration name. * @param \Drupal\Core\Config\Schema\Element $schema * Schema definition of configuration. * @param array|string $config_data @@ -269,16 +271,26 @@ public function submitForm(array &$form, array &$form_state) { * @return array * An associative array containing the structure of the form. */ - protected function buildConfigForm(Element $schema, $config_data, $base_config_data, $open = TRUE, $base_key = '') { + protected function buildConfigForm($name, Element $schema, $config_data, $base_config_data, $open = TRUE, $base_key = '') { $build = array(); foreach ($schema as $key => $element) { // Make the specific element key, "$base_key.$key". $element_key = implode('.', array_filter(array($base_key, $key))); $definition = $element->getDataDefinition() + array('label' => $this->t('N/A')); - if ($element instanceof Element) { + + // Invoke hook_config_translation_type_info_alter() implementations to + // alter the configuration types. + $definitions = array( + $definition['type'] => &$definition, + ); + + $this->moduleHandler->alter('config_translation_type_info', $definitions); + $element_type = $definition['type']; + + if ($element instanceof Element && !isset($definitions[$element_type]['form_element_class'])) { // Build sub-structure and include it with a wrapper in the form // if there are any translatable elements there. - $sub_build = $this->buildConfigForm($element, $config_data[$key], $base_config_data[$key], FALSE, $element_key); + $sub_build = $this->buildConfigForm($name, $element, $config_data[$key], $base_config_data[$key], FALSE, $element_key); if (!empty($sub_build)) { // For some configuration elements the same element structure can // repeat multiple times, (like views displays, filters, etc.). @@ -307,27 +319,12 @@ protected function buildConfigForm(Element $schema, $config_data, $base_config_d ) + $sub_build; } } - else { - $definition = $element->getDataDefinition(); - - // Invoke hook_config_translation_type_info_alter() implementations to - // alter the configuration types. - $definitions = array( - $definition['type'] => &$definition, - ); - $this->moduleHandler->alter('config_translation_type_info', $definitions); - - // Create form element only for translatable items. - if (!isset($definition['translatable']) || !isset($definition['type'])) { - continue; - } - + elseif (isset($definition['form_element_class'])) { $value = $config_data[$key]; $build[$element_key] = array( '#theme' => 'config_translation_manage_form_element', ); $build[$element_key]['source'] = array( - '#markup' => $base_config_data[$key] ? ('' . nl2br($base_config_data[$key] . '')) : t('(Empty)'), '#title' => $this->t( '!label (!source_language)', array( @@ -336,13 +333,15 @@ protected function buildConfigForm(Element $schema, $config_data, $base_config_d ) ), '#type' => 'item', + '#input' => FALSE, ); - $definition += array('form_element_class' => '\Drupal\config_translation\FormElement\Textfield'); - /** @var \Drupal\config_translation\FormElement\ElementInterface $form_element */ $form_element = new $definition['form_element_class'](); + + $build[$element_key]['source']['#markup'] = $form_element->getRenderedSource($base_config_data, $key, $this->sourceLanguage, $this->language); $build[$element_key]['translation'] = $form_element->getFormElement($definition, $this->language, $value); + $build[$element_key]['translation']['#parents'] = array_merge(array('config_names', $name), explode('.', $element_key)); } } return $build; @@ -368,6 +367,8 @@ protected function buildConfigForm(Element $schema, $config_data, $base_config_d * ); * Either format is used, the nested arrays are just containers and not * needed for saving the data. + * @param \Drupal\Core\Config\Schema\Element $schema + * Schema definition of configuration. * @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. @@ -375,14 +376,19 @@ protected function buildConfigForm(Element $schema, $config_data, $base_config_d * @return array * Translation configuration override data. */ - protected function setConfig(Language $language, Config $base_config, Config $config_translation, array $config_values, $shipped_config = FALSE) { - foreach ($config_values as $key => $value) { - if (is_array($value) && !isset($value['translation'])) { + protected function setConfig(Language $language, Config $base_config, Config $config_translation, array $config_values, Element $schema, $shipped_config = FALSE, $base_key = NULL) { + foreach ($schema as $key => $element) { + if (!isset($config_values[$key])) { + continue; + } + $element_key = implode('.', array_filter(array($base_key, $key))); + $definition = $element->getDataDefinition(); + $value = $config_values[$key]; + if ($element instanceof Element && empty($definition['translatable'])) { // Traverse into this level in the configuration. - $this->setConfig($language, $base_config, $config_translation, $value, $shipped_config); + $this->setConfig($language, $base_config, $config_translation, $value, $element, $shipped_config, $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. @@ -397,10 +403,40 @@ protected function setConfig(Language $language, Config $base_config, Config $co // If we got a translation, take that, otherwise create a new one. $translation = reset($translations) ?: $this->localeStorage->createTranslation($conditions); + // Some schema types provide a form_element_class which is responsible + // for providing us with a translation string. We iterate through the + // $key string to check if we have a schema definition for that. + $types = explode(".", $key); + $type = array_shift($types); + // If there is a schema definition, run the + // hook_config_translation_type_info_alter. + if (isset($schema[$type])) { + $element = $schema[$type]; + $definition = $element->getDataDefinition(); + + // Invoke hook_config_translation_type_info_alter() implementations to + // alter the configuration types. + $definitions = array( + $definition['type'] => &$definition, + ); + $this->moduleHandler->alter('config_translation_type_info', $definitions); + } + + // If form_element_class is set, create a new object of this class and + // let it return the string of the translation. + if (isset($definition['form_element_class'])) { + /** @var \Drupal\config_translation\FormElement\ElementInterface $form_element */ + $form_element = new $definition['form_element_class'](); + + $translation_string = $form_element->getTranslationString($value); + } else { + $translation_string = $value; + } + // 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']) { - $translation->setString($value['translation']) + if ($translation->isNew() || $translation->getString() != $translation_string) { + $translation->setString($translation_string) ->setCustomized() ->save(); } @@ -408,8 +444,8 @@ protected function setConfig(Language $language, Config $base_config, Config $co // 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['translation']) { - $config_translation->set($key, $value['translation']); + if ($base_config->get($key) !== $value) { + $config_translation->set($element_key, $value); } else { $config_translation->clear($key); diff --git a/core/modules/config_translation/lib/Drupal/config_translation/FormElement/Element.php b/core/modules/config_translation/lib/Drupal/config_translation/FormElement/Element.php index 40a43c2..ce33fcb 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/FormElement/Element.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/FormElement/Element.php @@ -7,6 +7,8 @@ namespace Drupal\config_translation\FormElement; +use Drupal\Core\Language\Language; + /** * Defines a base class for form elements. */ @@ -41,4 +43,18 @@ protected function translationManager() { return $this->translationManager; } + /** + * {@inheritdoc} + */ + public function getRenderedSource($base_config_data, $key, Language $source_language, Language $language) { + return $base_config_data[$key] ? ('' . nl2br($base_config_data[$key] . '')) : t('(Empty)'); + } + + /** + * {@inheritdoc} + */ + public function getTranslationString($value) { + return $value; + } + } diff --git a/core/modules/config_translation/lib/Drupal/config_translation/FormElement/ElementInterface.php b/core/modules/config_translation/lib/Drupal/config_translation/FormElement/ElementInterface.php index 9072cfc..3713024 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/FormElement/ElementInterface.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/FormElement/ElementInterface.php @@ -29,4 +29,34 @@ */ public function getFormElement(array $definition, Language $language, $value); + /** + * Returns the rendered source element for a given configuration definition. + * + * @param array|string $base_config_data + * Configuration object of base language, a string when done traversing + * the data building each sub-structure for the form. + * @param string $key + * The key in the configuration object. + * @param \Drupal\Core\Language\Language $source_language + * Thee source language of the configuration object. + * @param \Drupal\Core\Language\Language $language + * Language object to display the translation form for. + * + * @return string + * The rendered value in source language. + */ + public function getRenderedSource($base_config_data, $key, Language $source_language, Language $language); + + + /** + * Returns the translation to be used in string storage. + * + * @param mixed $value + * The value of the translation element. + * + * @return string + * String of translation. + */ + public function getTranslationString($value); + } diff --git a/core/modules/config_translation/lib/Drupal/config_translation/FormElement/TextFormat.php b/core/modules/config_translation/lib/Drupal/config_translation/FormElement/TextFormat.php new file mode 100644 index 0000000..522dbda --- /dev/null +++ b/core/modules/config_translation/lib/Drupal/config_translation/FormElement/TextFormat.php @@ -0,0 +1,46 @@ + 'text_format', + '#default_value' => $value['value'], + '#format' => $value['format'], + '#title' => $this->t($definition['label']) . ' (' . $language->name . ')', + '#attributes' => array('lang' => $language->id), + ); + return $element; + } + + /** + * {@inheritdoc} + */ + public function getRenderedSource($base_config_data, $key, Language $source_language, Language $language) { + $value = check_markup($base_config_data[$key]['value'], $base_config_data[$key]['format'], $source_language->id); + return $value ? '' . $value . '' : t('(Empty)'); + } + + /** + * {@inheritdoc} + */ + public function getTranslationString($value) { + return $value['value']; + } + +} diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationFormTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationFormTest.php index c5bce3f..591c796 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationFormTest.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationFormTest.php @@ -20,7 +20,7 @@ class ConfigTranslationFormTest extends WebTestBase { * * @var array */ - public static $modules = array('config_translation', 'config_translation_test'); + public static $modules = array('config_translation', 'config_translation_test', 'editor'); /** * The plugin ID of the mapper to test. diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php index 405aaf5..d32e894 100644 --- a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php +++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php @@ -23,7 +23,7 @@ class ConfigTranslationUiTest extends WebTestBase { * * @var array */ - public static $modules = array('node', 'contact', 'config_translation', 'config_translation_test', 'views', 'views_ui', 'contextual'); + public static $modules = array('node', 'contact', 'config_translation', 'config_translation_test', 'views', 'views_ui', 'contextual', 'filter'); /** * Languages to enable. @@ -675,6 +675,57 @@ public function testThemeDiscovery() { } /** + * Test text_format translation. + */ + public function testTextFormatTranslation() { + $this->drupalLogin($this->admin_user); + $file_storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); + + $config_parsed = $file_storage->read('config_translation_test.content'); + $config_parsed_compare = array( + 'content' => array( + 'value' => $config_parsed['content']['value'], + 'format' => $config_parsed['content']['format'], + ), + ); + + $expected = array( + 'content' => array( + 'value' => 'Hello World', + 'format' => 'plain_text', + ), + ); + $this->assertEqual($expected, $config_parsed_compare); + + $translation_base_url = 'admin/config/media/file-system/translate'; + $this->drupalGet($translation_base_url); + + // 'Add' link should be present for French translation. + $translation_page_url = "$translation_base_url/fr/add"; + $this->assertLinkByHref($translation_page_url); + + $this->drupalGet($translation_page_url); + + // Update translatable fields. + $edit = array( + 'config_names[config_translation_test.content][content][translation][value]' => 'Hello World - FR', + ); + + // Save language specific version of form. + $this->drupalPostForm($translation_page_url, $edit, t('Save translation')); + + // Get translation and check we've got the right value. + $config_parsed = $file_storage->read('language.config.fr.config_translation_test.content'); + $expected = array( + 'content' => array( + 'value' => 'Hello World - FR', + 'format' => 'plain_text', + ), + ); + $this->assertEqual($expected, $config_parsed); + } + + /** * Gets translation from locale storage. * * @param $config_name diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config/config_translation_test.content.yml b/core/modules/config_translation/tests/modules/config_translation_test/config/config_translation_test.content.yml new file mode 100644 index 0000000..55aa430 --- /dev/null +++ b/core/modules/config_translation/tests/modules/config_translation_test/config/config_translation_test.content.yml @@ -0,0 +1,6 @@ +id: test +label: 'Test' +langcode: en +content: + value: "Hello World" + format: plain_text diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config/schema/config_translation_test.schema.yml b/core/modules/config_translation/tests/modules/config_translation_test/config/schema/config_translation_test.schema.yml new file mode 100644 index 0000000..cea0fa9 --- /dev/null +++ b/core/modules/config_translation/tests/modules/config_translation_test/config/schema/config_translation_test.schema.yml @@ -0,0 +1,18 @@ +# Schema for the configuration files of the Configuration translation test module. + +config_translation_test.content: + type: mapping + label: 'Content' + mapping: + id: + type: string + label: 'Category identifier' + label: + type: label + label: 'Label' + langcode: + type: string + label: 'Default language' + content: + type: text_with_format + label: 'Content' \ No newline at end of file diff --git a/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.config_translation.yml b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.config_translation.yml new file mode 100644 index 0000000..070245c --- /dev/null +++ b/core/modules/config_translation/tests/modules/config_translation_test/config_translation_test.config_translation.yml @@ -0,0 +1,6 @@ +# Attach to file settings for testing. The base route does not matter. +system.file_system_settings: + title: 'Test config translation' + base_route_name: system.file_system_settings + names: + - config_translation_test.content diff --git a/core/modules/config_translation/tests/themes/config_translation_test_theme/config_translation_test_theme.config_translation.yml b/core/modules/config_translation/tests/themes/config_translation_test_theme/config_translation_test_theme.config_translation.yml index 7c8cdd8..5d84070 100644 --- a/core/modules/config_translation/tests/themes/config_translation_test_theme/config_translation_test_theme.config_translation.yml +++ b/core/modules/config_translation/tests/themes/config_translation_test_theme/config_translation_test_theme.config_translation.yml @@ -1,3 +1,4 @@ +# Attach to performance settings for testing. The base route does not matter. system.performance_settings: title: 'Theme translation test' base_route_name: system.performance_settings