summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2013-06-27 11:25:01 (GMT)
committer Alex Pott2013-06-27 11:25:01 (GMT)
commitc1f21810359878431af59e3156f81bec8a84de4f (patch)
tree9f70a56239bf93edd704344780fdbf9ba5805213
parentb07e8f077dbaa01b8f1c270b7c42883bf4a2e8a1 (diff)
Issue #1998056 by Sutharsan, clemens.tolboom, penyaskito: Automatically update interface translations using cron.
-rw-r--r--core/modules/locale/lib/Drupal/locale/Form/LocaleSettingsForm.php2
-rw-r--r--core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateBase.php296
-rw-r--r--core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateCronTest.php122
-rw-r--r--core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateInterfaceTest.php19
-rw-r--r--core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php350
-rw-r--r--core/modules/locale/locale.batch.inc380
-rw-r--r--core/modules/locale/locale.compare.inc90
-rw-r--r--core/modules/locale/locale.fetch.inc15
-rw-r--r--core/modules/locale/locale.install12
-rw-r--r--core/modules/locale/locale.module195
-rw-r--r--core/modules/locale/locale.pages.inc16
-rw-r--r--core/modules/locale/locale.translation.inc154
12 files changed, 848 insertions, 803 deletions
diff --git a/core/modules/locale/lib/Drupal/locale/Form/LocaleSettingsForm.php b/core/modules/locale/lib/Drupal/locale/Form/LocaleSettingsForm.php
index 4236bf2..5669d8f 100644
--- a/core/modules/locale/lib/Drupal/locale/Form/LocaleSettingsForm.php
+++ b/core/modules/locale/lib/Drupal/locale/Form/LocaleSettingsForm.php
@@ -32,8 +32,8 @@ class LocaleSettingsForm extends SystemConfigFormBase {
'#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'))),
);
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateBase.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateBase.php
new file mode 100644
index 0000000..ec9e74f
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateBase.php
@@ -0,0 +1,296 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\locale\Tests\LocaleUpdateTest.
+ */
+
+namespace Drupal\locale\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests for update translations.
+ */
+class LocaleUpdateBase extends WebTestBase {
+
+ /**
+ * The path of the translations directory where local translations are stored.
+ *
+ * @var string
+ */
+ protected $tranlations_directory;
+
+ /**
+ * Timestamp for an old translation.
+ *
+ * @var integer
+ */
+ protected $timestamp_old;
+
+ /**
+ * Timestamp for a medium aged translation.
+ *
+ * @var integer
+ */
+ protected $timestamp_medium;
+
+ /**
+ * Timestamp for a new translation.
+ *
+ * @var integer
+ */
+ protected $timestamp_new;
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('update', 'locale', 'locale_test');
+
+ function setUp() {
+ parent::setUp();
+ // Setup timestamps to identify old and new translation sources.
+ $this->timestamp_old = REQUEST_TIME - 300;
+ $this->timestamp_medium = REQUEST_TIME - 200;
+ $this->timestamp_new = REQUEST_TIME - 100;
+ $this->timestamp_now = REQUEST_TIME;
+ }
+
+ /**
+ * Sets the value of the default translations directory.
+ *
+ * @param string $path
+ * Path of the translations directory relative to the drupal installation
+ * directory.
+ */
+ protected function setTranslationsDirectory($path) {
+ $this->tranlations_directory = $path;
+ file_prepare_directory($path, FILE_CREATE_DIRECTORY);
+ config('locale.settings')->set('translation.path', $path)->save();
+ }
+
+ /**
+ * Adds a language.
+ *
+ * @param $langcode
+ * The language code of the language to add.
+ */
+ protected function addLanguage($langcode) {
+ $edit = array('predefined_langcode' => $langcode);
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+ drupal_static_reset('language_list');
+ $this->assertTrue(language_load($langcode), t('Language %langcode added.', array('%langcode' => $langcode)));
+ }
+
+ /**
+ * Creates a translation file and tests its timestamp.
+ *
+ * @param string $path
+ * Path of the file relative to the public file path.
+ * @param string $filename
+ * Name of the file to create.
+ * @param integer $timestamp
+ * Timestamp to set the file to. Defaults to current time.
+ * @param array $translations
+ * Array of source/target value translation strings. Only singular strings
+ * are supported, no plurals. No double quotes are allowed in source and
+ * translations strings.
+ */
+ protected function makePoFile($path, $filename, $timestamp = NULL, $translations = array()) {
+ $timestamp = $timestamp ? $timestamp : REQUEST_TIME;
+ $path = 'public://' . $path;
+ $text = '';
+ $po_header = <<<EOF
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 8\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
+
+EOF;
+
+ // Convert array of translations to Gettext source and translation strings.
+ if ($translations) {
+ foreach ($translations as $source => $target) {
+ $text .= 'msgid "'. $source . '"' . "\n";
+ $text .= 'msgstr "'. $target . '"' . "\n";
+ }
+ }
+
+ file_prepare_directory($path, FILE_CREATE_DIRECTORY);
+ $file = entity_create('file', array(
+ 'uid' => 1,
+ 'filename' => $filename,
+ 'uri' => $path . '/' . $filename,
+ 'filemime' => 'text/x-gettext-translation',
+ 'timestamp' => $timestamp,
+ 'status' => FILE_STATUS_PERMANENT,
+ ));
+ file_put_contents($file->getFileUri(), $po_header . $text);
+ touch(drupal_realpath($file->getFileUri()), $timestamp);
+ $file->save();
+ }
+
+ /**
+ * Setup the environment containing local and remote translation files.
+ *
+ * Update tests require a simulated environment for local and remote files.
+ * Normally remote files are located at a remote server (e.g. ftp.drupal.org).
+ * For testing we can not rely on this. A directory in the file system of the
+ * test site is designated for remote files and is addressed using an absolute
+ * URL. Because Drupal does not allow files with a po extension to be accessed
+ * (denied in .htaccess) the translation files get a _po extension. Another
+ * directory is designated for local translation files.
+ *
+ * The environment is set up with the following files. File creation times are
+ * set to create different variations in test conditions.
+ * contrib_module_one
+ * - remote file: timestamp new
+ * - local file: timestamp old
+ * contrib_module_two
+ * - remote file: timestamp old
+ * - local file: timestamp new
+ * contrib_module_three
+ * - remote file: timestamp old
+ * - local file: timestamp old
+ * custom_module_one
+ * - local file: timestamp new
+ * Time stamp of current translation set by setCurrentTranslations() is always
+ * timestamp medium. This makes it easy to predict which translation will be
+ * imported.
+ */
+ protected function setTranslationFiles() {
+ $config = config('locale.settings');
+
+ // A flag is set to let the locale_test module replace the project data with
+ // a set of test projects which match the below project files.
+ \Drupal::state()->set('locale.test_projects_alter', TRUE);
+
+ // Setup the environment.
+ $public_path = variable_get('file_public_path', conf_path() . '/files');
+ $this->setTranslationsDirectory($public_path . '/local');
+ $config->set('translation.default_filename', '%project-%version.%language._po')->save();
+
+ // Setting up sets of translations for the translation files.
+ $translations_one = array('January' => 'Januar_1', 'February' => 'Februar_1', 'March' => 'Marz_1');
+ $translations_two = array( 'February' => 'Februar_2', 'March' => 'Marz_2', 'April' => 'April_2');
+ $translations_three = array('April' => 'April_3', 'May' => 'Mai_3', 'June' => 'Juni_3');
+
+ // Add a number of files to the local file system to serve as remote
+ // translation server and match the project definitions set in
+ // locale_test_locale_translation_projects_alter().
+ $this->makePoFile('remote/8.x/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestamp_new, $translations_one);
+ $this->makePoFile('remote/8.x/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestamp_old, $translations_two);
+ $this->makePoFile('remote/8.x/contrib_module_three', 'contrib_module_three-8.x-1.0.de._po', $this->timestamp_old, $translations_three);
+
+ // Add a number of files to the local file system to serve as local
+ // translation files and match the project definitions set in
+ // locale_test_locale_translation_projects_alter().
+ $this->makePoFile('local', 'contrib_module_one-8.x-1.1.de._po', $this->timestamp_old, $translations_one);
+ $this->makePoFile('local', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestamp_new, $translations_two);
+ $this->makePoFile('local', 'contrib_module_three-8.x-1.0.de._po', $this->timestamp_old, $translations_three);
+ $this->makePoFile('local', 'custom_module_one.de.po', $this->timestamp_new);
+ }
+
+ /**
+ * Setup existing translations in the database and set up the status of
+ * existing translations.
+ */
+ protected function setCurrentTranslations() {
+ // Add non customized translations to the database.
+ $langcode = 'de';
+ $context = '';
+ $non_customized_translations = array(
+ 'March' => 'Marz',
+ 'June' => 'Juni',
+ );
+ foreach ($non_customized_translations as $source => $translation) {
+ $string = $this->container->get('locale.storage')->createString(array(
+ 'source' => $source,
+ 'context' => $context,
+ ))
+ ->save();
+ $this->container->get('locale.storage')->createTranslation(array(
+ 'lid' => $string->getId(),
+ 'language' => $langcode,
+ 'translation' => $translation,
+ 'customized' => LOCALE_NOT_CUSTOMIZED,
+ ))->save();
+ }
+
+ // Add customized translations to the database.
+ $customized_translations = array(
+ 'January' => 'Januar_customized',
+ 'February' => 'Februar_customized',
+ 'May' => 'Mai_customized',
+ );
+ foreach ($customized_translations as $source => $translation) {
+ $string = $this->container->get('locale.storage')->createString(array(
+ 'source' => $source,
+ 'context' => $context,
+ ))
+ ->save();
+ $this->container->get('locale.storage')->createTranslation(array(
+ 'lid' => $string->getId(),
+ 'language' => $langcode,
+ 'translation' => $translation,
+ 'customized' => LOCALE_CUSTOMIZED,
+ ))->save();
+ }
+
+ // Add a state of current translations in locale_files.
+ $default = array(
+ 'langcode' => $langcode,
+ 'uri' => '',
+ 'timestamp' => $this->timestamp_medium,
+ 'last_checked' => $this->timestamp_medium,
+ );
+ $data[] = array(
+ 'project' => 'contrib_module_one',
+ 'filename' => 'contrib_module_one-8.x-1.1.de._po',
+ 'version' => '8.x-1.1',
+ );
+ $data[] = array(
+ 'project' => 'contrib_module_two',
+ 'filename' => 'contrib_module_two-8.x-2.0-beta4.de._po',
+ 'version' => '8.x-2.0-beta4',
+ );
+ $data[] = array(
+ 'project' => 'contrib_module_three',
+ 'filename' => 'contrib_module_three-8.x-1.0.de._po',
+ 'version' => '8.x-1.0',
+ );
+ $data[] = array(
+ 'project' => 'custom_module_one',
+ 'filename' => 'custom_module_one.de.po',
+ 'version' => '',
+ );
+ foreach ($data as $file) {
+ $file = (object) array_merge($default, $file);
+ drupal_write_record('locale_file', $file);
+ }
+ }
+
+ /**
+ * Checks the translation of a string.
+ *
+ * @param string $source
+ * Translation source string
+ * @param string $translation
+ * Translation to check. Use empty string to check for a not existing
+ * translation.
+ * @param string $langcode
+ * Language code of the language to translate to.
+ * @param string $message
+ * (optional) A message to display with the assertion.
+ */
+ protected function assertTranslation($source, $translation, $langcode, $message = '') {
+ $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', array(':source' => $source, ':langcode' => $langcode))->fetchField();
+ $db_translation = $db_translation == FALSE ? '' : $db_translation;
+ $this->assertEqual($translation, $db_translation, $message ? $message : format_string('Correct translation of %source (%language)', array('%source' => $source, '%language' => $langcode)));
+ }
+}
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateCronTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateCronTest.php
new file mode 100644
index 0000000..04dd4fd
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateCronTest.php
@@ -0,0 +1,122 @@
+<?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');
+ }
+}
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateInterfaceTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateInterfaceTest.php
index 3c9d390..0a502b6 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateInterfaceTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateInterfaceTest.php
@@ -12,7 +12,7 @@ use Drupal\simpletest\WebTestBase;
/**
* Tests for the locale translation update status user interfaces.
*/
-class LocaleUpdateInterfaceTest extends WebTestBase {
+class LocaleUpdateInterfaceTest extends LocaleUpdateBase {
/**
* Modules to enable.
@@ -51,16 +51,13 @@ class LocaleUpdateInterfaceTest extends WebTestBase {
$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 @@ class LocaleUpdateInterfaceTest extends WebTestBase {
$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 @@ class LocaleUpdateInterfaceTest extends WebTestBase {
// 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.
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
index f85be0f..44bfabe 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
@@ -12,35 +12,7 @@ use Drupal\simpletest\WebTestBase;
/**
* Tests for update translations.
*/
-class LocaleUpdateTest extends WebTestBase {
-
- /**
- * The path of the translations directory where local translations are stored.
- *
- * @var string
- */
- private $tranlations_directory;
-
- /**
- * Timestamp for an old translation.
- *
- * @var integer
- */
- private $timestamp_old;
-
- /**
- * Timestamp for a medium aged translation.
- *
- * @var integer
- */
- private $timestamp_medium;
-
- /**
- * Timestamp for a new translation.
- *
- * @var integer
- */
- private $timestamp_new;
+class LocaleUpdateTest extends LocaleUpdateBase {
/**
* Modules to enable.
@@ -66,249 +38,7 @@ class LocaleUpdateTest extends WebTestBase {
// 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.
- $this->timestamp_old = REQUEST_TIME - 300;
- $this->timestamp_medium = REQUEST_TIME - 200;
- $this->timestamp_new = REQUEST_TIME - 100;
- $this->timestamp_now = REQUEST_TIME;
- }
-
- /**
- * Sets the value of the default translations directory.
- *
- * @param string $path
- * Path of the translations directory relative to the drupal installation
- * directory.
- */
- private function setTranslationsDirectory($path) {
- $this->tranlations_directory = $path;
- file_prepare_directory($path, FILE_CREATE_DIRECTORY);
- config('locale.settings')->set('translation.path', $path)->save();
- }
-
- /**
- * Adds a language.
- *
- * @param $langcode
- * The language code of the language to add.
- */
- function addLanguage($langcode) {
- $edit = array('predefined_langcode' => $langcode);
- $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
- drupal_static_reset('language_list');
- $this->assertTrue(language_load($langcode), t('Language %langcode added.', array('%langcode' => $langcode)));
- }
-
- /**
- * Creates a translation file and tests its timestamp.
- *
- * @param string $path
- * Path of the file relative to the public file path.
- * @param string $filename
- * Name of the file to create.
- * @param integer $timestamp
- * Timestamp to set the file to. Defaults to current time.
- * @param array $translations
- * Array of source/target value translation strings. Only singular strings
- * are supported, no plurals. No double quotes are allowed in source and
- * translations strings.
- */
- private function makePoFile($path, $filename, $timestamp = NULL, $translations = array()) {
- $timestamp = $timestamp ? $timestamp : REQUEST_TIME;
- $path = 'public://' . $path;
- $text = '';
- $po_header = <<<EOF
-msgid ""
-msgstr ""
-"Project-Id-Version: Drupal 8\\n"
-"MIME-Version: 1.0\\n"
-"Content-Type: text/plain; charset=UTF-8\\n"
-"Content-Transfer-Encoding: 8bit\\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
-
-EOF;
-
- // Convert array of translations to Gettext source and translation strings.
- if ($translations) {
- foreach ($translations as $source => $target) {
- $text .= 'msgid "'. $source . '"' . "\n";
- $text .= 'msgstr "'. $target . '"' . "\n";
- }
- }
-
- file_prepare_directory($path, FILE_CREATE_DIRECTORY);
- $file = entity_create('file', array(
- 'uid' => 1,
- 'filename' => $filename,
- 'uri' => $path . '/' . $filename,
- 'filemime' => 'text/x-gettext-translation',
- 'timestamp' => $timestamp,
- 'status' => FILE_STATUS_PERMANENT,
- ));
- file_put_contents($file->getFileUri(), $po_header . $text);
- touch(drupal_realpath($file->getFileUri()), $timestamp);
- $file->save();
- }
-
- /**
- * Setup the environment containting local and remote translation files.
- *
- * Update tests require a simulated environment for local and remote files.
- * Normally remote files are located at a remote server (e.g. ftp.drupal.org).
- * For testing we can not rely on this. A directory in the file system of the
- * test site is designated for remote files and is addressed using an absolute
- * URL. Because Drupal does not allow files with a po extension to be accessed
- * (denied in .htaccess) the translation files get a _po extension. Another
- * directory is designated for local translation files.
- *
- * The environment is set up with the following files. File creation times are
- * set to create different variations in test conditions.
- * contrib_module_one
- * - remote file: timestamp new
- * - local file: timestamp old
- * contrib_module_two
- * - remote file: timestamp old
- * - local file: timestamp new
- * contrib_module_three
- * - remote file: timestamp old
- * - local file: timestamp old
- * custom_module_one
- * - local file: timestamp new
- * Time stamp of current translation set by setCurrentTranslations() is always
- * timestamp medium. This makes it easy to predict which translation will be
- * imported.
- */
- private function setTranslationFiles() {
- $config = config('locale.settings');
-
- // A flag is set to let the locale_test module replace the project data with
- // a set of test projects which match the below project files.
- \Drupal::state()->set('locale.test_projects_alter', TRUE);
-
- // Setup the environment.
- $public_path = variable_get('file_public_path', conf_path() . '/files');
- $this->setTranslationsDirectory($public_path . '/local');
- $config->set('translation.default_filename', '%project-%version.%language._po')->save();
-
- // Setting up sets of translations for the translation files.
- $translations_one = array('January' => 'Januar_1', 'February' => 'Februar_1', 'March' => 'Marz_1');
- $translations_two = array( 'February' => 'Februar_2', 'March' => 'Marz_2', 'April' => 'April_2');
- $translations_three = array('April' => 'April_3', 'May' => 'Mai_3', 'June' => 'Juni_3');
-
- // Add a number of files to the local file system to serve as remote
- // translation server and match the project definitions set in
- // locale_test_locale_translation_projects_alter().
- $this->makePoFile('remote/8.x/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestamp_new, $translations_one);
- $this->makePoFile('remote/8.x/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestamp_old, $translations_two);
- $this->makePoFile('remote/8.x/contrib_module_three', 'contrib_module_three-8.x-1.0.de._po', $this->timestamp_old, $translations_three);
-
- // Add a number of files to the local file system to serve as local
- // translation files and match the project definitions set in
- // locale_test_locale_translation_projects_alter().
- $this->makePoFile('local', 'contrib_module_one-8.x-1.1.de._po', $this->timestamp_old, $translations_one);
- $this->makePoFile('local', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestamp_new, $translations_two);
- $this->makePoFile('local', 'contrib_module_three-8.x-1.0.de._po', $this->timestamp_old, $translations_three);
- $this->makePoFile('local', 'custom_module_one.de.po', $this->timestamp_new);
- }
-
- /**
- * Setup existing translations in the database and set up the status of
- * existing translations.
- */
- private function setCurrentTranslations() {
- // Add non customized translations to the database.
- $langcode = 'de';
- $context = '';
- $non_customized_translations = array(
- 'March' => 'Marz',
- 'June' => 'Juni',
- );
- foreach ($non_customized_translations as $source => $translation) {
- $string = $this->container->get('locale.storage')->createString(array(
- 'source' => $source,
- 'context' => $context,
- ))
- ->save();
- $target = $this->container->get('locale.storage')->createTranslation(array(
- 'lid' => $string->getId(),
- 'language' => $langcode,
- 'translation' => $translation,
- 'customized' => LOCALE_NOT_CUSTOMIZED,
- ))->save();
- }
-
- // Add customized translations to the database.
- $customized_translations = array(
- 'January' => 'Januar_customized',
- 'February' => 'Februar_customized',
- 'May' => 'Mai_customized',
- );
- foreach ($customized_translations as $source => $translation) {
- $string = $this->container->get('locale.storage')->createString(array(
- 'source' => $source,
- 'context' => $context,
- ))
- ->save();
- $target = $this->container->get('locale.storage')->createTranslation(array(
- 'lid' => $string->getId(),
- 'language' => $langcode,
- 'translation' => $translation,
- 'customized' => LOCALE_CUSTOMIZED,
- ))->save();
- }
-
- // Add a state of current translations in locale_files.
- $default = array(
- 'langcode' => $langcode,
- 'uri' => '',
- 'timestamp' => $this->timestamp_medium,
- 'last_checked' => $this->timestamp_medium,
- );
- $data[] = array(
- 'project' => 'contrib_module_one',
- 'filename' => 'contrib_module_one-8.x-1.1.de._po',
- 'version' => '8.x-1.1',
- );
- $data[] = array(
- 'project' => 'contrib_module_two',
- 'filename' => 'contrib_module_two-8.x-2.0-beta4.de._po',
- 'version' => '8.x-2.0-beta4',
- );
- $data[] = array(
- 'project' => 'contrib_module_three',
- 'filename' => 'contrib_module_three-8.x-1.0.de._po',
- 'version' => '8.x-1.0',
- );
- $data[] = array(
- 'project' => 'custom_module_one',
- 'filename' => 'custom_module_one.de.po',
- 'version' => '',
- );
- foreach ($data as $file) {
- $file = (object) array_merge($default, $file);
- drupal_write_record('locale_file', $file);
- }
- }
-
- /**
- * Checks the translation of a string.
- *
- * @param string $source
- * Translation source string
- * @param string $translation
- * Translation to check. Use empty string to check for a not existing
- * translation.
- * @param string $langcode
- * Language code of the language to translate to.
- * @param string $message
- * (optional) A message to display with the assertion.
- */
- function assertTranslation($source, $translation, $langcode, $message = '') {
- $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', array(':source' => $source, ':langcode' => $langcode))->fetchField();
- $db_translation = $db_translation == FALSE ? '' : $db_translation;
- $this->assertEqual($translation, $db_translation, $message ? $message : format_string('Correct translation of %source (%language)', array('%source' => $source, '%language' => $langcode)));
+ $this->addLanguage('de');
}
/**
@@ -384,7 +114,7 @@ EOF;
// Get status of translation sources at local file system.
$this->drupalGet('admin/reports/translations/check');
- $result = \Drupal::state()->get('locale.translation_status');
+ $result = locale_translation_get_status();
$this->assertEqual($result['contrib_module_one']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_one found');
$this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestamp_old, 'Translation timestamp found');
$this->assertEqual($result['contrib_module_two']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_two found');
@@ -400,8 +130,8 @@ EOF;
// Get status of translation sources at both local and remote locations.
$this->drupalGet('admin/reports/translations/check');
- $result = \Drupal::state()->get('locale.translation_status');
- $this->assertEqual($result['contrib_module_one']['de']->type, 'remote', 'Translation of contrib_module_one found');
+ $result = locale_translation_get_status();
+ $this->assertEqual($result['contrib_module_one']['de']->type, LOCALE_TRANSLATION_REMOTE, 'Translation of contrib_module_one found');
$this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestamp_new, 'Translation timestamp found');
$this->assertEqual($result['contrib_module_two']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_two found');
$this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestamp_new, 'Translation timestamp found');
@@ -417,7 +147,6 @@ EOF;
* Test conditions:
* - Source: remote and local files
* - Import overwrite: all existing translations
- * - Translation directory: available
*/
function testUpdateImportSourceRemote() {
$config = config('locale.settings');
@@ -440,7 +169,6 @@ EOF;
// 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');
@@ -448,7 +176,7 @@ EOF;
$this->drupalPost('admin/reports/translations', array(), t('Update translations'));
// Check if the translation has been updated, using the status cache.
- $status = \Drupal::state()->get('locale.translation_status');
+ $status = locale_translation_get_status();
$this->assertEqual($status['contrib_module_one']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_one found');
$this->assertEqual($status['contrib_module_two']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_two found');
$this->assertEqual($status['contrib_module_three']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_three found');
@@ -481,7 +209,6 @@ EOF;
* Test conditions:
* - Source: local files only
* - Import overwrite: all existing translations
- * - Translation directory: available
*/
function testUpdateImportSourceLocal() {
$config = config('locale.settings');
@@ -503,7 +230,7 @@ EOF;
$this->drupalPost('admin/reports/translations', array(), t('Update translations'));
// Check if the translation has been updated, using the status cache.
- $status = \Drupal::state()->get('locale.translation_status');
+ $status = locale_translation_get_status();
$this->assertEqual($status['contrib_module_one']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_one found');
$this->assertEqual($status['contrib_module_two']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_two found');
$this->assertEqual($status['contrib_module_three']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_three found');
@@ -531,68 +258,11 @@ EOF;
}
/**
- * Tests translation import without a translations directory.
- *
- * Test conditions:
- * - Source: remote and local files
- * - Import overwrite: all existing translations
- * - Translation directory: not available
- */
- function testUpdateImportWithoutDirectory() {
- $config = config('locale.settings');
-
- // Build the test environment.
- $this->setTranslationFiles();
- $this-> setCurrentTranslations();
- $config->set('translation.default_filename', '%project-%version.%language._po');
-
- // Set the update conditions for this test.
- $this->setTranslationsDirectory('');
- $edit = array(
- 'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
- 'overwrite' => LOCALE_TRANSLATION_OVERWRITE_ALL,
- );
- $this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration'));
-
- // Execute the translation update.
- $this->drupalGet('admin/reports/translations/check');
- $this->drupalPost('admin/reports/translations', array(), t('Update translations'));
-
- // Check if the translation has been updated, using the status cache.
- $status = \Drupal::state()->get('locale.translation_status');
- $this->assertEqual($status['contrib_module_one']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_one found');
- $this->assertEqual($status['contrib_module_two']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_two found');
- $this->assertEqual($status['contrib_module_three']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_three found');
-
- // Check the new translation status.
- // The static cache needs to be flushed first to get the most recent data
- // from the database. The function was called earlier during this test.
- drupal_static_reset('locale_translation_get_file_history');
- $history = locale_translation_get_file_history();
- $this->assertTrue($history['contrib_module_one']['de']->timestamp >= $this->timestamp_now, 'Translation of contrib_module_one is imported');
- $this->assertTrue($history['contrib_module_one']['de']->last_checked >= $this->timestamp_now, 'Translation of contrib_module_one is updated');
- $this->assertEqual($history['contrib_module_two']['de']->timestamp, $this->timestamp_medium, 'Translation of contrib_module_two is imported');
- $this->assertEqual($history['contrib_module_two']['de']->last_checked, $this->timestamp_medium, 'Translation of contrib_module_two is updated');
- $this->assertEqual($history['contrib_module_three']['de']->timestamp, $this->timestamp_medium, 'Translation of contrib_module_three is not imported');
- $this->assertEqual($history['contrib_module_three']['de']->last_checked, $this->timestamp_medium, 'Translation of contrib_module_three is not updated');
-
- // Check whether existing translations have (not) been overwritten.
- $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_1', 'Translation of January');
- $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_1', 'Translation of February');
- $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_1', 'Translation of March');
- $this->assertEqual(t('May', array(), array('langcode' => 'de')), 'Mai_customized', 'Translation of May');
- $this->assertEqual(t('June', array(), array('langcode' => 'de')), 'Juni', 'Translation of June');
- $this->assertEqual(t('Monday', array(), array('langcode' => 'de')), 'Montag', 'Translation of Monday');
- }
-
- /**
- * Tests translation import with a translations directory and only overwrite
- * non-customized translations.
+ * Tests translation import and only overwrite non-customized translations.
*
* Test conditions:
* - Source: remote and local files
* - Import overwrite: only overwrite non-customized translations
- * - Translation directory: available
*/
function testUpdateImportModeNonCustomized() {
$config = config('locale.settings');
@@ -624,13 +294,11 @@ EOF;
}
/**
- * Tests translation import with a translations directory and don't overwrite
- * any translation.
+ * Tests translation import and don't overwrite any translation.
*
* Test conditions:
* - Source: remote and local files
* - Import overwrite: don't overwrite any existing translation
- * - Translation directory: available
*/
function testUpdateImportModeNone() {
$config = config('locale.settings');
diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc
index 352b0a5..91b7555 100644
--- a/core/modules/locale/locale.batch.inc
+++ b/core/modules/locale/locale.batch.inc
@@ -16,149 +16,71 @@ use Guzzle\Http\Exception\RequestException;
require_once __DIR__ . '/locale.translation.inc';
/**
- * Batch operation callback: Check the availability of a remote po file.
+ * Batch operation callback: Check status of a remote and local po file.
*
- * Checks the presence and creation time of one po file per batch process. The
- * file URL and timestamp are stored.
+ * Checks the presence and creation time po translation files in located at
+ * remote server location and local file system.
*
- * @param array $source
- * A translation source object of the project for which to check the state of
- * a remote po file.
+ * @param string $project
+ * Machine name of the project for which to check the translation status.
+ * @param string $langcode
+ * Language code of the language for which to check the translation.
+ * @param array $options
+ * Optional, an array with options that can have the following elements:
+ * - 'finish_feedback': Whether or not to give feedback to the user when the
+ * batch is finished. Optional, defaults to TRUE.
+ * - 'use_remote': Whether or not to check the remote translation file.
+ * Optional, defaults to TRUE.
* @param array $context
- * The batch context array. The collected state is stored in the 'results'
- * parameter of the context.
- *
- * @see locale_translation_batch_status_fetch_local()
- * @see locale_translation_batch_status_compare()
+ * The batch context.
*/
-function locale_translation_batch_status_fetch_remote($source, &$context) {
- // Check the translation file at the remote server and update the source
- // data with the remote status.
- if (isset($source->files[LOCALE_TRANSLATION_REMOTE])) {
- $remote_file = $source->files[LOCALE_TRANSLATION_REMOTE];
- $result = locale_translation_http_check($remote_file->uri);
+function locale_translation_batch_status_check($project, $langcode, $options = array(), &$context) {
+ $failure = $checked = FALSE;
+ $options += array(
+ 'finish_feedback' => TRUE,
+ 'use_remote' => TRUE,
+ );
+ $source = locale_translation_get_status(array($project), array($langcode));
+ $source = $source[$project][$langcode];
+
+ // Check the status of local translation files.
+ if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) {
+ if ($file = locale_translation_source_check_file($source)) {
+ locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
+ }
+ $checked = TRUE;
+ }
- if ($result) {
+ // Check the status of remote translation files.
+ if ($options['use_remote'] && isset($source->files[LOCALE_TRANSLATION_REMOTE])) {
+ $remote_file = $source->files[LOCALE_TRANSLATION_REMOTE];
+ if ($result = locale_translation_http_check($remote_file->uri)) {
// Update the file object with the result data. In case of a redirect we
- // store the resulting uri. If a file is not found we don't update the
- // file object, and store it unchanged.
+ // store the resulting uri.
if (isset($result['last_modified'])) {
$remote_file->uri = isset($result['location']) ? $result['location'] : $remote_file->uri;
$remote_file->timestamp = $result['last_modified'];
- $source->files[LOCALE_TRANSLATION_REMOTE] = $remote_file;
+ locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_REMOTE, $remote_file);
}
- // Record success.
- $context['results']['files'][$source->name] = $source->name;
+ // @todo What to do with when the file is not found (404)? To prevent
+ // re-checking within the TTL (1day, 1week) we can set a last_checked
+ // timestamp or cache the result.
+ $checked = TRUE;
}
else {
- // An error occured when checking the file. Record the failure for
- // reporting at the end of the batch.
- $context['results']['failed_files'][] = $source->name;
+ $failure = TRUE;
}
- $context['results']['sources'][$source->name][$source->langcode] = $source;
- $context['message'] = t('Checked translation for %project.', array('%project' => $source->project));
}
-}
-
-/**
- * Batch operation callback: Check the availability of local po files.
- *
- * Checks the presence and creation time of po files in the local file system.
- * The file path and the timestamp are stored.
- *
- * @param array $sources
- * Array of translation source objects of projects for which to check the
- * state of local po files.
- * @param array $context
- * The batch context array. The collected state is stored in the 'results'
- * parameter of the context.
- *
- * @see locale_translation_batch_status_fetch_remote()
- * @see locale_translation_batch_status_compare()
- */
-function locale_translation_batch_status_fetch_local($sources, &$context) {
- // Get the status of local translation files and store the result data in the
- // batch results for later processing.
- foreach ($sources as $source) {
- if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) {
- locale_translation_source_check_file($source);
- // If remote data was collected before, we merge it into the newly
- // collected result.
- if (isset($context['results']['sources'][$source->name][$source->langcode])) {
- $source->files[LOCALE_TRANSLATION_REMOTE] = $context['results']['sources'][$source->name][$source->langcode]->files[LOCALE_TRANSLATION_REMOTE];
- }
-
- // Record success and store the updated source data.
- $context['results']['files'][$source->name] = $source->name;
- $context['results']['sources'][$source->name][$source->langcode] = $source;
- }
+ // Provide user feedback and record success or failure for reporting at the
+ // end of the batch.
+ if ($options['finish_feedback'] && $checked) {
+ $context['results']['files'][] = $source->name;
}
- $context['message'] = t('Checked all translations.');
-}
-
-/**
- * Batch operation callback: Compare states and store the result.
- *
- * In the preceding batch processes data of remote and local translation sources
- * is collected. Here we compare the collected results and update the source
- * object with the data of the most recent translation file. The end result is
- * stored in the 'locale.translation_status' state variable. Other
- * processes can collect this data after the batch process is completed.
- *
- * @param array $context
- * The batch context array. The 'results' element contains a structured array
- * of project data with languages, local and remote source data.
- *
- * @see locale_translation_batch_status_fetch_remote()
- * @see locale_translation_batch_status_fetch_local()
- */
-function locale_translation_batch_status_compare(&$context) {
- $history = locale_translation_get_file_history();
- $results = array();
-
- if (isset($context['results']['sources'])) {
- foreach ($context['results']['sources'] as $project => $langcodes) {
- foreach ($langcodes as $langcode => $source) {
- $local = isset($source->files[LOCALE_TRANSLATION_LOCAL]) ? $source->files[LOCALE_TRANSLATION_LOCAL] : NULL;
- $remote = isset($source->files[LOCALE_TRANSLATION_REMOTE]) ? $source->files[LOCALE_TRANSLATION_REMOTE] : NULL;
-
- // The available translation files are compared and data of the most
- // recent file is used to update the source object.
- $file = _locale_translation_source_compare($local, $remote) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $remote : $local;
- if (isset($file->timestamp)) {
- $source->type = $file->type;
- $source->timestamp = $file->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[$project][$langcode] = $source;
- }
- }
- $context['message'] = t('Updated translation status.');
+ if ($failure && !$checked) {
+ $context['results']['failed_files'][] = $source->name;
}
- locale_translation_status_save($results);
+ $context['message'] = t('Checked translation for %project.', array('%project' => $source->project));
}
/**
@@ -182,7 +104,7 @@ function locale_translation_batch_status_finished($success, $results) {
}
if (isset($results['files'])) {
drupal_set_message(format_plural(
- count($results['sources']),
+ count($results['files']),
'Checked available interface translation updates for one project.',
'Checked available interface translation updates for @count projects.'
));
@@ -190,6 +112,7 @@ function locale_translation_batch_status_finished($success, $results) {
if (!isset($results['failed_files']) && !isset($results['files'])) {
drupal_set_message(t('Nothing to check.'));
}
+ Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
}
else {
drupal_set_message(t('An error occurred trying to check available interface translation updates.'), 'error');
@@ -197,63 +120,32 @@ function locale_translation_batch_status_finished($success, $results) {
}
/**
- * Loads translation source data for the projects to be updated.
- *
- * Source data is loaded from cache and stored in the context results array.
- * Source data contains the translations status per project / per language
- * and whether translation updates are available and where the updates can be
- * retrieved from. The data is stored in the $context['results'] parameter
- * so that other batch operations can take this data as input for their
- * operation.
- *
- * @see locale_translation_batch_fetch_download()
- * @see locale_translation_batch_fetch_import()
- * @see locale_translation_batch_fetch_update_status()
- * @see locale_translation_batch_status_compare()
- */
-function locale_translation_batch_fetch_sources($projects, $langcodes, &$context) {
- $context['results']['input'] = locale_translation_load_sources($projects, $langcodes);
-
- // If this batch operation is preceded by the status check operations, the
- // results of those operation are stored in the context. We remove them here
- // to keep the result records clean.
- unset($context['results']['files']);
- unset($context['results']['failed_files']);
-}
-
-/**
* Batch operation: Download a remote translation file.
*
- * This operation downloads a remote gettext file and saves it in the temporary
- * directory. The remote file URL is taken from the input data in
- * $context['results']['input']. The result of the operation is stored in
- * $context['results']['sources'] and contains the URL of the temporary file.
+ * Downloads a remote gettext file into the translations directory. When
+ * successfully the translation status is updated.
*
* @param object $project
* Source object of the translatable project.
* @param string $langcode
* Language code.
- * @param $context
- * Batch context array.
+ * @param array $context
+ * The batch context.
*
- * @see locale_translation_batch_fetch_sources()
* @see locale_translation_batch_fetch_import()
- * @see locale_translation_batch_fetch_update_status()
- * @see locale_translation_batch_status_compare()
*/
function locale_translation_batch_fetch_download($project, $langcode, &$context) {
- $sources = $context['results']['input'];
- if (isset($sources[$project . ':' . $langcode])) {
- $source = $sources[$project . ':' . $langcode];
+ $sources = locale_translation_get_status(array($project), array($langcode));
+ if (isset($sources[$project][$langcode])) {
+ $source = $sources[$project][$langcode];
if (isset($source->type) && $source->type == LOCALE_TRANSLATION_REMOTE) {
- if ($file = locale_translation_download_source($source->files[LOCALE_TRANSLATION_REMOTE])) {
+ if ($file = locale_translation_download_source($source->files[LOCALE_TRANSLATION_REMOTE], 'translations://')) {
$context['message'] = t('Downloaded translation for %project.', array('%project' => $source->project));
- $source->files[LOCALE_TRANSLATION_DOWNLOADED] = $file;
+ locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file);
}
else {
$context['results']['failed_files'][] = $source->files[LOCALE_TRANSLATION_REMOTE];
}
- $context['results']['sources'][$project][$langcode] = $source;
}
}
}
@@ -261,12 +153,8 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context)
/**
* Batch process: Import translation file.
*
- * This batch operation imports either a local gettext file or a downloaded
- * remote gettext file. In case of a downloaded file the location of the
- * temporary file is found in the $context['results']['sources']. The temporary
- * file will be deleted after importing or will be moved to the local
- * translations directory. In case of a local file the file will just be
- * imported.
+ * Imports a gettext file from the translation directory. When successfully the
+ * translation status is updated.
*
* @param object $project
* Source object of the translatable project.
@@ -274,42 +162,25 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context)
* Language code.
* @param array $options
* Array of import options.
- * @param $context
- * Batch context array.
+ * @param array $context
+ * The batch context.
*
* @see locale_translate_batch_import_files()
- * @see locale_translation_batch_fetch_sources()
* @see locale_translation_batch_fetch_download()
- * @see locale_translation_batch_fetch_update_status()
- * @see locale_translation_batch_status_compare()
*/
function locale_translation_batch_fetch_import($project, $langcode, $options, &$context) {
- $sources = $context['results']['input'];
- if (isset($sources[$project . ':' . $langcode])) {
- $source = $sources[$project . ':' . $langcode];
+ $sources = locale_translation_get_status(array($project), array($langcode));
+ if (isset($sources[$project][$langcode])) {
+ $source = $sources[$project][$langcode];
if (isset($source->type)) {
if ($source->type == LOCALE_TRANSLATION_REMOTE || $source->type == LOCALE_TRANSLATION_LOCAL) {
-
- // If we are working on a remote file we will import the downloaded
- // file. If the file was local just mark the result as such.
- if ($source->type == LOCALE_TRANSLATION_REMOTE) {
- if (isset($context['results']['sources'][$source->project][$source->langcode]->files[LOCALE_TRANSLATION_DOWNLOADED])) {
- $import_type = LOCALE_TRANSLATION_DOWNLOADED;
- $source_result = $context['results']['sources'][$source->project][$source->langcode];
- }
- }
- else {
- $import_type = LOCALE_TRANSLATION_LOCAL;
- $source_result = $source;
- }
-
- $file = $source_result->files[$import_type];
+ $file = $source->files[LOCALE_TRANSLATION_LOCAL];
module_load_include('bulk.inc', 'locale');
$options += array(
'message' => t('Importing translation for %project.', array('%project' => $source->project)),
);
// Import the translation file. For large files the batch operations is
- // progressive and will be called repeatedly untill finished.
+ // progressive and will be called repeatedly until finished.
locale_translate_batch_import($file, $options, $context);
// The import is finished.
@@ -318,96 +189,13 @@ function locale_translation_batch_fetch_import($project, $langcode, $options, &$
if (isset($context['results']['files'][$file->uri])) {
$context['message'] = t('Imported translation for %project.', array('%project' => $source->project));
- // Keep the data of imported source. In the following batch
- // operation it will be saved in the {locale_file} table.
- $source_result->files[LOCALE_TRANSLATION_IMPORTED] = $source_result->files[$source->type];
-
- // Downloaded files are stored in the temporary files directory. If
- // files should be kept locally, they will be moved to the local
- // translations after successfull import. Otherwise the temporary
- // file is deleted after being imported.
- if ($import_type == LOCALE_TRANSLATION_DOWNLOADED && config('locale.settings')->get('translation.path') && isset($source_result->files[LOCALE_TRANSLATION_LOCAL])) {
- if (file_unmanaged_move($file->uri, $source_result->files[LOCALE_TRANSLATION_LOCAL]->uri, FILE_EXISTS_REPLACE)) {
- // The downloaded file is now moved to the local file location.
- // From this point forward we can treat it as if we imported a
- // local file.
- $import_type = LOCALE_TRANSLATION_LOCAL;
- }
- }
- // The downloaded file is imported but will not be stored locally.
- // Store the timestamp and delete the file.
- if ($import_type == LOCALE_TRANSLATION_DOWNLOADED) {
- $timestamp = filemtime($source_result->files[$import_type]->uri);
- $source_result->files[LOCALE_TRANSLATION_IMPORTED]->timestamp = $timestamp;
- $source_result->files[LOCALE_TRANSLATION_IMPORTED]->last_checked = REQUEST_TIME;
- file_unmanaged_delete($file->uri);
- }
- // If the translation file is stored in the local directory. The
- // timestamp of the file is stored.
- if ($import_type == LOCALE_TRANSLATION_LOCAL) {
- $timestamp = filemtime($source_result->files[$import_type]->uri);
- $source_result->files[LOCALE_TRANSLATION_LOCAL]->timestamp = $timestamp;
- $source_result->files[LOCALE_TRANSLATION_IMPORTED]->timestamp = $timestamp;
- $source_result->files[LOCALE_TRANSLATION_IMPORTED]->last_checked = REQUEST_TIME;
-
- }
+ // Save the data of imported source into the {locale_file} table and
+ // update the current translation status.
+ locale_translation_status_save($project, $langcode, LOCALE_TRANSLATION_CURRENT, $source->files[LOCALE_TRANSLATION_LOCAL]);
}
- else {
- // File import failed. We can delete the temporary file.
- if ($import_type == LOCALE_TRANSLATION_DOWNLOADED) {
- file_unmanaged_delete($file->uri);
- }
- }
- }
- $context['results']['sources'][$source->project][$source->langcode] = $source_result;
- }
- }
- }
-}
-
-/**
- * Batch process: Update the download history table.
- *
- * This batch process updates the {local_file} table with the data of imported
- * gettext files. Import data is taken from $context['results']['sources'].
- *
- * @param $context
- * Batch context array.
- *
- * @see locale_translation_batch_fetch_sources()
- * @see locale_translation_batch_fetch_download()
- * @see locale_translation_batch_fetch_import()
- * @see locale_translation_batch_status_compare()
- */
-function locale_translation_batch_fetch_update_status(&$context) {
- $results = array();
-
- if (isset($context['results']['sources'])) {
- foreach ($context['results']['sources'] as $project => $langcodes) {
- foreach ($langcodes as $langcode => $source) {
-
- // Store the state of the imported translations in {locale_file} table.
- // During the batch execution the data of the imported files is
- // temporary stored in $context['results']['sources']. Now it will be
- // stored in the database. Afterwards the temporary import and download
- // data can be deleted.
- if (isset($source->files[LOCALE_TRANSLATION_IMPORTED])) {
- $file = $source->files[LOCALE_TRANSLATION_IMPORTED];
- locale_translation_update_file_history($file);
- unset($source->files[LOCALE_TRANSLATION_IMPORTED]);
}
- unset($source->files[LOCALE_TRANSLATION_DOWNLOADED]);
-
- // The source data is now up to date. Data of local and/or remote source
- // file is up to date including an updated time stamp. In a next batch
- // operation this can be used to update the translation status.
- $context['results']['sources'][$project][$langcode] = $source;
}
}
- $context['message'] = t('Updated translations.');
-
- // The file history has changed, flush the static cache now.
- drupal_static_reset('locale_translation_get_file_history');
}
}
@@ -421,6 +209,9 @@ function locale_translation_batch_fetch_update_status(&$context) {
*/
function locale_translation_batch_fetch_finished($success, $results) {
module_load_include('bulk.inc', 'locale');
+ if ($success) {
+ Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
+ }
return locale_translate_batch_finished($success, $results);
}
@@ -478,9 +269,7 @@ function locale_translation_http_check($uri) {
}
/**
- * Downloads source file from a remote server.
- *
- * The downloaded file is stored in the temporary files directory.
+ * Downloads a translation file from a remote server.
*
* @param object $source_file
* Source file object with at least:
@@ -489,21 +278,22 @@ function locale_translation_http_check($uri) {
* - "langcode": Translation language.
* - "version": Project version.
* - "filename": File name.
+ * @param string $directory
+ * Directory where the downloaded file will be saved. Defaults to the
+ * temporary file path.
*
* @return object
* File object if download was successful. FALSE on failure.
*/
-function locale_translation_download_source($source_file) {
- if ($uri = system_retrieve_file($source_file->uri, 'temporary://')) {
- $file = new stdClass();
- $file->project = $source_file->project;
- $file->langcode = $source_file->langcode;
- $file->version = $source_file->version;
- $file->type = LOCALE_TRANSLATION_DOWNLOADED;
+function locale_translation_download_source($source_file, $directory = 'temporary://') {
+ if ($uri = system_retrieve_file($source_file->uri, $directory)) {
+ $file = clone($source_file);
+ $file->type = LOCALE_TRANSLATION_LOCAL;
$file->uri = $uri;
- $file->filename = $source_file->filename;
+ $file->directory = $directory;
+ $file->timestamp = filemtime($uri);
return $file;
}
- watchdog('locale', 'Unable to download translation file @uri.', array('@uri' => $source->files[LOCALE_TRANSLATION_REMOTE]->uri), WATCHDOG_ERROR);
+ watchdog('locale', 'Unable to download translation file @uri.', array('@uri' => $source_file->uri), WATCHDOG_ERROR);
return FALSE;
}
diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc
index 66411d8..3cbaac5 100644
--- a/core/modules/locale/locale.compare.inc
+++ b/core/modules/locale/locale.compare.inc
@@ -207,31 +207,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.
*
* @param array $projects
@@ -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);
}
diff --git a/core/modules/locale/locale.fetch.inc b/core/modules/locale/locale.fetch.inc
index da55056..9568691 100644
--- a/core/modules/locale/locale.fetch.inc
+++ b/core/modules/locale/locale.fetch.inc
@@ -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;
}
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index 92b28ea..eac6fa4 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -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'))),
);
}
}
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 460e5e5..1e605fe 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -127,16 +127,6 @@ const LOCALE_TRANSLATION_LOCAL = 'local';
const LOCALE_TRANSLATION_CURRENT = 'current';
/**
- * Translation source is a downloaded file.
- */
-const LOCALE_TRANSLATION_DOWNLOADED = 'download';
-
-/**
- * Translation source is an imported file.
- */
-const LOCALE_TRANSLATION_IMPORTED = 'import';
-
-/**
* Implements hook_help().
*/
function locale_help($path, $arg) {
@@ -434,6 +424,92 @@ function locale_themes_disabled($themes) {
}
/**
+ * Implements hook_cron().
+ *
+ * @see drupal_cron_run()
+ * @see locale_queue_info()
+ */
+function locale_cron() {
+ // Update translations only when an update frequency was set by the admin
+ // and a translatable language was set.
+ // Update tasks are added to the queue here but processed by Drupal's cron
+ // using the cron worker defined in locale_queue_info().
+ if ($frequency = config('locale.settings')->get('translation.update_interval_days') && locale_translatable_language_list()) {
+ module_load_include('translation.inc', 'locale');
+ locale_cron_fill_queue();
+ }
+}
+
+/**
+ * Implements hook_queue_info().
+ */
+function locale_queue_info() {
+ $queues['locale_translation'] = array(
+ 'title' => t('Update translations'),
+ 'worker callback' => 'locale_translation_worker',
+ 'cron' => array(
+ 'time' => 30,
+ ),
+ );
+ return $queues;
+}
+
+/**
+ * Callback: Executes interface translation queue tasks.
+ *
+ * The translation update functions executed here are batch operations which
+ * are also used in translation update batches. The batch functions may need to
+ * be executed multiple times to complete their task, typically this is the
+ * translation import function. When a batch function is not finished, a new
+ * queue task is created and added to the end of the queue. The batch context
+ * data is needed to continue the batch task is stored in the queue with the
+ * queue data.
+ *
+ * @param array $data
+ * Queue data array containing:
+ * - Function name.
+ * - Array of function arguments. Optionally contains the batch context data.
+ *
+ * @see locale_queue_info()
+ */
+function locale_translation_worker($data) {
+ module_load_include('batch.inc', 'locale');
+ list($function, $args) = $data;
+
+ // We execute batch operation functions here to check, download and import the
+ // translation files. Batch functions use a context variable as last argument
+ // which is passed by reference. When a batch operation is called for the
+ // first time a default batch context is created. When called iterative
+ // (usually the batch import function) the batch context is passed through via
+ // the queue and is part of the $data.
+ $last = count($args) - 1;
+ if (!is_array($args[$last]) || !isset($args[$last]['finished'])) {
+ $batch_context = array(
+ 'sandbox' => array(),
+ 'results' => array(),
+ 'finished' => 1,
+ 'message' => '',
+ );
+ }
+ else {
+ $batch_context = $args[$last];
+ unset ($args[$last]);
+ }
+ $args = array_merge($args, array(&$batch_context));
+
+ // Call the batch operation function.
+ call_user_func_array($function, $args);
+
+ // If the batch operation is not finished we create a new queue task to
+ // continue the task. This is typically the translation import task.
+ if ($batch_context['finished'] < 1) {
+ unset($batch_context['strings']);
+ $queue = Drupal::queue('locale_translation', TRUE);
+ $queue->createItem(array($function, $args));
+ }
+}
+
+/**
* Imports translations when new modules or themes are installed.
*
* This function will start a batch to import translations for the added
@@ -444,7 +520,6 @@ function locale_themes_disabled($themes) {
* translations for, indexed by type.
*/
function locale_system_update(array $components) {
-
$components += array('module' => array(), 'theme' => array());
$list = array_merge($components['module'], $components['theme']);
@@ -768,6 +843,7 @@ function locale_form_system_file_system_settings_alter(&$form, $form_state) {
'#default_value' => config('locale.settings')->get('translation.path'),
'#maxlength' => 255,
'#description' => t('A local file system path where interface translation files will be stored.'),
+ '#required' => TRUE,
'#after_build' => array('system_check_directory'),
'#weight' => 10,
);
@@ -830,7 +906,7 @@ function locale_translation_get_file_history() {
// Get file history from the database.
$result = db_query('SELECT project, langcode, filename, version, uri, timestamp, last_checked FROM {locale_file}');
foreach ($result as $file) {
- $file->type = LOCALE_TRANSLATION_CURRENT;
+ $file->type = $file->timestamp ? LOCALE_TRANSLATION_CURRENT : '';
$history[$file->project][$file->langcode] = $file;
}
}
@@ -856,7 +932,11 @@ function locale_translation_update_file_history($file) {
else {
$update = array();
}
- return drupal_write_record('locale_file', $file, $update);
+ $result = drupal_write_record('locale_file', $file, $update);
+ // The file history has changed, flush the static cache now.
+ // @todo Can we make this more fine grained?
+ drupal_static_reset('locale_translation_get_file_history');
+ return $result;
}
/**
@@ -880,27 +960,92 @@ function locale_translation_file_history_delete($projects = array(), $langcodes
}
/**
+ * Gets the current translation status.
+ *
+ * @todo What is 'translation status'?
+ */
+function locale_translation_get_status($projects = NULL, $langcodes = NULL) {
+ $result = array();
+ $status = Drupal::state()->get('locale.translation_status');
+ module_load_include('translation.inc', 'locale');
+ $projects = $projects ? $projects : array_keys(locale_translation_get_projects());
+ $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
+
+ // Get the translation status of each project-language combination. If no
+ // status was stored, a new translation source is created.
+ foreach ($projects as $project) {
+ foreach ($langcodes as $langcode) {
+ if (isset($status[$project][$langcode])) {
+ $result[$project][$langcode] = $status[$project][$langcode];
+ }
+ else {
+ $sources = locale_translation_build_sources(array($project), array($langcode));
+ if (isset($sources[$project][$langcode])) {
+ $result[$project][$langcode] = $sources[$project][$langcode];
+ }
+ }
+ }
+ }
+ return $result;
+}
+
+/**
* Saves the status of translation sources in static cache.
*
+ * @param string $project
+ * Machine readable project name.
+ * @param string $langcode
+ * Language code.
+ * @param string $type
+ * Type of data to be stored.
* @param array $data
- * Array of translation source data, structured by project name and langcode.
+ * File object also containing timestamp when the translation is last updated.
*/
-function locale_translation_status_save($data) {
+function locale_translation_status_save($project, $langcode, $type, $data) {
// Followup issue: http://drupal.org/node/1842362
// Split status storage per module/language and expire individually. This will
// improve performance for large sites.
- $status = Drupal::state()->get('locale.translation_status');
- $status = empty($status) ? array() : $status;
- // Merge the new data into the existing structured status array.
- foreach ($data as $project => $languages) {
- foreach ($languages as $langcode => $source) {
- $status[$project][$langcode] = $source;
+ // Load the translation status or build it if not already available.
+ module_load_include('translation.inc', 'locale');
+ $status = locale_translation_get_status();
+ if (empty($status)) {
+ $projects = locale_translation_get_projects(array($project));
+ if (isset($projects[$project])) {
+ $status[$project][$langcode] = locale_translation_source_build($projects[$project], $langcode);
}
}
- Drupal::state()->set('locale.translation_status', $status);
- Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
+ // Merge the new status data with the existing status.
+ if (isset($status[$project][$langcode])) {
+ switch ($type) {
+ case LOCALE_TRANSLATION_REMOTE:
+ case LOCALE_TRANSLATION_LOCAL:
+ // Add the source data to the status array.
+ $status[$project][$langcode]->files[$type] = $data;
+
+ // Check if this translation is the most recent one. Set timestamp and
+ // data type of the most recent translation source.
+ if (isset($data->timestamp) && $data->timestamp) {
+ if ($data->timestamp > $status[$project][$langcode]->timestamp) {
+ $status[$project][$langcode]->timestamp = $data->timestamp;
+ $status[$project][$langcode]->last_checked = REQUEST_TIME;
+ $status[$project][$langcode]->type = $type;
+ }
+ }
+ break;
+ case LOCALE_TRANSLATION_CURRENT:
+ $data->last_checked = REQUEST_TIME;
+ $status[$project][$langcode]->timestamp = $data->timestamp;
+ $status[$project][$langcode]->last_checked = $data->last_checked;
+ $status[$project][$langcode]->type = $type;
+ locale_translation_update_file_history($data);
+ break;
+ }
+
+ Drupal::state()->set('locale.translation_status', $status);
+ Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
+ }
}
/**
@@ -910,7 +1055,7 @@ function locale_translation_status_save($data) {
* Language code(s) to be deleted from the cache.
*/
function locale_translation_status_delete_languages($langcodes) {
- if ($status = Drupal::state()->get('locale.translation_status')) {
+ if ($status = locale_translation_get_status()) {
foreach ($status as $project => $languages) {
foreach ($languages as $langcode => $source) {
if (in_array($langcode, $langcodes)) {
@@ -929,7 +1074,7 @@ function locale_translation_status_delete_languages($langcodes) {
* Project name(s) to be deleted from the cache.
*/
function locale_translation_status_delete_projects($projects) {
- $status = Drupal::state()->get('locale.translation_status');
+ $status = locale_translation_get_status();
foreach ($status as $project => $languages) {
if (in_array($project, $projects)) {
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 51a9e3a..eb39f3c 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -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);
}
}
diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc
index 99a8559..3c77ff0 100644
--- a/core/modules/locale/locale.translation.inc
+++ b/core/modules/locale/locale.translation.inc
@@ -104,12 +104,12 @@ function locale_translation_load_sources($projects = NULL, $langcodes = NULL) {
$langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
// Load source data from locale_translation_status cache.
- $status = Drupal::state()->get('locale.translation_status');
+ $status = locale_translation_get_status();
// Use only the selected projects and languages for update.
foreach($projects as $project) {
foreach ($langcodes as $langcode) {
- $sources[$project . ':' . $langcode] = isset($status[$project][$langcode]) ? $status[$project][$langcode] : NULL;
+ $sources[$project][$langcode] = isset($status[$project][$langcode]) ? $status[$project][$langcode] : NULL;
}
}
return $sources;
@@ -124,7 +124,7 @@ function locale_translation_load_sources($projects = NULL, $langcodes = NULL) {
* Array of language codes. Defaults to all translatable languages.
*
* @return array
- * Array of source objects. Keyed with <project name>:<language code>.
+ * Array of source objects. Keyed by project name and language code.
*
* @see locale_translation_source_build()
*/
@@ -136,7 +136,7 @@ function locale_translation_build_sources($projects = array(), $langcodes = arra
foreach ($projects as $project) {
foreach ($langcodes as $langcode) {
$source = locale_translation_source_build($project, $langcode);
- $sources[$source->name . ':' . $source->langcode] = $source;
+ $sources[$source->name][$source->langcode] = $source;
}
}
return $sources;
@@ -159,32 +159,24 @@ function locale_translation_build_sources($projects = array(), $langcodes = arra
* Translation source object.
*
* @return stdClass
- * File object (filename, basename, name) updated with data of the po file.
- * On success the files property of the source object is updated.
- * files[LOCALE_TRANSLATION_LOCAL]:
+ * Source file object of the po file, updated with:
* - "uri": File name and path.
* - "timestamp": Last updated time of the po file.
* FALSE if the file is not found.
*
* @see locale_translation_source_build()
*/
-function locale_translation_source_check_file(&$source) {
+function locale_translation_source_check_file($source) {
if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) {
- $directory = $source->files[LOCALE_TRANSLATION_LOCAL]->directory;
- $filename = '/' . preg_quote($source->files[LOCALE_TRANSLATION_LOCAL]->filename) . '$/';
-
- // If the directory contains a stream wrapper, it is converted to a real
- // path. This is required for file_scan_directory() which can not handle
- // stream wrappers.
- if ($scheme = file_uri_scheme($directory)) {
- $directory = str_replace($scheme . '://', drupal_realpath($scheme . '://'), $directory);
- }
+ $source_file = $source->files[LOCALE_TRANSLATION_LOCAL];
+ $directory = $source_file->directory;
+ $filename = '/' . preg_quote($source_file->filename) . '$/';
if ($files = file_scan_directory($directory, $filename, array('key' => 'name', 'recurse' => FALSE))) {
$file = current($files);
- $source->files[LOCALE_TRANSLATION_LOCAL]->uri = $file->uri;
- $source->files[LOCALE_TRANSLATION_LOCAL]->timestamp = filemtime($file->uri);
- return $file;
+ $source_file->uri = $file->uri;
+ $source_file->timestamp = filemtime($file->uri);
+ return $source_file;
}
}
return FALSE;
@@ -211,13 +203,14 @@ function locale_translation_source_check_file(&$source) {
* - "files": Array of file objects containing properties of local and remote
* translation files.
* Other processes can add the following properties:
- * - "type": Most recent file type LOCALE_TRANSLATION_REMOTE or
- * LOCALE_TRANSLATION_LOCAL. Corresponding with a key of the
- * "files" array.
- * - "timestamp": Timestamp of the most recent translation file.
+ * - "type": Most recent translation source found. LOCALE_TRANSLATION_REMOTE and
+ * LOCALE_TRANSLATION_LOCAL indicate available new translations,
+ * LOCALE_TRANSLATION_CURRENT indicate that the current translation is them
+ * most recent. "type" sorresponds with a key of the "files" array.
+ * - "timestamp": The creation time of the "type" translation (file).
+ * - "last_checked": The time when the "type" translation was last checked.
* The "files" array can hold file objects of type:
- * LOCALE_TRANSLATION_LOCAL, LOCALE_TRANSLATION_REMOTE,
- * LOCALE_TRANSLATION_DOWNLOADED, LOCALE_TRANSLATION_IMPORTED and
+ * LOCALE_TRANSLATION_LOCAL, LOCALE_TRANSLATION_REMOTE and
* LOCALE_TRANSLATION_CURRENT. Each contains following properties:
* - "type": The object type (LOCALE_TRANSLATION_LOCAL,
* LOCALE_TRANSLATION_REMOTE, etc. see above).
@@ -239,6 +232,9 @@ function locale_translation_source_build($project, $langcode, $filename = NULL)
$source = clone $project;
$source->project = $project->name;
$source->langcode = $langcode;
+ $source->type = '';
+ $source->timestamp = 0;
+ $source->last_checked = 0;
$filename = $filename ? $filename : config('locale.settings')->get('translation.default_filename');
@@ -256,17 +252,15 @@ function locale_translation_source_build($project, $langcode, $filename = NULL)
'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)),
'uri' => locale_translation_build_server_pattern($source, $source->server_pattern),
);
- if (config('locale.settings')->get('translation.path')) {
- $files[LOCALE_TRANSLATION_LOCAL] = (object) array(
- 'project' => $project->name,
- 'langcode' => $langcode,
- 'version' => $project->version,
- 'type' => LOCALE_TRANSLATION_LOCAL,
- 'filename' => locale_translation_build_server_pattern($source, $filename),
- 'directory' => 'translations://',
- );
- $files[LOCALE_TRANSLATION_LOCAL]->uri = $files[LOCALE_TRANSLATION_LOCAL]->directory . $files[LOCALE_TRANSLATION_LOCAL]->filename;
- }
+ $files[LOCALE_TRANSLATION_LOCAL] = (object) array(
+ 'project' => $project->name,
+ 'langcode' => $langcode,
+ 'version' => $project->version,
+ 'type' => LOCALE_TRANSLATION_LOCAL,
+ 'filename' => locale_translation_build_server_pattern($source, $filename),
+ 'directory' => 'translations://',
+ );
+ $files[LOCALE_TRANSLATION_LOCAL]->uri = $files[LOCALE_TRANSLATION_LOCAL]->directory . $files[LOCALE_TRANSLATION_LOCAL]->filename;
}
else {
$files[LOCALE_TRANSLATION_LOCAL] = (object) array(
@@ -281,10 +275,94 @@ function locale_translation_source_build($project, $langcode, $filename = NULL)
}
$source->files = $files;
+ // If this project+language is already translated, we add its status and
+ // update the current translation timestamp and last_updated time. If the
+ // project+language is not translated before, create a new record.
+ $history = locale_translation_get_file_history();
+ if (isset($history[$project->name][$langcode]) && $history[$project->name][$langcode]->timestamp) {
+ $source->files[LOCALE_TRANSLATION_CURRENT] = $history[$project->name][$langcode];
+ $source->type = LOCALE_TRANSLATION_CURRENT;
+ $source->timestamp = $history[$project->name][$langcode]->timestamp;
+ $source->last_checked = $history[$project->name][$langcode]->last_checked;
+ }
+ else {
+ locale_translation_update_file_history($source);
+ }
+
return $source;
}
/**
+ * 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);
+}
+
+/**
+ * Populate a queue with project to check for translation updates.
+ */
+function locale_cron_fill_queue() {
+ $updates = array();
+ $config = config('locale.settings');
+
+ // Determine which project+language should be updated.
+ $last = REQUEST_TIME - $config->get('translation.update_interval_days') * 3600 * 24;
+ $query = db_select('locale_file', 'f');
+ $query->join('locale_project', 'p', 'p.name = f.project');
+ $query->condition('f.last_checked', $last, '<');
+ $query->fields('f', array('project', 'langcode'));
+ if (!$config->get('translation.check_disabled_modules')) {
+ $query->condition('p.status', 1);
+ }
+ $files = $query->execute()->fetchAll();
+ foreach ($files as $file) {
+ $updates[$file->project][] = $file->langcode;
+
+ // Update the last_checked timestamp of the project+language that will
+ // be checked for updates.
+ db_update('locale_file')
+ ->fields(array('last_checked' => REQUEST_TIME))
+ ->condition('project', $file->project)
+ ->condition('langcode', $file->langcode)
+ ->execute();
+ }
+
+ // For each project+language combination a number of tasks are added to
+ // the queue.
+ if ($updates) {
+ module_load_include('fetch.inc', 'locale');
+ $options = _locale_translation_default_update_options();
+ $queue = Drupal::queue('locale_translation', TRUE);
+
+ foreach ($updates as $project => $languages) {
+ $batch = locale_translation_batch_update_build(array($project), $languages, $options);
+ foreach ($batch['operations'] as $item) {
+ $queue->createItem($item);
+ }
+ }
+ }
+}
+
+/**
* Determine if a file is a remote file.
*
* @param string $uri
@@ -354,5 +432,7 @@ function _locale_translation_default_update_options() {
'not_customized' => $config->get('translation.overwrite_not_customized'),
'customized' => $config->get('translation.overwrite_customized'),
),
+ 'finish_feedback' => TRUE,
+ 'use_remote' => locale_translation_use_remote_source(),
);
}