Commit c1f21810 authored by alexpott's avatar alexpott

Issue #1998056 by Sutharsan, clemens.tolboom, penyaskito: Automatically update...

Issue #1998056 by Sutharsan, clemens.tolboom, penyaskito: Automatically update interface translations using cron.
parent b07e8f07
......@@ -32,8 +32,8 @@ public function buildForm(array $form, array &$form_state) {
'#default_value' => $config->get('translation.update_interval_days'),
'#options' => array(
'0' => t('Never (manually)'),
'1' => t('Daily'),
'7' => t('Weekly'),
'30' => t('Monthly'),
),
'#description' => t('Select how frequently you want to check for new interface translations for your currently installed modules and themes. <a href="@url">Check updates now</a>.', array('@url' => url('admin/reports/translations/check'))),
);
......
<?php
/**
* @file
* Contains Drupal\locale\Tests\LocaleUpdateCronTest.
*/
namespace Drupal\locale\Tests;
/**
* Tests for translation update using cron.
*/
class LocaleUpdateCronTest extends LocaleUpdateBase {
protected $batch_output = array();
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('update', 'locale', 'locale_test');
public static function getInfo() {
return array(
'name' => 'Update translations using cron',
'description' => 'Tests for using cron to update 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);
$this->addLanguage('de');
}
/**
* Tests interface translation update using cron.
*/
function testUpdateCron() {
// Set a flag to let the locale_test module replace the project data with a
// set of test projects.
\Drupal::state()->set('locale.test_projects_alter', TRUE);
// Setup local and remote translations files.
$this->setTranslationFiles();
config('locale.settings')->set('translation.default_filename', '%project-%version.%language._po')->save();
// Update translations using batch to ensure a clean test starting point.
$this->drupalGet('admin/reports/translations/check');
$this->drupalPost('admin/reports/translations', array(), t('Update translations'));
// Store translation status for comparison.
$initial_history = locale_translation_get_file_history();
// Prepare for test: Simulate new translations being availabe.
// Change the last updated timestamp of a translation file.
$contrib_module_two_uri = 'public://local/contrib_module_two-8.x-2.0-beta4.de._po';
touch(drupal_realpath($contrib_module_two_uri), REQUEST_TIME);
// Prepare for test: Simulate that the file has not been checked for a long
// time. Set the last_check timestamp to zero.
$query = db_update('locale_file');
$query->fields(array('last_checked' => 0));
$query->condition('project', 'contrib_module_two');
$query->condition('langcode', 'de');
$query->execute();
// Test: Disable cron update and verify that no tasks are added to the
// queue.
$edit = array(
'update_interval_days' => 0,
);
$this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration'));
// Execute locale cron taks to add tasks to the queue.
locale_cron();
// Check whether no tasks are added to the queue.
$queue = \Drupal::queue('locale_translation', TRUE);
$this->assertEqual($queue->numberOfItems(), 0, 'Queue is empty');
// Test: Enable cron update and check if update tasks are added to the
// queue.
// Set cron update to Weekly.
$edit = array(
'update_interval_days' => 7,
);
$this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration'));
// Execute locale cron taks to add tasks to the queue.
locale_cron();
// Check whether tasks are added to the queue.
$queue = \Drupal::queue('locale_translation', TRUE);
$this->assertEqual($queue->numberOfItems(), 3, 'Queue holds tasks for one project.');
$item = $queue->claimItem();
$queue->releaseItem($item);
$this->assertEqual($item->data[1][0], 'contrib_module_two', 'Queue holds tasks for contrib module one.');
// Test: Run cron for a second time and check if tasks are not added to
// the queue twice.
locale_cron();
// Check whether no more tasks are added to the queue.
$queue = \Drupal::queue('locale_translation', TRUE);
$this->assertEqual($queue->numberOfItems(), 3, 'Queue holds tasks for one project.');
// Test: Execute cron and check if tasks are executed correctly.
// Run cron to process the tasks in the queue.
$this->drupalGet('admin/reports/status/run-cron');
drupal_static_reset('locale_translation_get_file_history');
$history = locale_translation_get_file_history();
$initial = $initial_history['contrib_module_two']['de'];
$current = $history['contrib_module_two']['de'];
$this->assertTrue($current->timestamp > $initial->timestamp, 'Timestamp is updated');
$this->assertTrue($current->last_checked > $initial->last_checked, 'Last checked is updated');
}
}
......@@ -12,7 +12,7 @@
/**
* Tests for the locale translation update status user interfaces.
*/
class LocaleUpdateInterfaceTest extends WebTestBase {
class LocaleUpdateInterfaceTest extends LocaleUpdateBase {
/**
* Modules to enable.
......@@ -51,16 +51,13 @@ function testInterface() {
$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'));
$this->addLanguage('de');
// 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 = \Drupal::state()->get('locale.translation_status');
unset($status['drupal']);
// mark Drupal core as translated and continue with the prepared modules.
$status = locale_translation_get_status();
$status['drupal']['de']->type = 'current';
\Drupal::state()->set('locale.translation_status', $status);
// One language added, all translations up to date.
......@@ -71,7 +68,7 @@ function testInterface() {
$this->assertText(t('All translations up to date.'), 'Translations up to date');
// Set locale_test_translate module to have a local translation available.
$status = \Drupal::state()->get('locale.translation_status');
$status = locale_translation_get_status();
$status['locale_test_translate']['de']->type = 'local';
\Drupal::state()->set('locale.translation_status', $status);
......@@ -84,9 +81,9 @@ function testInterface() {
// Set locale_test_translate module to have a dev release and no
// translation found.
$status = \Drupal::state()->get('locale.translation_status');
$status = locale_translation_get_status();
$status['locale_test_translate']['de']->version = '1.3-dev';
unset($status['locale_test_translate']['de']->type);
$status['locale_test_translate']['de']->type = '';
\Drupal::state()->set('locale.translation_status', $status);
// Check if no updates were found.
......
This diff is collapsed.
......@@ -206,31 +206,6 @@ function locale_translation_default_translation_server() {
);
}
/**
* Build path to translation source, out of a server path replacement pattern.
*
* @param stdClass $project
* Project object containing data to be inserted in the template.
* @param string $template
* String containing placeholders. Available placeholders:
* - "%project": Project name.
* - "%version": Project version.
* - "%core": Project core version.
* - "%language": Language code.
*
* @return string
* String with replaced placeholders.
*/
function locale_translation_build_server_pattern($project, $template) {
$variables = array(
'%project' => $project->name,
'%version' => $project->version,
'%core' => $project->core,
'%language' => isset($project->langcode) ? $project->langcode : '%language',
);
return strtr($template, $variables);
}
/**
* Check for the latest release of project translations.
*
......@@ -242,6 +217,7 @@ function locale_translation_build_server_pattern($project, $template) {
* @return array
* Available sources indexed by project and language.
*/
// @todo Return batch or NULL
function locale_translation_check_projects($projects = array(), $langcodes = array()) {
if (locale_translation_use_remote_source()) {
// Retrieve the status of both remote and local translation sources by
......@@ -251,6 +227,7 @@ function locale_translation_check_projects($projects = array(), $langcodes = arr
else {
// Retrieve and save the status of local translations only.
locale_translation_check_projects_local($projects, $langcodes);
Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
}
}
......@@ -276,7 +253,7 @@ function locale_translation_check_projects_batch($projects = array(), $langcodes
/**
* Builds a batch to get the status of remote and local translation files.
*
* The batch process fetches the state of both remote and (if configured) local
* The batch process fetches the state of both local and (if configured) remote
* translation files. The data of the most recent translation is stored per
* per project and per language. This data is stored in a state variable
* 'locale.translation_status'. The timestamp it was last updated is stored
......@@ -294,8 +271,9 @@ function locale_translation_check_projects_batch($projects = array(), $langcodes
function locale_translation_batch_status_build($projects = array(), $langcodes = array()) {
$projects = $projects ? $projects : array_keys(locale_translation_get_projects());
$langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
$options = _locale_translation_default_update_options();
$operations = _locale_translation_batch_status_operations($projects, $langcodes);
$operations = _locale_translation_batch_status_operations($projects, $langcodes, $options);
$batch = array(
'operations' => $operations,
......@@ -312,30 +290,26 @@ function locale_translation_batch_status_build($projects = array(), $langcodes =
* Helper function to construct batch operations checking remote translation
* status.
*
* @param array projects
* @param array $projects
* Array of project names to be processed.
* @param array langcodes
* @param array $langcodes
* Array of language codes.
* @param array $options
* Batch processing options.
*
* @return array
* Array of batch operations.
*/
function _locale_translation_batch_status_operations($projects, $langcodes) {
function _locale_translation_batch_status_operations($projects, $langcodes, $options = array()) {
$operations = array();
// Set the batch processes for remote sources.
$sources = locale_translation_build_sources($projects, $langcodes);
if (locale_translation_use_remote_source()) {
foreach ($sources as $source) {
$operations[] = array('locale_translation_batch_status_fetch_remote', array($source));
foreach ($projects as $project) {
foreach ($langcodes as $langcode) {
// Check status of local and remote translation sources.
$operations[] = array('locale_translation_batch_status_check', array($project, $langcode, $options));
}
}
// Check for local sources, compare the results of local and remote and store
// the most recent.
$operations[] = array('locale_translation_batch_status_fetch_local', array($sources));
$operations[] = array('locale_translation_batch_status_compare', array());
return $operations;
}
......@@ -343,8 +317,7 @@ function _locale_translation_batch_status_operations($projects, $langcodes) {
* Check and store the status and timestamp of local po files.
*
* Only po files in the local file system are checked. Any remote translation
* sources will be ignored. Results are stored in the state variable
* 'locale.translation_status'.
* files will be ignored.
*
* Projects may contain a server_pattern option containing a pattern of the
* path to the po source files. If no server_pattern is defined the default
......@@ -362,8 +335,6 @@ function _locale_translation_batch_status_operations($projects, $langcodes) {
function locale_translation_check_projects_local($projects = array(), $langcodes = array()) {
$projects = locale_translation_get_projects($projects);
$langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
$history = locale_translation_get_file_history();
$results = array();
// For each project and each language we check if a local po file is
// available. When found the source object is updated with the appropriate
......@@ -371,35 +342,8 @@ function locale_translation_check_projects_local($projects = array(), $langcodes
foreach ($projects as $name => $project) {
foreach ($langcodes as $langcode) {
$source = locale_translation_source_build($project, $langcode);
if (locale_translation_source_check_file($source)) {
$source->type = 'local';
$source->timestamp = $source->files['local']->timestamp;
}
// Compare the available translation with the current translations status.
// If the project/language was translated before and it is more recent
// than the most recent translation, the translation is up to date. Which
// is marked in the source object with type "current".
if (isset($history[$source->project][$source->langcode])) {
$current = $history[$source->project][$source->langcode];
// Add the current translation to the source object to save it in
// the status cache.
$source->files[LOCALE_TRANSLATION_CURRENT] = $current;
if (isset($source->type)) {
$available = $source->files[$source->type];
$result = _locale_translation_source_compare($current, $available) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $available : $current;
$source->type = $result->type;
$source->timestamp = $result->timestamp;
}
else {
$source->type = $current->type;
$source->timestamp = $current->timestamp;
}
}
$results[$name][$langcode] = $source;
$file = locale_translation_source_check_file($source);
locale_translation_status_save($name, $langcode, LOCALE_TRANSLATION_LOCAL, $file);
}
}
locale_translation_status_save($results);
}
......@@ -30,8 +30,12 @@ function locale_translation_batch_update_build($projects = array(), $langcodes =
module_load_include('compare.inc', 'locale');
$projects = $projects ? $projects : array_keys(locale_translation_get_projects());
$langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
$status_options = $options;
$status_options['finish_feedback'] = FALSE;
$operations = _locale_translation_batch_status_operations($projects, $langcodes);
// Check status of local and remote translation files.
$operations = _locale_translation_batch_status_operations($projects, $langcodes, $status_options);
// Download and import translations.
$operations = array_merge($operations, _locale_translation_fetch_operations($projects, $langcodes, $options));
$batch = array(
......@@ -90,9 +94,7 @@ function locale_translation_batch_fetch_build($projects = array(), $langcodes =
*/
function _locale_translation_fetch_operations($projects, $langcodes, $options) {
$operations = array();
$config = config('locale.settings');
$operations[] = array('locale_translation_batch_fetch_sources', array($projects, $langcodes));
foreach ($projects as $project) {
foreach ($langcodes as $langcode) {
if (locale_translation_use_remote_source()) {
......@@ -102,12 +104,5 @@ function _locale_translation_fetch_operations($projects, $langcodes, $options) {
}
}
// Update and save the translation status.
$operations[] = array('locale_translation_batch_fetch_update_status', array());
// Update and save the source status. New translation files have been
// downloaded, so other sources will be newer. We update the status now.
$operations[] = array('locale_translation_batch_status_compare', array());
return $operations;
}
......@@ -292,17 +292,17 @@ function locale_requirements($phase) {
$requirements = array();
if ($phase == 'runtime') {
$available_updates = array();
$updates_not_found = array();
$untranslated = array();
$languages = locale_translatable_language_list();
if ($languages) {
// Determine the status of the translation updates per lanuage.
$status = Drupal::state()->get('locale.translation_status');
$status = locale_translation_get_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;
if (empty($project_info->type)) {
$untranslated[$langcode] = $languages[$langcode]->name;
}
elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) {
$available_updates[$langcode] = $languages[$langcode]->name;
......@@ -310,7 +310,7 @@ function locale_requirements($phase) {
}
}
if ($available_updates || $updates_not_found) {
if ($available_updates || $untranslated) {
if ($available_updates) {
$requirements['locale_translation'] = array(
'title' => 'Translation update status',
......@@ -324,7 +324,7 @@ function locale_requirements($phase) {
'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'))),
'description' => t('Missing translations for: @languages. See the <a href="@updates">Available translation updates</a> page for more information.', array('@languages' => implode(', ', $untranslated), '@updates' => url('admin/reports/translations'))),
);
}
}
......
This diff is collapsed.
......@@ -502,14 +502,14 @@ function locale_translation_status_form($form, &$form_state) {
module_load_include('compare.inc', 'locale');
$updates = $options = array();
$languages_update = $languages_not_found = array();
$projects_update = 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 = Drupal::state()->get('locale.translation_status');
$status = locale_translation_get_status();
// Prepare information about projects which have available translation
// updates.
......@@ -517,7 +517,7 @@ function locale_translation_status_form($form, &$form_state) {
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)) {
if (empty($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,
......@@ -536,6 +536,7 @@ function locale_translation_status_form($form, &$form_state) {
'timestamp' => $recent->timestamp,
);
$languages_update[$langcode] = $langcode;
$projects_update[$project_info->name] = $project_info->name;
}
}
}
......@@ -579,6 +580,12 @@ function locale_translation_status_form($form, &$form_state) {
$empty = t('No translation status available. <a href="@check">Check manually</a>.', 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,
......@@ -622,6 +629,7 @@ function locale_translation_status_form_validate($form, &$form_state) {
function locale_translation_status_form_submit($form, &$form_state) {
module_load_include('fetch.inc', 'locale');
$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.
......@@ -637,7 +645,7 @@ function locale_translation_status_form_submit($form, &$form_state) {
batch_set($batch);
}
else {
$batch = locale_translation_batch_fetch_build(array(), $langcodes, $options);
$batch = locale_translation_batch_fetch_build($projects, $langcodes, $options);
batch_set($batch);
}
}
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment