Commit 0485f292 authored by Dries's avatar Dries
Browse files

Issue #1804702 by Sutharsan, balintk, YesCT, Jose Reyero, Gábor Hojtsy:...

Issue #1804702 by Sutharsan, balintk, YesCT, Jose Reyero, Gábor Hojtsy: Display interface translation status.
parent 4116dc4a
<?php
/**
* @file
* Contains Drupal\locale\Tests\LocaleUpdateInterfaceTest.
*/
namespace Drupal\locale\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests for the locale translation update status user interfaces.
*/
class LocaleUpdateInterfaceTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('update', 'locale', 'locale_test_translate');
public static function getInfo() {
return array(
'name' => 'Update translations user interface',
'description' => 'Tests for the user interface of project interface translations.',
'group' => 'Locale',
);
}
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. <a href="@add_language">Add a language</a> 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. As this is an uncontrolled factor in the test, we will
// ignore Drupal core here and continue with the prepared modules.
$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 <a href="@updates">Available translation updates</a> 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('Missing translations for: @languages. See the <a href="@updates">Available translation updates</a> page for more information.', array('@languages' => t('German'), '@updates' => url('admin/reports/translations'))), 'Missing translations message');
$this->drupalGet('admin/reports/translations');
$this->assertText(t('Missing translations for one project'), 'No translations 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');
}
}
......@@ -2,7 +2,7 @@
/**
* @file
* Definition of Drupal\locale\Tests\LocaleCompareTest.
* Contains Drupal\locale\Tests\LocaleUpdateTest.
*/
namespace Drupal\locale\Tests;
......@@ -10,7 +10,7 @@
use Drupal\simpletest\WebTestBase;
/**
* Tests for comparing status of existing project translations with available translations.
* Tests for update translations.
*/
class LocaleUpdateTest extends WebTestBase {
......@@ -51,24 +51,21 @@ 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',
);
}
/**
* 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();
module_load_include('compare.inc', 'locale');
module_load_include('fetch.inc', 'locale');
$admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
$this->drupalLogin($admin_user);
// We use German as test language. This language must match the translation
// file that come with the locale_test module (test.de.po) and can therefore
// not be chosen randomly.
$this->drupalPost('admin/config/regional/language/add', array('predefined_langcode' => 'de'), t('Add language'));
// Setup timestamps to identify old and new translation sources.
......@@ -429,9 +426,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('<label for="edit-langcodes-de" class="language-name">German</label>', '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 +492,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 +548,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 +603,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 +641,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');
......@@ -794,4 +800,5 @@ function testEnableCustomLanguage() {
$this->assertText('Allowed HTML source string', t('String successfully imported.'));
$this->assertNoText('Another allowed HTML source string', t('String with disallowed translation not imported.'));
}
}
......@@ -47,3 +47,77 @@
.locale-translate-edit-form table.changed {
margin-top: 0;
}
/**
* Available translation updates page.
*/
#locale-translation-status-form table {
table-layout: fixed;
}
#locale-translation-status-form th.select-all {
width: 4%;
}
#locale-translation-status-form th.title {
width: 25%;
}
#locale-translation-status-form th.description {
}
#locale-translation-status-form td {
vertical-align: top;
}
#locale-translation-status-form .expand .inner {
background: transparent url(../../misc/menu-collapsed.png) left .6em no-repeat;
margin-left: -12px;
padding-left: 12px;
}
#locale-translation-status-form .expanded .expand .inner {
background: transparent url(../../misc/menu-expanded.png) left .6em no-repeat;
}
#locale-translation-status-form label {
color: #1d1d1d;
font-size: 1.15em;
}
#locale-translation-status-form .description {
cursor: pointer;
}
#locale-translation-status-form .description .inner {
color: #5c5c5b;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#locale-translation-status-form .expanded .description .inner {
height: auto;
overflow: visible;
white-space: normal;
}
#locale-translation-status-form .expanded .description .text {
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
.js #locale-translation-status-form .description .inner {
height: 20px;
}
#locale-translation-status-form .expanded .description .inner {
height: auto;
overflow: visible;
white-space: normal;
}
#locale-translation-status-form .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-form th.title {
width: 20%;
}
#locale-translation-status-form th.status {
width: 40%;
}
}
......@@ -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-form').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('.update-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 '<abbr class="warning ajax-changed" title="' + Drupal.t('Changed') + '">*</abbr>';
......
......@@ -282,6 +282,70 @@ function locale_schema() {
return $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 <a href="@updates">Available translation updates</a> 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('Missing translations'),
'severity' => REQUIREMENT_INFO,
'description' => t('Missing translations for: @languages. See the <a href="@updates">Available translation updates</a> 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 <a href="@updates">Available translation updates</a> page for more information.', array('@updates' => url('admin/reports/translations'))),
);
}
}
}
return $requirements;
}
/**
* @addtogroup updates-7.x-to-8.x
* @{
......
......@@ -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',
),
);
}
......
......@@ -596,41 +596,125 @@ function locale_translate_settings_submit($form, &$form_state) {
* @see locale_menu()
*/
function locale_translation_status_form($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. <a href="@add_lanuage">Add language</a> 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_info->name == 'drupal' ? t('Drupal core') : $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 = '<div class="locale checked">';
$markup .= $last ? t('Last checked: @time ago', array('@time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never');
$markup .= ' <span class="check-manually">(' . l(t('Check manually'), 'admin/reports/translations/check', array('query' => drupal_get_destination())) . ')</span>';
$markup .= "</div>\n";
$last_checked = state()->get('locale.translation_last_checked');
$form['last_checked'] = array(
'#markup' => '<p>' . $markup . '</p>',
'#markup' => '<p>' . theme('locale_translation_last_check', array('last' => $last_checked)) . '</p>',
);
$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. <a href="@add_language">Add a language</a> 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. <a href="@check">Check manually</a>.', array('@check' => url('admin/reports/translations/check')));
}
$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') . '/locale.admin.css');
$form['actions'] = array('#type' => 'actions');
if ($languages_update) {
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Update translations'),
);
}
return $form;
}
/**
* Form submission handler for locale_translation_status().
* Form validation handler for locale_translation_status_form().
*/
function locale_translation_status_form_validate($form, &$form_state) {
// Check if a language has been selected. 'tableselect' doesn't.
if (!array_filter($form_state['values']['langcodes'])) {
form_set_error('', t('Select a language to update.'));
}
}
/**
* Form submission handler for locale_translation_status_form().
*/
function locale_translation_status_form_submit($form, &$form_state) {
module_load_include('fetch.inc', 'locale');
......@@ -656,7 +740,74 @@ function locale_translation_status_form_submit($form, &$form_state) {
}
/**
* Default theme function for translation edit form.
* 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) {
// 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'] = '<label for="' . $form_element[$langcode]['#id'] . '" class="language-name">' . $title . '</label>';
}
// 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 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.