diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 1fcf266..caf46a2 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -44,6 +44,8 @@ * multiple entity forms when the forms are similar. Defaults to * Drupal\Core\Entity\EntityFormController. * - label: The human-readable name of the type. + * - bundle_label: The human-readable name of the entity bundles, e.g. + * Vocabulary. * - label_callback: (optional) A function taking an entity and optional * langcode argument, and returning the label of the entity. If langcode is * omitted, the entity's default language is used. diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php index e0bbfd9..96b436e 100644 --- a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php +++ b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php @@ -18,6 +18,7 @@ * @Plugin( * id = "comment", * label = @Translation("Comment"), + * bundle_label = @Translation("Content type"), * module = "comment", * controller_class = "Drupal\comment\CommentStorageController", * render_controller_class = "Drupal\comment\CommentRenderController", diff --git a/core/modules/language/language.admin.css b/core/modules/language/language.admin.css new file mode 100644 index 0000000..2af92d7 --- /dev/null +++ b/core/modules/language/language.admin.css @@ -0,0 +1,16 @@ +/** + * @file + * Styles for the content language administration page. + */ + +#language-content-settings-form table .bundle { + width: 25%; +} + +#language-content-settings-form table td.bundle { + font-weight: bold; +} + +#language-content-settings-form table .operations { + width: 75%; +} diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc index 14bbeb7..11c6e62 100644 --- a/core/modules/language/language.admin.inc +++ b/core/modules/language/language.admin.inc @@ -1027,3 +1027,171 @@ function language_negotiation_configure_browser_delete_form_submit($form, &$form } $form_state['redirect'] = 'admin/config/regional/language/detection/browser'; } + +/** + * Returns the content language settings form. + */ +function language_content_settings_page() { + return drupal_get_form('language_content_settings_form', language_entity_supported()); +} + +/** + * Form constructor for the content language settings form. + * + * @param array $supported + * Entity types with language support. + * + * @see language_content_settings_form_submit() + * + * @ingroup forms + */ +function language_content_settings_form(array $form, array $form_state, array $supported) { + $entity_info = entity_get_info(); + $labels = array(); + $default = array(); + + foreach ($supported as $entity_type) { + $labels[$entity_type] = isset($entity_info[$entity_type]['label']) ? $entity_info[$entity_type]['label'] : $entity_type; + $default[$entity_type] = FALSE; + + // Check whether we have any custom setting. + foreach (entity_get_bundles($entity_type) as $bundle) { + $conf = language_get_default_configuration($entity_type, $bundle); + if (empty($conf['language_hidden']) || $conf['langcode'] != 'site_default') { + $default[$entity_type] = $entity_type; + } + $language_configuration[$entity_type][$bundle] = $conf; + } + } + + $path = drupal_get_path('module', 'language'); + $form = array( + '#labels' => $labels, + '#attached' => array( + 'css' => array($path . '/language.admin.css'), + ), + ); + + $form['entity_types'] = array( + '#title' => t('Custom language settings'), + '#type' => 'checkboxes', + '#options' => $labels, + '#default_value' => $default, + ); + + $form['settings'] = array('#tree' => TRUE); + + foreach ($supported as $entity_type) { + $info = $entity_info[$entity_type]; + + $form['settings'][$entity_type] = array( + '#title' => $labels[$entity_type], + '#type' => 'container', + '#theme' => 'language_content_settings_table', + '#bundle_label' => isset($info['bundle_label']) ? $info['bundle_label'] : $labels[$entity_type], + '#states' => array( + 'visible' => array( + ':input[name="entity_types[' . $entity_type . ']"]' => array('checked' => TRUE), + ), + ), + ); + + foreach (entity_get_bundles($entity_type) as $bundle) { + $form['settings'][$entity_type][$bundle]['settings'] = array( + '#type' => 'item', + '#label' => isset($info['bundles'][$bundle]) ? $info['bundles'][$bundle]['label'] : $labels[$entity_type], + 'language' => array( + '#type' => 'language_configuration', + '#entity_information' => array( + 'entity_type' => $entity_type, + 'bundle' => $bundle, + ), + '#default_value' => $language_configuration[$entity_type][$bundle], + ), + ); + } + } + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + return $form; +} + +/** + * Implements hook_preprocess_HOOK() for theme_language_content_settings_table(). + */ +function template_preprocess_language_content_settings_table(&$variables) { + // Add a render element representing the bundle language settings table. + $element = $variables['element']; + + $header = array( + array( + 'data' => $element['#bundle_label'], + 'class' => array('bundle'), + ), + array( + 'data' => t('Configuration'), + 'class' => array('operations'), + ), + ); + + $rows = array(); + foreach (element_children($element) as $bundle) { + $rows[$bundle] = array( + 'data' => array( + array( + 'data' => array( + '#prefix' => '', + '#markup' => check_plain($element[$bundle]['settings']['#label']), + ), + 'class' => array('bundle'), + ), + array( + 'data' => $element[$bundle]['settings'], + 'class' => array('operations'), + ), + ), + 'class' => array('bundle-settings'), + ); + } + + $variables['build'] = array( + '#title' => $element['#title'], + '#header' => $header, + '#rows' => $rows, + '#theme' => 'table', + ); +} + +/** + * Returns HTML for an administration settings table. + * + * @param array $variables + * An associative array containing: + * - build: A render element representing a table of bundle content language + * settings for a particular entity type. + * + * @ingroup themable + */ +function theme_language_content_settings_table($variables) { + return '

' . $variables['build']['#title'] . '

' . drupal_render($variables['build']); +} + +/** + * Form submission handler for language_content_settings_form(). + */ +function language_content_settings_form_submit(array $form, array &$form_state) { + $entity_types = $form_state['values']['entity_types']; + $settings = &$form_state['values']['settings']; + foreach ($settings as $entity_type => $entity_settings) { + foreach ($entity_settings as $bundle => $bundle_settings) { + language_save_default_configuration($entity_type, $bundle, $bundle_settings['settings']['language']); + } + } + drupal_set_message(t('Settings successfully updated.')); +} diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 987553d..9ecd1c5 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -52,6 +52,9 @@ function language_help($path, $arg) { return '

' . t('With multiple languages enabled, registered users can select their preferred language and authors can assign a specific language to content.') . '

'; } break; + + case 'admin/config/regional/content-language': + return t('Change language settings for content types, taxonomy vocabularies, user profiles, or any other supported element on your site. By default, language settings hide the language selector and the language is the site\'s default language.'); } } @@ -150,6 +153,15 @@ function language_menu() { 'type' => MENU_VISIBLE_IN_BREADCRUMB, ); + // Content language settings. + $items['admin/config/regional/content-language'] = array( + 'title' => 'Content language settings', + 'description' => 'Configure content language support for any multilingual element.', + 'page callback' => 'language_content_settings_page', + 'access arguments' => array('administer languages'), + 'file' => 'language.admin.inc', + ); + return $items; } @@ -187,10 +199,34 @@ function language_theme() { 'render element' => 'form', 'file' => 'language.admin.inc', ), + 'language_content_settings_table' => array( + 'render element' => 'element', + 'file' => 'language.admin.inc', + ), ); } /** + * Returns a list of supported entity types. + * + * @return array + * An array of entity type names. + */ +function language_entity_supported() { + $supported = array(); + foreach (entity_get_info() as $entity_type => $info) { + // @todo Revisit this once all core entities are migrated to the Entity + // Field API and language support for configuration entities has been + // sorted out. + $entity_class = new ReflectionClass($info['class']); + if ($info['fieldable'] && !$entity_class->implementsInterface('Drupal\Core\Config\Entity\ConfigEntityInterface')) { + $supported[$entity_type] = $entity_type; + } + } + return $supported; +} + +/** * Implements hook_element_info_alter(). */ function language_element_info_alter(&$type) { diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php index 47de776..216cfc9 100644 --- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php +++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php @@ -18,6 +18,7 @@ * @Plugin( * id = "node", * label = @Translation("Content"), + * bundle_label = @Translation("Content type"), * module = "node", * controller_class = "Drupal\node\NodeStorageController", * render_controller_class = "Drupal\node\NodeRenderController", diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php index 6b438ba..d669336 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php @@ -18,6 +18,7 @@ * @Plugin( * id = "taxonomy_term", * label = @Translation("Taxonomy term"), + * bundle_label = @Translation("Vocabulary"), * module = "taxonomy", * controller_class = "Drupal\taxonomy\TermStorageController", * render_controller_class = "Drupal\taxonomy\TermRenderController", diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSettingsTest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSettingsTest.php new file mode 100644 index 0000000..2157b98 --- /dev/null +++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSettingsTest.php @@ -0,0 +1,122 @@ + 'Entity Translation settings', + 'description' => 'Tests the entity translation settings UI.', + 'group' => 'Entity Translation UI', + ); + } + + function setUp() { + parent::setUp(); + + // Set up two content types to test field instances shared between different + // bundles. + $this->drupalCreateContentType(array('type' => 'article')); + $this->drupalCreateContentType(array('type' => 'page')); + + $admin_user = $this->drupalCreateUser(array('administer languages', 'administer entity translation')); + $this->drupalLogin($admin_user); + } + + /** + * Tests that the settings UI works as expected. + */ + function testSettingsUI() { + // Test that the translation settings are ignored if the bundle is marked + // translatable but the entity type is not. + $edit = array('settings[comment][comment_node_article][translatable]' => TRUE); + $this->assertSettings('comment', NULL, FALSE, $edit); + + // Test that the translation settings are ignored if only a field is marked + // as translatable and not the related entity type and bundle. + $edit = array('settings[comment][comment_node_article][fields][comment_body]' => TRUE); + $this->assertSettings('comment', NULL, FALSE, $edit); + + // Test that the translation settings are not stored if an entity type and + // bundle are marked as translatable but no field is. + $edit = array( + 'entity_types[comment]' => TRUE, + 'settings[comment][comment_node_article][translatable]' => TRUE, + ); + $this->assertSettings('comment', 'comment_node_article', FALSE, $edit); + $xpath_err = '//div[@id="messages"]//div[contains(@class, "error")]'; + $this->assertTrue($this->xpath($xpath_err), 'Enabling translation only for entity bundles generates a form error.'); + + // Test that the translation settings are not stored if a non-configurable + // language is set as default and the language selector is hidden. + $edit = array( + 'entity_types[comment]' => TRUE, + 'settings[comment][comment_node_article][settings][language][langcode]' => LANGUAGE_NOT_SPECIFIED, + 'settings[comment][comment_node_article][settings][language][language_hidden]' => TRUE, + 'settings[comment][comment_node_article][translatable]' => TRUE, + 'settings[comment][comment_node_article][fields][comment_body]' => TRUE, + ); + $this->assertSettings('comment', 'comment_node_article', FALSE, $edit); + $this->assertTrue($this->xpath($xpath_err), 'Enabling translation with a fixed non-configurable language generates a form error.'); + + // Test that a field shared among different bundles can be enabled without + // needing to make all the related bundles translatable. + $edit = array( + 'entity_types[comment]' => TRUE, + 'settings[comment][comment_node_article][settings][language][langcode]' => 'current_interface', + 'settings[comment][comment_node_article][settings][language][language_hidden]' => FALSE, + 'settings[comment][comment_node_article][translatable]' => TRUE, + 'settings[comment][comment_node_article][fields][comment_body]' => TRUE, + ); + $this->assertSettings('comment', 'comment_node_article', TRUE, $edit); + $field = field_info_field('comment_body'); + $this->assertTrue($field['translatable'], 'Comment body is translatable.'); + + // Test that language settings are correctly stored. + $language_configuration = language_get_default_configuration('comment', 'comment_node_article'); + $this->assertEqual($language_configuration['langcode'], 'current_interface', 'The default language for article comments is set to the current interface language.'); + $this->assertFalse($language_configuration['language_hidden'], 'The language selector for article comments is shown.'); + } + + /** + * Asserts that translatability has the expected value for the given bundle. + * + * @param string $entity_type + * The entity type for which to check translatibility. + * @param string $bundle + * The bundle for which to check translatibility. + * @param boolean $enabled + * TRUE if translatibility should be enabled, FALSE otherwise. + * @param array $edit + * An array of values to submit to the entity translation settings page. + * + * @return boolean + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertSettings($entity_type, $bundle, $enabled, $edit) { + $this->drupalPost('admin/config/regional/content-language', $edit, t('Save')); + $args = array('@entity_type' => $entity_type, '@bundle' => $bundle, '@enabled' => $enabled ? 'enabled' : 'disabled'); + $message = format_string('Translation for entity @entity_type (@bundle) is @enabled.', $args); + drupal_static_reset(); + return $this->assertEqual(translation_entity_enabled($entity_type, $bundle), $enabled, $message); + } + +} diff --git a/core/modules/translation_entity/translation_entity.admin.css b/core/modules/translation_entity/translation_entity.admin.css new file mode 100644 index 0000000..5f03b78 --- /dev/null +++ b/core/modules/translation_entity/translation_entity.admin.css @@ -0,0 +1,29 @@ +/** + * @file + * Styles for the content language administration page. + */ + +#language-content-settings-form table .bundle { + width: 24%; +} + +#language-content-settings-form table td.bundle { + font-weight: bold; +} + +#language-content-settings-form table .field { + width: 24%; + padding-left: 3em; +} + +#language-content-settings-form table .field label { + font-weight: normal; +} + +#language-content-settings-form table .translatable { + width: 1%; +} + +#language-content-settings-form table .operations { + width: 75%; +} diff --git a/core/modules/translation_entity/translation_entity.admin.inc b/core/modules/translation_entity/translation_entity.admin.inc index bacba11..e2bd0ef 100644 --- a/core/modules/translation_entity/translation_entity.admin.inc +++ b/core/modules/translation_entity/translation_entity.admin.inc @@ -8,6 +8,180 @@ use Drupal\Core\Entity\EntityInterface; /** + * Implements hook_form_FORM_ID_alter() for language_content_settings_form(). + * + * @see translation_entity_form_language_content_settings_form_alter() + */ +function _translation_entity_form_language_content_settings_form_alter(array &$form, array &$form_state) { + // Inject into the content language settings the translation settings if the + // user has the required permission. + if (!user_access('administer entity translation')) { + return; + } + + $default = $form['entity_types']['#default_value']; + foreach ($default as $entity_type => $enabled) { + $default[$entity_type] = $enabled || translation_entity_enabled($entity_type) ? $entity_type : FALSE; + } + $form['entity_types']['#default_value'] = $default; + + $path = drupal_get_path('module', 'translation_entity'); + $form['#attached']['css'][] = $path . '/translation_entity.admin.css'; + $form['#attached']['js'][] = $path . '/translation_entity.admin.js'; + + foreach ($form['#labels'] as $entity_type => $label) { + foreach (entity_get_bundles($entity_type) as $bundle) { + $form['settings'][$entity_type][$bundle]['translatable'] = array( + '#type' => 'checkbox', + '#default_value' => translation_entity_enabled($entity_type, $bundle), + ); + + // Here we do not want the widget to be altered and hold also the "Enable + // translation" checkbox, which would be redundant. Hence we add this key + // to be able to skip alterations. + $form['settings'][$entity_type][$bundle]['settings']['language']['#translation_entity_skip_alter'] = TRUE; + + // @todo Exploit field definitions once all core entities and field types + // are migrated to the Entity Field API. + foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) { + $field = field_info_field($field_name); + $form['settings'][$entity_type][$bundle]['fields'][$field_name] = array( + '#label' => $instance['label'], + '#type' => 'checkbox', + '#default_value' => $field['translatable'], + ); + } + } + } + + $form['#validate'][] = 'translation_entity_form_language_content_settings_validate'; + $form['#submit'][] = 'translation_entity_form_language_content_settings_submit'; +} + +/** + * Form validation handler for translation_entity_admin_settings_form(). + * + * @see translation_entity_admin_settings_form_submit() + */ +function translation_entity_form_language_content_settings_validate(array $form, array &$form_state) { + $settings = &$form_state['values']['settings']; + foreach ($settings as $entity_type => $entity_settings) { + foreach ($entity_settings as $bundle => $bundle_settings) { + if (!empty($bundle_settings['translatable'])) { + $name = "settings][$entity_type][$bundle][translatable"; + + $translatable_fields = isset($settings[$entity_type][$bundle]['fields']) ? array_filter($settings[$entity_type][$bundle]['fields']) : FALSE; + if (empty($translatable_fields)) { + $t_args = array('%bundle' => $form['settings'][$entity_type][$bundle]['settings']['#label']); + form_set_error($name, t('At least one field needs to be translatable to enable %bundle for translation.', $t_args)); + } + + $values = $bundle_settings['settings']['language']; + if (language_is_locked($values['langcode']) && $values['language_hidden']) { + foreach (language_list(LANGUAGE_LOCKED) as $language) { + $locked_languages[] = $language->name; + } + form_set_error($name, t('Translation is not supported if language is always one of: @locked_languages', array('@locked_languages' => implode(', ', $locked_languages)))); + } + } + } + } +} + +/** + * Form submission handler for translation_entity_admin_settings_form(). + * + * @see translation_entity_admin_settings_form_validate() + */ +function translation_entity_form_language_content_settings_submit(array $form, array &$form_state) { + $entity_types = $form_state['values']['entity_types']; + $settings = &$form_state['values']['settings']; + + // If an entity type is not translatable all its bundles and fields must be + // marked as non-translatable. Similarly, if a bundle is made non-translatable + // all of its fields will be not translatable. + foreach ($settings as $entity_type => &$entity_settings) { + foreach ($entity_settings as $bundle => &$bundle_settings) { + $bundle_settings['translatable'] = $bundle_settings['translatable'] && $entity_types[$entity_type]; + if (!empty($bundle_settings['fields'])) { + foreach ($bundle_settings['fields'] as $field_name => $translatable) { + $bundle_settings['fields'][$field_name] = $translatable && $bundle_settings['translatable']; + } + } + } + } + + _translation_entity_update_field_translatability($settings); + drupal_set_message(t('Settings successfully updated.')); +} + +/** + * Stores entity translation settings. + * + * @param array $settings + * An associative array of settings keyed by entity type and bundle. At bundle + * level the following keys are available: + * - translatable: The bundle translatability status, which is a bool. + * - settings: An array of language configuration settings as defined by + * language_save_default_configuration(). + * - fields: An associative array with field names as keys and a boolean as + * value, indicating field translatability. + * + * @todo Remove this migration entirely once the Field API is converted to the + * Entity Field API. + */ +function _translation_entity_update_field_translatability($settings) { + $fields = array(); + foreach ($settings as $entity_type => $entity_settings) { + foreach ($entity_settings as $bundle => $bundle_settings) { + // Collapse field settings since here we have per instance settings, but + // translatability has per-field scope. We assume that all the field + // instances have the same value. + if (!empty($bundle_settings['fields'])) { + foreach ($bundle_settings['fields'] as $field_name => $translatable) { + // If a field is enabled for translation for at least one instance we + // need to mark it as translatable. + $fields[$field_name] = $translatable || !empty($fields[$field_name]); + } + } + // @todo Store non-configurable field settings to be able to alter their + // definition afterwards. + } + } + + $operations = array(); + foreach ($fields as $field_name => $translatable) { + $field = field_info_field($field_name); + if ($field['translatable'] != $translatable) { + // If a field is untranslatable, it can have no data except under + // LANGUAGE_NOT_SPECIFIED. Thus we need a field to be translatable before + // we convert data to the entity language. Conversely we need to switch + // data back to LANGUAGE_NOT_SPECIFIED before making a field + // untranslatable lest we lose information. + $field_operations = array( + array('translation_entity_translatable_switch', array($translatable, $field_name)), + ); + if (field_has_data($field)) { + $field_operations[] = array('translation_entity_translatable_batch', array($translatable, $field_name)); + $field_operations = $translatable ? $field_operations : array_reverse($field_operations); + } + $operations = array_merge($operations, $field_operations); + } + } + + // As last operation store the submitted settings. + $operations[] = array('translation_entity_save_settings', array($settings)); + + $batch = array( + 'title' => t('Updating translatability for the selected fields'), + 'operations' => $operations, + 'finished' => 'translation_entity_translatable_batch_done', + 'file' => drupal_get_path('module', 'translation_entity') . '/translation_entity.admin.inc', + ); + batch_set($batch); +} + +/** * Form constructor for the confirmation of translatability switching. */ function translation_entity_translatable_form(array $form, array &$form_state, $field_name) { @@ -65,9 +239,9 @@ function translation_entity_translatable_form_submit(array $form, array $form_st } // If a field is untranslatable, it can have no data except under - // LANGUAGE_NOT_SPECIFIED. Thus we need a field to be translatable before we convert - // data to the entity language. Conversely we need to switch data back to - // LANGUAGE_NOT_SPECIFIED before making a field untranslatable lest we lose + // LANGUAGE_NOT_SPECIFIED. Thus we need a field to be translatable before we + // convert data to the entity language. Conversely we need to switch data back + // to LANGUAGE_NOT_SPECIFIED before making a field untranslatable lest we lose // information. $operations = array( array('translation_entity_translatable_batch', array(!$translatable, $field_name)), @@ -101,13 +275,10 @@ function translation_entity_translatable_form_submit(array $form, array $form_st */ function translation_entity_translatable_switch($translatable, $field_name) { $field = field_info_field($field_name); - - if ($field['translatable'] === $translatable) { - return; + if ($field['translatable'] !== $translatable) { + $field['translatable'] = $translatable; + field_update_field($field); } - - $field['translatable'] = $translatable; - field_update_field($field); } /** @@ -120,9 +291,12 @@ function translation_entity_translatable_switch($translatable, $field_name) { * Field machine name. */ function translation_entity_translatable_batch($translatable, $field_name, &$context) { - $entity_types = array(); + $field = field_info_field($field_name); + $column = isset($field['columns']['value']) ? 'value' : key($field['columns']); + $query_field = "$field_name.$column"; // Determine the entity types to act on. + $entity_types = array(); foreach (field_info_instances() as $entity_type => $info) { foreach ($info as $bundle => $instances) { foreach ($instances as $instance_field_name => $instance) { @@ -142,7 +316,7 @@ function translation_entity_translatable_batch($translatable, $field_name, &$con // How many entities will need processing? $query = entity_query($entity_type); $count = $query - ->exists($field_name) + ->exists($query_field) ->count() ->execute(); @@ -167,7 +341,7 @@ function translation_entity_translatable_batch($translatable, $field_name, &$con $offset = $context['sandbox']['progress_entity_type'][$entity_type]; $query = entity_query($entity_type); $result = $query - ->exists($field_name) + ->exists($query_field) ->sort($info['entity_keys']['id']) ->range($offset, 10) ->execute(); diff --git a/core/modules/translation_entity/translation_entity.admin.js b/core/modules/translation_entity/translation_entity.admin.js new file mode 100644 index 0000000..97c3679 --- /dev/null +++ b/core/modules/translation_entity/translation_entity.admin.js @@ -0,0 +1,36 @@ +(function ($) { + +"use strict"; + +/** + * Makes field translatability inherit bundle translatability. + */ +Drupal.behaviors.translationEntity = { + attach: function (context) { + // Initially hide all field rows for non translatable bundles. + var $input = $('table .bundle-settings .translatable :input', context); + $input.filter(':not(:checked)').once('translation-entity-admin-hide', function() { + $(this).closest('.bundle-settings').nextUntil('.bundle-settings').hide(); + }); + + // When a bundle is made translatable all of its field instances should + // inherit this setting. Instead when it is made non translatable its field + // instances are hidden, since their translatability no longer matters. + $input.once('translation-entity-admin-bind', function() { + var $bundleTranslatable = $(this).click(function() { + var $bundleSettings = $bundleTranslatable.closest('.bundle-settings'); + var $fieldSettings = $bundleSettings.nextUntil('.bundle-settings'); + if ($bundleTranslatable.is(':checked')) { + $bundleSettings.find('.operations :input[name$="[language_hidden]"]').attr('checked', false); + $fieldSettings.find('.translatable :input').attr('checked', true); + $fieldSettings.show(); + } + else { + $fieldSettings.hide(); + } + }); + }); + } +}; + +})(jQuery); diff --git a/core/modules/translation_entity/translation_entity.info b/core/modules/translation_entity/translation_entity.info index 3d2b90b..8a7ef0c 100644 --- a/core/modules/translation_entity/translation_entity.info +++ b/core/modules/translation_entity/translation_entity.info @@ -4,3 +4,4 @@ dependencies[] = language package = Multilingual version = VERSION core = 8.x +configure = admin/config/regional/content-language diff --git a/core/modules/translation_entity/translation_entity.install b/core/modules/translation_entity/translation_entity.install index e66e544..e7cbb65 100644 --- a/core/modules/translation_entity/translation_entity.install +++ b/core/modules/translation_entity/translation_entity.install @@ -66,7 +66,8 @@ function translation_entity_install() { function translation_entity_enable() { $t_args = array( '!language_url' => url('admin/config/regional/language'), + '!settings_url' => url('admin/config/regional/content-language'), ); - $message = t('You just added content translation capabilities to your site. To exploit them be sure to enable at least two languages and enable translation for content types, taxonomy vocabularies, accounts and any other element whose content you wish to translate.', $t_args); + $message = t('Content translation has been enabled. To use content translation, enable at least two languages and enable translation for content types, taxonomy vocabularies, accounts, or any other element you wish to translate.', $t_args); drupal_set_message($message, 'warning'); } diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module index 91ee94c..9298afe 100644 --- a/core/modules/translation_entity/translation_entity.module +++ b/core/modules/translation_entity/translation_entity.module @@ -23,12 +23,7 @@ function translation_entity_help($path, $arg) { $output .= '
'; $output .= '
' . t('Enabling translation') . '
'; $output .= '

' . t('Before you can translate content, there must be at least two non-system languages added on the languages administration page.', array('!url' => url('admin/config/regional/language'))) . '

'; - $output .= '

' . t('After adding languages, enable translation for any content you wish to translate:') . '

'; - $output .= ''; - $output .= '

' . t('Finally, under the Manage fields tab, edit each field you wish to be translatable, and enable translation under Global settings.') . '

'; + $output .= '

' . t('After adding languages, configure translation.', array('!url' => url('admin/config/regional/content-language'))) . '

'; $output .= '
' . t('Translating content') . '
'; $output .= '
' . t('After enabling translation you can create a new piece of content, or edit existing content and assign it a language. Then, you will see a Translations tab or link that will gives an overview of the translation status for the current content. From there, you can add translations and edit or delete existing translations. This process is similar for every translatable element on your site, such as taxonomy terms, comments or user accounts.') . '
'; $output .= '
' . t('Changing source language') . '
'; @@ -39,6 +34,13 @@ function translation_entity_help($path, $arg) { $output .= '
' . t('The Entity Translation module makes a basic set of permissions available. Additional permissions are made available after translation is enabled for each translatable element.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-translation_entity')))) . '
'; $output .= '
'; return $output; + + case 'admin/config/regional/content-language': + $output = ''; + if (!language_multilingual()) { + $output .= '
' . t('Before you can translate content, there must be at least two non-system languages added on the languages administration page.', array('!url' => url('admin/config/regional/language'))); + } + return $output; } } @@ -163,7 +165,7 @@ function translation_entity_menu() { 'description' => 'Confirm page for changing field translatability.', 'page callback' => 'drupal_get_form', 'page arguments' => array('translation_entity_translatable_form', 5), - 'access arguments' => array('toggle field translatability'), + 'access arguments' => array('administer entity translation'), 'file' => 'translation_entity.admin.inc', ); @@ -395,9 +397,9 @@ function translation_entity_permission() { 'title' => t('Edit original values'), 'description' => t('Access the entity form in the original language.'), ), - 'toggle field translatability' => array( - 'title' => t('Toggle field translatability'), - 'description' => t('Toggle translatability of fields performing a bulk update.'), + 'administer entity translation' => array( + 'title' => t('Administer entity translation'), + 'description' => t('Configure translatability of entities and fields.'), ), 'translate any entity' => array( 'title' => t('Translate any entity'), @@ -594,7 +596,7 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form '#title' => $link_title, '#href' => $path, '#options' => array('query' => drupal_get_destination()), - '#access' => user_access('toggle field translatability'), + '#access' => user_access('administer entity translation'), ), ); } @@ -653,19 +655,20 @@ function translation_entity_enable_widget($entity_type, $bundle, array &$form, a * Processed language configuration element. */ function translation_entity_language_configuration_element_process(array $element, array &$form_state, array &$form) { - $form_state['translation_entity']['key'] = $element['#name']; - $context = $form_state['language'][$element['#name']]; - - $element['translation_entity'] = array( - '#type' => 'checkbox', - '#title' => t('Enable translation'), - '#default_value' => translation_entity_enabled($context['entity_type'], $context['bundle']), - '#element_validate' => array('translation_entity_language_configuration_element_validate'), - '#prefix' => '', - ); + if (empty($element['#translation_entity_skip_alter']) && user_access('administer entity translation')) { + $form_state['translation_entity']['key'] = $element['#name']; + $context = $form_state['language'][$element['#name']]; - $form['#submit'][] = 'translation_entity_language_configuration_element_submit'; + $element['translation_entity'] = array( + '#type' => 'checkbox', + '#title' => t('Enable translation'), + '#default_value' => translation_entity_enabled($context['entity_type'], $context['bundle']), + '#element_validate' => array('translation_entity_language_configuration_element_validate'), + '#prefix' => '', + ); + $form['#submit'][] = 'translation_entity_language_configuration_element_submit'; + } return $element; } @@ -710,3 +713,101 @@ function translation_entity_language_configuration_element_submit(array $form, a menu_router_rebuild(); } } + +/** + * Implements hook_form_FORM_ID_alter() for language_content_settings_form(). + */ +function translation_entity_form_language_content_settings_form_alter(array &$form, array &$form_state) { + module_load_include('inc', 'translation_entity', 'translation_entity.admin'); + _translation_entity_form_language_content_settings_form_alter($form, $form_state); +} + +/** + * Stores entity translation settings. + * + * @param array $settings + * An associative array of settings keyed by entity type and bundle. At bundle + * level the following keys are available: + * - translatable: The bundle translatability status, which is a bool. + * - settings: An array of language configuration settings as defined by + * language_save_default_configuration(). + * - fields: An associative array with field names as keys and a boolean as + * value, indicating field translatability. + */ +function translation_entity_save_settings($settings) { + foreach ($settings as $entity_type => $entity_settings) { + foreach ($entity_settings as $bundle => $bundle_settings) { + translation_entity_set_config($entity_type, $bundle, 'enabled', $bundle_settings['translatable']); + } + } + // Ensure entity and menu router information are correctly rebuilt. + entity_info_cache_clear(); + menu_router_rebuild(); +} + +/** + * Implements hook_preprocess_HOOK() for theme_language_content_settings_table(). + */ +function translation_entity_preprocess_language_content_settings_table(&$variables) { + // Alter the 'build' variable injecting the translation settings if the user + // has the required permission. + if (!user_access('administer entity translation')) { + return; + } + + $element = $variables['element']; + $build = &$variables['build']; + + array_unshift($build['#header'], array('data' => t('Translatable'), 'class' => array('translatable'))); + $rows = array(); + + foreach (element_children($element) as $bundle) { + $field_names = !empty($element[$bundle]['fields']) ? element_children($element[$bundle]['fields']) : array(); + $checkbox_id = $element[$bundle]['translatable']['#id']; + $rows[$bundle] = $build['#rows'][$bundle]; + + $translatable = array( + 'data' => $element[$bundle]['translatable'], + 'class' => array('translatable'), + ); + array_unshift($rows[$bundle]['data'], $translatable); + + $rows[$bundle]['data'][1]['data']['#prefix'] = '