diff --git a/core/modules/locale/lib/Drupal/locale/Form/LocaleForm.php b/core/modules/locale/lib/Drupal/locale/Form/LocaleForm.php index c06a4aa..243a3c3 100644 --- a/core/modules/locale/lib/Drupal/locale/Form/LocaleForm.php +++ b/core/modules/locale/lib/Drupal/locale/Form/LocaleForm.php @@ -30,15 +30,4 @@ public function export() { module_load_include('bulk.inc', 'locale'); return drupal_get_form('locale_translate_export_form'); } - - /** - * Wraps locale_translation_status_form(). - * - * @todo Remove locale_translation_status_form(). - */ - public function status() { - module_load_include('pages.inc', 'locale'); - return drupal_get_form('locale_translation_status_form'); - } - } diff --git a/core/modules/locale/lib/Drupal/locale/Form/TranslationStatusForm.php b/core/modules/locale/lib/Drupal/locale/Form/TranslationStatusForm.php new file mode 100644 index 0000000..3bb4385 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Form/TranslationStatusForm.php @@ -0,0 +1,298 @@ +get('module_handler'), + $container->get('state') + ); + } + + /** + * Constructs a TranslationStatusForm object. + * + * @param ModuleHandlerInterface $module_handler + * A module handler. + * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state + * The state key/value store interface, gives access to state based config settings. + */ + public function __construct(ModuleHandlerInterface $module_handler, KeyValueStoreInterface $state) { + $this->moduleHandler = $module_handler; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public function getFormID() { + return 'locale_translation_status_form'; + } + + /** + * Form builder for displaying the current translation status. + * + * @ingroup forms + */ + public function buildForm(array $form, array &$form_state) { + $this->moduleHandler->loadInclude('locale', 'translation.inc'); + $this->moduleHandler->loadInclude('locale', 'compare.inc'); + + $languages = locale_translatable_language_list(); + $status = $this->state->get('locale.translation_status'); + $options = array(); + $languages_update = array(); + $languages_not_found = array(); + $projects_update = array(); + // Prepare information about projects which have available translation + // updates. + if ($languages && $status) { + $updates = $this->prepareUpdateData($status); + + // Build data options for the select table. + foreach ($updates as $langcode => $update) { + $title = String::checkPlain($languages[$langcode]->name); + $locale_translation_update_info = array('#theme' => 'locale_translation_update_info'); + foreach (array('updates', 'not_found') as $update_status) { + if (isset($update[$update_status])) { + $locale_translation_update_info['#' . $update_status] = $update[$update_status]; + } + } + $options[$langcode] = array( + 'title' => array( + 'class' => array('label'), + 'data' => array( + '#title' => $title, + '#markup' => $title + ), + ), + 'status' => array( + 'class' => array('description', 'expand', 'priority-low'), + 'data' => drupal_render($locale_translation_update_info), + ), + ); + if (!empty($update['not_found'])) { + $languages_not_found[$langcode] = $langcode; + } + else if (!empty($update['updates'])) { + $languages_update[$langcode] = $langcode; + } + } + // Sort the table data on language name. + uasort($options, function ($a, $b) { + return strcasecmp($a['title']['data']['#title'], $b['title']['data']['#title']); + }); + $languages_not_found = array_diff($languages_not_found, $languages_update); + } + + $last_checked = $this->state->get('locale.translation_last_checked'); + $form['last_checked'] = array( + '#theme' => 'locale_translation_last_check', + '#last' => $last_checked, + ); + + $header = array( + 'title' => array( + 'data' => $this->t('Language'), + 'class' => array('title'), + ), + 'status' => array( + 'data' => $this->t('Status'), + 'class' => array('status', 'priority-low'), + ), + ); + + if (!$languages) { + $empty = $this->t('No translatable languages available. Add a language first.', array('@add_language' => $this->urlGenerator()->generateFromPath('admin/config/regional/language'))); + } + elseif ($status) { + $empty = $this->t('All translations up to date.'); + } + else { + $empty = $this->t('No translation status available. Check manually.', array('@check' => url('admin/reports/translations/check'))); + } + + // The projects which require an update. Used by the _submit callback. + $form['projects_update'] = array( + '#type' => 'value', + '#value' => $projects_update, + ); + + $form['langcodes'] = array( + '#type' => 'tableselect', + '#header' => $header, + '#options' => $options, + '#default_value' => $languages_update, + '#empty' => $empty, + '#js_select' => TRUE, + '#multiple' => TRUE, + '#required' => TRUE, + '#not_found' => $languages_not_found, + '#after_build' => array('locale_translation_language_table'), + ); + + $form['#attached']['library'][] = array('locale', 'drupal.locale.admin'); + $form['#attached']['css'] = array(drupal_get_path('module', 'locale') . '/css/locale.admin.css'); + + $form['actions'] = array('#type' => 'actions'); + if ($languages_update) { + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Update translations'), + ); + } + + return $form; + } + + /** + * Prepare information about projects with available translation updates. + * + * @param array $status + * Translation update status as an array keyed by Project ID and langcode. + * + * @return array + * Translation update status as an array keyed by language code and + * translation update status. + */ + protected function prepareUpdateData(array $status) { + $updates = array(); + + // @todo Calling locale_translation_build_projects() is an expensive way to + // get a module name. In follow-up issue http://drupal.org/node/1842362 + // the project name will be stored to display use, like here. + $project_data = locale_translation_build_projects(); + + foreach ($status as $project_id => $project) { + foreach ($project as $langcode => $project_info) { + // No translation file found for this project-language combination. + if (!isset($project_info->type)) { + $updates[$langcode]['not_found'][] = array( + 'name' => $project_info->name == 'drupal' ? $this->t('Drupal core') : $project_data[$project_info->name]->info['name'], + 'version' => $project_info->version, + 'info' => $this->createInfoString($project_info), + ); + } + // Translation update found for this project-language combination. + elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE ) { + $local = isset($project_info->files[LOCALE_TRANSLATION_LOCAL]) ? $project_info->files[LOCALE_TRANSLATION_LOCAL] : NULL; + $remote = isset($project_info->files[LOCALE_TRANSLATION_REMOTE]) ? $project_info->files[LOCALE_TRANSLATION_REMOTE] : NULL; + $recent = _locale_translation_source_compare($local, $remote) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $remote : $local; + $updates[$langcode]['updates'][] = array( + 'name' => $project_data[$project_info->name]->info['name'], + 'version' => $project_info->version, + 'timestamp' => $recent->timestamp, + ); + } + } + } + return $updates; + } + + /** + * Provides debug info for projects in case translation files are not found. + * + * Translations files are being fetched either from Drupal translation server + * and local files or only from the local filesystem depending on the + * "Translation source" setting at admin/config/regional/translate/settings. + * This method will produce debug information including the respective path(s) + * based on this setting. + * + * Translations for development versions are never fetched, so the debug info + * for that is a fixed message. + * + * @param array $project_info + * An array which is the project information of the source. + * + * @return string + * The string which contains debug information. + */ + protected function createInfoString($project_info) { + $remote_path = isset($project_info->files['remote']->uri) ? $project_info->files['remote']->uri : FALSE; + $local_path = isset($project_info->files['local']->uri) ? $project_info->files['local']->uri : FALSE; + + if (strpos($project_info->version, 'dev') !== FALSE) { + return $this->t('No translation files are provided for development releases.'); + } + if (locale_translation_use_remote_source() && $remote_path && $local_path) { + return $this->t('File not found at %remote_path nor at %local_path', array( + '%remote_path' => $remote_path, + '%local_path' => $local_path, + )); + } + elseif ($local_path) { + return $this->t('File not found at %local_path', array('%local_path' => $local_path)); + } + return $this->t('Translation file location could not be determined.'); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + // Check if a language has been selected. 'tableselect' doesn't. + if (!array_filter($form_state['values']['langcodes'])) { + form_set_error('', $this->t('Select a language to update.')); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + $this->moduleHandler->loadInclude('locale', 'fetch.inc'); + $langcodes = array_filter($form_state['values']['langcodes']); + $projects = array_filter($form_state['values']['projects_update']); + + // Set the translation import options. This determines if existing + // translations will be overwritten by imported strings. + $options = _locale_translation_default_update_options(); + + // If the status was updated recently we can immediately start fetching the + // translation updates. If the status is expired we clear it an run a batch to + // update the status and then fetch the translation updates. + $last_checked = $this->state->get('locale.translation_last_checked'); + if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) { + locale_translation_clear_status(); + $batch = locale_translation_batch_update_build(array(), $langcodes, $options); + batch_set($batch); + } + else { + $batch = locale_translation_batch_fetch_build($projects, $langcodes, $options); + batch_set($batch); + } + } + +} diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 8386be5..e834b1c 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -1442,3 +1442,19 @@ function _locale_rebuild_js($langcode = NULL) { return TRUE; } } +/** + * Form element callback: After build changes to the language update table. + * + * Adds labels to the languages and removes checkboxes from languages from which + * translation files could not be found. + */ +function locale_translation_language_table($form_element) { + // Remove checkboxes of languages without updates. + if ($form_element['#not_found']) { + foreach ($form_element['#not_found'] as $langcode) { + $form_element[$langcode] = array(); + } + } + return $form_element; +} + diff --git a/core/modules/locale/locale.routing.yml b/core/modules/locale/locale.routing.yml index 190ddf7..cf61af9 100644 --- a/core/modules/locale/locale.routing.yml +++ b/core/modules/locale/locale.routing.yml @@ -40,7 +40,7 @@ locale.translate_export: locale.translate_status: path: '/admin/reports/translations' defaults: - _content: '\Drupal\locale\Form\LocaleForm::status' + _form: '\Drupal\locale\Form\TranslationStatusForm' _title: 'Available translation updates' requirements: _permission: 'translate interface'