diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateInterfaceTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateInterfaceTest.php new file mode 100644 index 0000000..c30c707 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateInterfaceTest.php @@ -0,0 +1,107 @@ + 'Update translations user interface', + 'description' => 'Tests for the user interface of project interface translations.', + 'group' => 'Locale', + ); + } + + /** + * Setup the test environment. + * + * We use German as default test language. Due to hardcoded configurations in + * the locale_test module, the language can not be chosen randomly. + */ + function setUp() { + parent::setUp(); + $admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface')); + $this->drupalLogin($admin_user); + } + + /** + * Tests the user interfaces of the interface translation update system. + * + * Testing the Available updates summary on the side wide status page and the + * Avaiable translation updates page. + */ + function testInterface() { + // No language added. + // Check status page and Available translation updates page. + $this->drupalGet('admin/reports/status'); + $this->assertNoText(t('Translation update status'), 'No status message'); + + $this->drupalGet('admin/reports/translations'); + $this->assertRaw(t('No translatable languages available. Add a language first.', array('@add_language' => url('admin/config/regional/language'))), 'Language message'); + + // Add German language + $edit = array( + 'predefined_langcode' => 'de', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Drupal core is probably in 8.x, but tests may also be executed with + // stable releases. Therefore we will just ignore it here to make sure we + // have a controlled environment. + $status = state()->get('locale.translation_status'); + unset($status['drupal']); + state()->set('locale.translation_status', $status); + + // One language added, all translations up to date. + $this->drupalGet('admin/reports/status'); + $this->assertText(t('Translation update status'), 'Status message'); + $this->assertText(t('Up to date'), 'Translations up to date'); + $this->drupalGet('admin/reports/translations'); + $this->assertText(t('All translations up to date.'), 'Translations up to date'); + + // Set locale_test_translate module to have a local translation available. + $status = state()->get('locale.translation_status'); + $status['locale_test_translate']['de']->type = 'local'; + state()->set('locale.translation_status', $status); + + // Check if updates are available for German. + $this->drupalGet('admin/reports/status'); + $this->assertText(t('Translation update status'), 'Status message'); + $this->assertRaw(t('Updates available for: @languages. See the Available translation updates page for more information.', array('@languages' => t('German'), '@updates' => url('admin/reports/translations'))), 'Updates available message'); + $this->drupalGet('admin/reports/translations'); + $this->assertText(t('Updates for: @modules', array('@modules' => 'Locale test translate')), 'Translations avaiable'); + + // Set locale_test_translate module to have a dev release and no + // translation found. + $status = state()->get('locale.translation_status'); + $status['locale_test_translate']['de']->version = '1.3-dev'; + unset($status['locale_test_translate']['de']->type); + state()->set('locale.translation_status', $status); + + // Check if no updates were found. + $this->drupalGet('admin/reports/status'); + $this->assertText(t('Translation update status'), 'Status message'); + $this->assertRaw(t('Updates not found for: @languages. See the Available translation updates page for more information.', array('@languages' => t('German'), '@updates' => url('admin/reports/translations'))), 'Updates not found message'); + $this->drupalGet('admin/reports/translations'); + $this->assertText(t('No updates found for 1 project'), 'No updates found'); + $this->assertText(t('@module (@version).', array('@module' => 'Locale test translate', '@version' => '1.3-dev')), 'Release details'); + $this->assertText(t('No translation files are provided for development releases.'), 'Release info'); + } +} \ No newline at end of file diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php index 6f96943..9896aa7 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\locale\Tests\LocaleCompareTest. + * Definition of Drupal\locale\Tests\LocaleUpdateTest. */ namespace Drupal\locale\Tests; @@ -51,7 +51,7 @@ class LocaleUpdateTest extends WebTestBase { public static function getInfo() { return array( - 'name' => 'Update Interface translations', + 'name' => 'Update translations', 'description' => 'Tests for updating the interface translations of projects.', 'group' => 'Locale', ); @@ -429,9 +429,18 @@ function testUpdateImportSourceRemote() { ); $this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration')); - // Execute the translation update. + // Get the translation status. $this->drupalGet('admin/reports/translations/check'); - $this->drupalPost('admin/reports/translations', array(), t('Update')); + + // Check the status on the Available translation status page. + $this->assertRaw('', 'German language found'); + $this->assertText('Updates for: Contributed module one, Contributed module two, Custom module one, Locale test', 'Updates found'); + $this->assertText('Updates for: Contributed module one, Contributed module two, Custom module one, Locale test', 'Updates found'); + $this->assertText('Contributed module one (' . format_date($this->timestamp_now, 'html_date') . ')', 'Updates for Contrib module one'); + $this->assertText('Contributed module two (' . format_date($this->timestamp_new, 'html_date') . ')', 'Updates for Contrib module two'); + + // Execute the translation update. + $this->drupalPost('admin/reports/translations', array(), t('Update translations')); // Check if the translation has been updated, using the status cache. $status = state()->get('locale.translation_status'); @@ -486,7 +495,7 @@ function testUpdateImportSourceLocal() { // Execute the translation update. $this->drupalGet('admin/reports/translations/check'); - $this->drupalPost('admin/reports/translations', array(), t('Update')); + $this->drupalPost('admin/reports/translations', array(), t('Update translations')); // Check if the translation has been updated, using the status cache. $status = state()->get('locale.translation_status'); @@ -542,7 +551,7 @@ function testUpdateImportWithoutDirectory() { // Execute the translation update. $this->drupalGet('admin/reports/translations/check'); - $this->drupalPost('admin/reports/translations', array(), t('Update')); + $this->drupalPost('admin/reports/translations', array(), t('Update translations')); // Check if the translation has been updated, using the status cache. $status = state()->get('locale.translation_status'); @@ -597,7 +606,7 @@ function testUpdateImportModeNonCustomized() { // Execute translation update. $this->drupalGet('admin/reports/translations/check'); - $this->drupalPost('admin/reports/translations', array(), t('Update')); + $this->drupalPost('admin/reports/translations', array(), t('Update translations')); // Check whether existing translations have (not) been overwritten. $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January'); @@ -635,7 +644,7 @@ function testUpdateImportModeNone() { // Execute translation update. $this->drupalGet('admin/reports/translations/check'); - $this->drupalPost('admin/reports/translations', array(), t('Update')); + $this->drupalPost('admin/reports/translations', array(), t('Update translations')); // Check whether existing translations have (not) been overwritten. $this->assertTranslation('January', 'Januar_customized', 'de'); diff --git a/core/modules/locale/locale.admin.css b/core/modules/locale/locale.admin.css index 2452608..3a632b0 100644 --- a/core/modules/locale/locale.admin.css +++ b/core/modules/locale/locale.admin.css @@ -47,3 +47,77 @@ .locale-translate-edit-form table.changed { margin-top: 0; } + +/** + * Available translation updates page. + */ +#locale-translation-status table { + table-layout: fixed; +} +#locale-translation-status th.select-all { + width: 4%; +} +#locale-translation-status th.title { + width: 25%; +} +#locale-translation-status th.description { +} +#locale-translation-status td { + vertical-align: top; +} +#locale-translation-status .expand .inner { + background: transparent url(../../misc/menu-collapsed.png) left .6em no-repeat; + margin-left: -12px; + padding-left: 12px; +} +#locale-translation-status .expanded .expand .inner { + background: transparent url(../../misc/menu-expanded.png) left .6em no-repeat; +} + +#locale-translation-status label { + color: #1d1d1d; + font-size: 1.15em; +} +#locale-translation-status .description { + cursor: pointer; +} +#locale-translation-status .description .inner { + color: #5c5c5b; + line-height: 20px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +#locale-translation-status .expanded .description .inner { + height: auto; + overflow: visible; + white-space: normal; +} +#locale-translation-status .expanded .description .text { + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; +} +.js #locale-translation-status .description .inner { + height: 20px; +} +#locale-translation-status .expanded .description .inner { + height: auto; + overflow: visible; + white-space: normal; +} +#locale-translation-status .details { + padding: 5px 0; + max-width: 490px; + white-space: normal; + font-size: 0.9em; + color: #666; +} +@media screen and (max-width: 40em) { + #locale-translation-status th.title { + width: 20%; + } + #locale-translation-status th.status { + width: 40%; + } +} diff --git a/core/modules/locale/locale.admin.js b/core/modules/locale/locale.admin.js index a9ded86..0f7357e 100644 --- a/core/modules/locale/locale.admin.js +++ b/core/modules/locale/locale.admin.js @@ -40,6 +40,41 @@ Drupal.behaviors.localeTranslateDirty = { } }; +/** + * Show/hide the description details on Available translation updates page. + */ +Drupal.behaviors.hideUpdateInformation = { + attach: function (context, settings) { + var $table = $('#locale-translation-status').once('expand-updates'); + var effect = settings.hideUpdates; + if ($table.length) { + var $tbodies = $table.find('tbody'); + + // Open/close the description details by toggling a tr class. + $tbodies.on('click keydown', '.description', function (e) { + if (e.keyCode && (e.keyCode !== 13 && e.keyCode !== 32)) { + return; + } + e.preventDefault(); + var $tr = $(this).closest('tr'); + + $tr.toggleClass('expanded'); + + // Change screen reader text. + $tr.find('.udpate-description-prefix').text(function () { + if ($tr.hasClass('expanded')) { + return Drupal.t('Hide description'); + } + else { + return Drupal.t('Show description'); + } + }); + }); + } + $table.find('.requirements, .links').hide(); + } +}; + $.extend(Drupal.theme, { localeTranslateChangedMarker: function () { return '*'; diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 2745f55..a82c44b 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -283,6 +283,70 @@ function locale_schema() { } /** + * Implements hook_requirements(). + */ +function locale_requirements($phase) { + $requirements = array(); + if ($phase == 'runtime') { + $available_updates = array(); + $updates_not_found = array(); + $languages = locale_translatable_language_list(); + + if ($languages) { + // Determine the status of the translation updates per lanuage. + $status = state()->get('locale.translation_status'); + if ($status) { + foreach ($status as $project_id => $project) { + foreach ($project as $langcode => $project_info) { + if (!isset($project_info->type)) { + $updates_not_found[$langcode] = $languages[$langcode]->name; + } + elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) { + $available_updates[$langcode] = $languages[$langcode]->name; + } + } + } + + if ($available_updates || $updates_not_found) { + if ($available_updates) { + $requirements['locale_translation'] = array( + 'title' => 'Translation update status', + 'value' => l(t('Updates available'), 'admin/reports/translations'), + 'severity' => REQUIREMENT_WARNING, + 'description' => t('Updates available for: @languages. See the Available translation updates page for more information.', array('@languages' => implode(', ', $available_updates), '@updates' => url('admin/reports/translations'))), + ); + } + else { + $requirements['locale_translation'] = array( + 'title' => 'Translation update status', + 'value' => t('Updates not found'), + 'severity' => REQUIREMENT_WARNING, + 'description' => t('Updates not found for: @languages. See the Available translation updates page for more information.', array('@languages' => implode(', ', $updates_not_found), '@updates' => url('admin/reports/translations'))), + ); + } + } + else { + $requirements['locale_translation'] = array( + 'title' => 'Translation update status', + 'value' => t('Up to date'), + 'severity' => REQUIREMENT_OK, + ); + } + } + else { + $requirements['locale_translation'] = array( + 'title' => 'Translation update status', + 'value' => l(t('Can not determine status'), 'admin/reports/translations'), + 'severity' => REQUIREMENT_WARNING, + 'description' => t('No translation status is available. See the Available translation updates page for more information.', array('@updates' => url('admin/reports/translations'))), + ); + } + } + } + return $requirements; +} + +/** * @addtogroup updates-7.x-to-8.x * @{ */ diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index dac0557..5cdcf3d 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -227,7 +227,7 @@ function locale_menu() { 'title' => 'Available translation updates', 'description' => 'Get a status report about available interface translations for your installed modules and themes.', 'page callback' => 'drupal_get_form', - 'page arguments' => array('locale_translation_status_form'), + 'page arguments' => array('locale_translation_status'), 'access arguments' => array('translate interface'), 'file' => 'locale.pages.inc', ); @@ -263,6 +263,14 @@ function locale_theme() { 'render element' => 'form', 'file' => 'locale.pages.inc', ), + 'locale_translation_last_check' => array( + 'variables' => array('last' => NULL), + 'file' => 'locale.pages.inc', + ), + 'locale_translation_update_info' => array( + 'arguments' => array('updates' => array(), 'not_found' => array()), + 'file' => 'locale.pages.inc', + ), ); } diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index cf4edce..86e333b 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -595,46 +595,130 @@ function locale_translate_settings_submit($form, &$form_state) { * * @see locale_menu() */ -function locale_translation_status_form($form, &$form_state) { +function locale_translation_status($form, &$form_state) { + module_load_include('translation.inc', 'locale'); module_load_include('compare.inc', 'locale'); + $updates = $options = array(); + $languages_update = $languages_not_found = 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(); $languages = locale_translatable_language_list(); + $projects = locale_translation_get_projects(); $status = state()->get('locale.translation_status'); - if (!$languages) { - drupal_set_message(t('No translatable languages available. Add language first.', array('@add_lanuage' => url('admin/config/regional/language'))), 'warning'); + // Prepare information about projects which have available translation + // updates. + if ($languages && $status) { + 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_data[$project_info->name]->info['name'], + 'version' => $project_info->version, + 'info' => _locale_translation_status_debug_info($project_info), + ); + $languages_not_found[$langcode] = $langcode; + } + // 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, + ); + $languages_update[$langcode] = $langcode; + } + } + } + $languages_not_found = array_diff($languages_not_found, $languages_update); + + // Build data options for the select table. + foreach($updates as $langcode => $update) { + $options[$langcode] = array( + 'title' => check_plain($languages[$langcode]->name), + 'status' => array('class' => array('description', 'expand', 'priority-low'), 'data' => theme('locale_translation_update_info', $update)), + ); + } + // Sort the table data on language name. + uasort($options, 'drupal_sort_title'); } - $last = state()->get('locale.translation_last_checked'); - $markup = '
'; - $markup .= $last ? t('Last checked: @time ago', array('@time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never'); - $markup .= ' (' . l(t('Check manually'), 'admin/reports/translations/check', array('query' => drupal_get_destination())) . ')'; - $markup .= "
\n"; + $last_checked = state()->get('locale.translation_last_checked'); $form['last_checked'] = array( - '#markup' => '

' . $markup . '

', + '#markup' => '

' . theme('locale_translation_last_check', array('last' => $last_checked)) . '

', ); - $form['langcodes'] = array( - '#type' => 'value', - '#value' => drupal_map_assoc(array_keys($languages)), + $header = array( + 'title' => array( + 'data' => t('Language'), + 'class' => array('title'), + ), + 'status' => array( + 'data' => t('Status'), + 'class' => array('status', 'priority-low'), + ), ); - $form['actions'] = array( - '#type' => 'actions' - ); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Update') + if (!$languages) { + $empty = t('No translatable languages available. Add a language first.', array('@add_language' => url('admin/config/regional/language'))); + } + elseif ($status) { + $empty = t('All translations up to date.'); + } + else { + $empty = t('No translation status available. Check manually.', array('@check' => url('admin/reports/translations/check'))); + } + + $form['languages'] = 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') . '/locale.admin.css'); + + $form['actions'] = array('#type' => 'actions'); + if ($languages_update) { + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Update translations'), + ); + } + return $form; } /** + * Form validation handler for locale_translation_status(). + */ +function locale_translation_status_validate($form, &$form_state) { + // Check if a language has been selected. tableselect fails to do so. + if (!array_filter($form_state['values']['languages'])) { + form_set_error('', t('Select a language to update.')); + } +} + +/** * Form submission handler for locale_translation_status(). */ -function locale_translation_status_form_submit($form, &$form_state) { +function locale_translation_status_submit($form, &$form_state) { module_load_include('fetch.inc', 'locale'); - $langcodes = array_filter($form_state['values']['langcodes']); + $langcodes = array_filter($form_state['values']['languages']); // Set the translation import options. This determines if existing // translations will be overwritten by imported strings. @@ -656,7 +740,70 @@ function locale_translation_status_form_submit($form, &$form_state) { } /** + * Form element callback: After build changes to the language update table. + * + * Adds labels to the languages and removes checkboxes from languages which have + * no updates ane one or more translation files could not be found. + */ +function locale_translation_language_table($form_element) { + // Add labels to Language names. + foreach ($form_element['#options'] as $langcode => $option) { + $id = $form_element[$langcode]['#id']; + $title = $option['title']; + $form_element['#options'][$langcode]['title'] = ''; + } + + // 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; +} + +/** + * Provides debug info when translation update were not found for a project. + * + * 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 string $version + * Version of the project. + * @param string $remote_path + * Remote path where the translation file was tried to load from. + * @param string $local_path + * Local path where the translation file was tried to load from. + */ +function _locale_translation_status_debug_info($source) { + $remote_path = isset($source->files['remote']->uri) ? $source->files['remote']->uri : ''; + $local_path = isset($source->files['local']->uri) ? $source->files['local']->uri : ''; + + if (strpos($source->version, 'dev') !== FALSE) { + return t('No translation files are provided for development releases.'); + } + if (locale_translation_use_remote_source() && $remote_path && $local_path) { + return t('File not found at %remote_path nor at %local_path', array( + '%remote_path' => $remote_path, + '%local_path' => $local_path, + )); + } + elseif ($local_path) { + return t('File not found at %local_path', array('%local_path' => $local_path)); + } + return t('Translation file location could not be determined.'); +} + +/** * Default theme function for translation edit form. + * + * @see locale_translate_edit_form() + * @ingroup themeable */ function theme_locale_translate_edit_form_strings($variables) { $output = ''; @@ -689,3 +836,73 @@ function theme_locale_translate_edit_form_strings($variables) { $output .= theme('pager'); return $output; } + +/** + * Default theme function for translation status information per language. + * + * @see locale_translation_status() + * @ingroup themeable + */ +function theme_locale_translation_update_info($variables) { + $description = $details = ''; + + // Build output for available updates. + if (isset($variables['updates'])) { + if ($variables['updates']) { + $releases = array(); + foreach ($variables['updates'] as $update) { + $modules[] = $update['name']; + $releases[] = t('@module (@date)', array('@module' => $update['name'], '@date' => format_date($update['timestamp'], 'html_date'))); + } + } + $description = '' . t('Updates for: @modules', array('@modules' => implode(', ', $modules))) . ''; + $details = theme('item_list', array('items' => $releases)); + } + + // Build output for updates not found. + if (isset($variables['not_found'])) { + if (empty($description)) { + $description = '' . format_plural(count($variables['not_found']), 'No updates found for 1 project', 'No updates found for @count projects') . ''; + } + if ($variables['not_found']) { + $releases = array(); + foreach ($variables['not_found'] as $update) { + $version = $update['version'] ? $update['version'] : t('no version'); + $releases[] = t('@module (@version).', array('@module' => $update['name'], '@version' => $version)) . ' ' . $update['info']; + } + } + if ($details) { + $details .= t('No updates found for:'); + } + $details .= theme('item_list', array('items' => $releases)); + } + + $output = '
'; + $output .= 'Show description' . $description; + $output .= $details ? '
' . $details . '
' : ''; + $output .= '
'; + return $output; +} + +/** + * Returns HTML for the last time we checked for update data. + * + * In addition to properly formatting the given timestamp, this function also + * provides a "Check manually" link that refreshes the available update and + * redirects back to the same page. + * + * @param $variables + * An associative array containing: + * - last: The timestamp when the site last checked for available updates. + * + * @see locale_translation_status() + * @ingroup themeable + */ +function theme_locale_translation_last_check($variables) { + $last = $variables['last']; + $output = '
'; + $output .= $last ? t('Last checked: @time ago', array('@time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never'); + $output .= ' (' . l(t('Check manually'), 'admin/reports/translations/check', array('query' => drupal_get_destination())) . ')'; + $output .= "
\n"; + return $output; +} diff --git a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module index f4f1972..98eb358 100644 --- a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module +++ b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module @@ -4,3 +4,17 @@ * @file * Simulates a custom module with a local po file. */ + +/** + * Implements hook_system_info_alter(). + * + * By default this modules is hidden but once enabled it behaves like a normal + * (not hidden) module. This hook implementation changes the .info data by + * setting the hidden status to FALSE. + */ +function locale_test_translate_system_info_alter(&$info, $file, $type) { + if ($file->name == 'locale_test_translate') { + // Don't hide the module. + $info['hidden'] = FALSE; + } +} diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css index d74f502..7855059 100644 --- a/core/themes/seven/style.css +++ b/core/themes/seven/style.css @@ -1006,23 +1006,29 @@ div.add-or-remove-shortcuts { #system-modules .fieldset-wrapper { padding: 0; } -#system-modules table { +#system-modules table, +#locale-translation-status table { border: 0; } #system-modules tr.even, -#system-modules tr.odd { +#system-modules tr.odd, +#locale-translation-status tr.even, +#locale-translation-status tr.odd { background: #f3f4ee; border: 0; border-bottom: 10px solid #fff; } -#system-modules tr td:last-child { +#system-modules tr td:last-child, +#locale-translation-status tr td:last-child { border: 0; } -#system-modules table th { +#system-modules table th, +#locale-translation-status table th { border: 0; border-bottom: 10px solid #fff; } -#system-modules .sticky-header th { +#system-modules .sticky-header th, +#locale-translation-status .sticky-header th { border: 0; } /* Recent content block */