diff --git a/core/lib/Drupal/Component/Gettext/PoStreamReader.php b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
index 74a9bae..83ad207 100644
--- a/core/lib/Drupal/Component/Gettext/PoStreamReader.php
+++ b/core/lib/Drupal/Component/Gettext/PoStreamReader.php
@@ -170,7 +170,7 @@ public function open() {
       $this->readHeader();
     }
     else {
-      throw new Exception('Cannot open stream without URI set.');
+      throw new \Exception('Cannot open stream without URI set.');
     }
   }
 
@@ -185,7 +185,7 @@ public function close() {
       fclose($this->_fd);
     }
     else {
-      throw new Exception('Cannot close stream that is not open.');
+      throw new \Exception('Cannot close stream that is not open.');
     }
   }
 
diff --git a/core/modules/locale/config/locale.settings.yml b/core/modules/locale/config/locale.settings.yml
index a1c9f35..c5ddd67 100644
--- a/core/modules/locale/config/locale.settings.yml
+++ b/core/modules/locale/config/locale.settings.yml
@@ -3,3 +3,7 @@ translation:
   check_disabled_modules: false
   default_filename: '%project-%version.%language.po'
   default_server_pattern: 'http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po'
+  overwrite_customized: false
+  overwrite_not_customized: true
+  update_interval_days: '0'
+
diff --git a/core/modules/locale/lib/Drupal/locale/Gettext.php b/core/modules/locale/lib/Drupal/locale/Gettext.php
index a7c37ca..43432e4 100644
--- a/core/modules/locale/lib/Drupal/locale/Gettext.php
+++ b/core/modules/locale/lib/Drupal/locale/Gettext.php
@@ -53,10 +53,11 @@ static function filesToArray($langcode, array $files) {
    *
    * @param stdClass $file
    *   File object with an URI property pointing at the file's path.
+   *   @todo: Describe Required parts of the file object.
+   *   - 'langcode': The language code.
    *
    * @param array $options
    *   An array with options that can have the following elements:
-   *   - 'langcode': The language code, required.
    *   - 'overwrite_options': Overwrite options array as defined in
    *     Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array.
    *   - 'customized': Flag indicating whether the strings imported from $file
@@ -83,7 +84,7 @@ static function fileToDatabase($file, $options) {
     );
     // Instantiate and initialize the stream reader for this file.
     $reader = new PoStreamReader();
-    $reader->setLangcode($options['langcode']);
+    $reader->setLangcode($file->langcode);
     $reader->setURI($file->uri);
 
     try {
@@ -100,7 +101,7 @@ static function fileToDatabase($file, $options) {
 
     // Initialize the database writer.
     $writer = new PoDatabaseWriter();
-    $writer->setLangcode($options['langcode']);
+    $writer->setLangcode($file->langcode);
     $writer_options = array(
       'overwrite_options' => $options['overwrite_options'],
       'customized' => $options['customized'],
diff --git a/core/modules/locale/lib/Drupal/locale/PoDatabaseReader.php b/core/modules/locale/lib/Drupal/locale/PoDatabaseReader.php
index 74cf40d..f2660fd 100644
--- a/core/modules/locale/lib/Drupal/locale/PoDatabaseReader.php
+++ b/core/modules/locale/lib/Drupal/locale/PoDatabaseReader.php
@@ -102,7 +102,7 @@ function getHeader() {
    *   Always, because you cannot set the PO header of a reader.
    */
   function setHeader(PoHeader $header) {
-    throw new Exception('You cannot set the PO header in a reader.');
+    throw new \Exception('You cannot set the PO header in a reader.');
   }
 
   /**
diff --git a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
index b5b03c0..337bd90 100644
--- a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
+++ b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
@@ -160,14 +160,14 @@ function setHeader(PoHeader $header) {
     // Check for options.
     $options = $this->getOptions();
     if (empty($options)) {
-      throw new Exception("Options should be set before assigning a PoHeader.");
+      throw new \Exception("Options should be set before assigning a PoHeader.");
     }
     $overwrite_options = $options['overwrite_options'];
 
     // Check for langcode.
     $langcode = $this->_langcode;
     if (empty($langcode)) {
-      throw new Exception("Langcode should be set before assigning a PoHeader.");
+      throw new \Exception("Langcode should be set before assigning a PoHeader.");
     }
 
     if (array_sum($overwrite_options) || empty($locale_plurals[$langcode]['plurals'])) {
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareTest.php
deleted file mode 100644
index 017e2c5..0000000
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareTest.php
+++ /dev/null
@@ -1,234 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\locale\Tests\LocaleCompareTest.
- */
-
-namespace Drupal\locale\Tests;
-
-use Drupal\simpletest\WebTestBase;
-
-/**
- * Tests for comparing status of existing project translations with available translations.
- */
-class LocaleCompareTest extends WebTestBase {
-
-  /**
-   * The path of the translations directory where local translations are stored.
-   *
-   * @var string
-   */
-  private $tranlations_directory;
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('update', 'locale', 'locale_test');
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Compare project states',
-      'description' => 'Tests for comparing status of existing project translations with available translations.',
-      'group' => 'Locale',
-    );
-  }
-
-  /**
-   * Setup the test environment.
-   *
-   * We use German as default test language. Due to hardcoded configurations in
-   * the locale_test module, the language can not be chosen randomly.
-   */
-  function setUp() {
-    parent::setUp();
-    module_load_include('compare.inc', 'locale');
-    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
-    $this->drupalLogin($admin_user);
-    $this->drupalPost('admin/config/regional/language/add', array('predefined_langcode' => 'de'), t('Add language'));
-  }
-
-  /**
-   * Set 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);
-    variable_set('locale_translate_file_directory', $path);
-  }
-
-  /**
-   * 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 string $data
-   *   Translation data to put into the file. Po header data will be added.
-   */
-  private function makePoFile($path, $filename, $timestamp = NULL, $data = '') {
-    $timestamp = $timestamp ? $timestamp : REQUEST_TIME;
-    $path = 'public://' . $path;
-    $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;
-
-    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->uri, $po_header . $data);
-    touch(drupal_realpath($file->uri), $timestamp);
-    $file->save();
-  }
-
-  /**
-   * Test for translation status storage and translation status comparison.
-   */
-  function testLocaleCompare() {
-    // Create and login user.
-    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages'));
-    $this->drupalLogin($admin_user);
-
-    module_load_include('compare.inc', 'locale');
-
-    // Check if hidden modules are not included.
-    $projects = locale_translation_project_list();
-    $this->assertFalse(isset($projects['locale_test']), 'Hidden module not found');
-
-    // Make the test modules look like a normal custom module. i.e. make the
-    // modules not hidden. locale_test_system_info_alter() modifies the project
-    // info of the locale_test and locale_test_disabled modules.
-    state()->set('locale_translation_test_system_info_alter', TRUE);
-
-    // Reset static system list caches to reflect info changes.
-    drupal_static_reset('locale_translation_project_list');
-    system_list_reset();
-
-    // Check if interface translation data is collected from hook_info.
-    $projects = locale_translation_project_list();
-    $this->assertEqual($projects['locale_test']['info']['interface translation server pattern'], 'core/modules/locale/test/test.%language.po', 'Interface translation parameter found in project info.');
-    $this->assertEqual($projects['locale_test']['name'] , 'locale_test', format_string('%key found in project info.', array('%key' => 'interface translation project')));
-
-    // Get the locale settings.
-    $config = config('locale.settings');
-
-    // Check if disabled modules are detected.
-    $config->set('translation.check_disabled_modules', TRUE)->save();
-    drupal_static_reset('locale_translation_project_list');
-    $projects = locale_translation_project_list();
-    $this->assertTrue(isset($projects['locale_test_disabled']), 'Disabled module found');
-
-    // Check the fully processed list of project data of both enabled and
-    // disabled modules.
-    $config->set('translation.check_disabled_modules', TRUE)->save();
-    drupal_static_reset('locale_translation_project_list');
-    $projects = locale_translation_get_projects();
-    $this->assertEqual($projects['drupal']->name, 'drupal', 'Core project found');
-    $this->assertEqual($projects['locale_test']->server_pattern, 'core/modules/locale/test/test.%language.po', 'Interface translation parameter found in project info.');
-    $this->assertEqual($projects['locale_test_disabled']->status, '0', 'Disabled module found');
-    $config->delete('translation.check_disabled_modules');
-
-    // Return the locale test modules back to their hidden state.
-    state()->delete('locale_translation_test_system_info_alter');
-  }
-
-  /**
-   * Checks if local or remote translation sources are detected.
-   *
-   * This test requires 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 txt extension. Another
-   * directory is designated for local translation files.
-   *
-   * The translation status process by default checks the status of the
-   * installed projects. For testing purpose a predefined set of modules with
-   * fixed file names and release versions is used. Using a
-   * hook_locale_translation_projects_alter implementation in the locale_test
-   * module this custom project definition is applied.
-   *
-   * This test generates a set of local and remote translation files in their
-   * respective local and remote translation directory. The test checks whether
-   * the most recent files are selected in the different check scenarios: check
-   * for local files only, check for remote files only, check for both local and
-   * remote files.
-   */
-  function testCompareCheckLocal() {
-    $config = config('locale.settings');
-
-    // A flag is set to let the locale_test module replace the project data with
-    // a set of test projects.
-    state()->set('locale_translation_test_projects', TRUE);
-
-    // Setup timestamps to identify old and new translation sources.
-    $timestamp_old = REQUEST_TIME - 100;
-    $timestamp_new = REQUEST_TIME;
-
-    // Set up 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.txt')->save();
-
-    // 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.txt', $timestamp_new);
-    $this->makePoFile('remote/8.x/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de.txt', $timestamp_old);
-    $this->makePoFile('remote/8.x/contrib_module_three', 'contrib_module_three-8.x-1.0.de.txt', $timestamp_old);
-
-    // 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.txt', $timestamp_old);
-    $this->makePoFile('local', 'contrib_module_two-8.x-2.0-beta4.de.txt', $timestamp_new);
-    $this->makePoFile('local', 'custom_module_one.de.po', $timestamp_new);
-
-    // Get status of translation sources at local file system.
-    $config->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)->save();
-    $this->drupalGet('admin/reports/translations/check');
-    $result = state()->get('locale_translation_status');
-    $this->assertEqual($result['contrib_module_one']['de']->type, 'local', 'Translation of contrib_module_one found');
-    $this->assertEqual($result['contrib_module_one']['de']->timestamp, $timestamp_old, 'Translation timestamp found');
-    $this->assertEqual($result['contrib_module_two']['de']->type, 'local', 'Translation of contrib_module_two found');
-    $this->assertEqual($result['contrib_module_two']['de']->timestamp, $timestamp_new, 'Translation timestamp found');
-    $this->assertEqual($result['locale_test']['de']->type, 'local', 'Translation of locale_test found');
-    $this->assertEqual($result['custom_module_one']['de']->type, 'local', 'Translation of custom_module_one found');
-
-    // Get status of translation sources at both local and remote the locations.
-    $config->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL)->save();
-    $this->drupalGet('admin/reports/translations/check');
-    $result = state()->get('locale_translation_status');
-    $this->assertEqual($result['contrib_module_one']['de']->type, 'remote', 'Translation of contrib_module_one found');
-    $this->assertEqual($result['contrib_module_one']['de']->timestamp, $timestamp_new, 'Translation timestamp found');
-    $this->assertEqual($result['contrib_module_two']['de']->type, 'local', 'Translation of contrib_module_two found');
-    $this->assertEqual($result['contrib_module_two']['de']->timestamp, $timestamp_new, 'Translation timestamp found');
-    $this->assertEqual($result['contrib_module_three']['de']->type, 'remote', 'Translation of contrib_module_three found');
-    $this->assertEqual($result['contrib_module_three']['de']->timestamp, $timestamp_old, 'Translation timestamp found');
-    $this->assertEqual($result['locale_test']['de']->type, 'local', 'Translation of locale_test found');
-    $this->assertEqual($result['custom_module_one']['de']->type, 'local', 'Translation of custom_module_one found');
-  }
-}
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
index 06d292f..5d7ce3a 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleFileImportStatus.php
@@ -89,7 +89,7 @@ function mockImportedPoFile($langcode, $timestamp_difference = 0) {
 
     // Fill the {locale_file} with a custom timestamp.
     if ($timestamp_difference != 0) {
-      locale_translate_update_file_history($file);
+      locale_translate_updation_file_history($file);
     }
 
     $count = db_query('SELECT COUNT(*) FROM {locale_file} WHERE langcode = :langcode', array(':langcode' => $langcode))->fetchField();
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
new file mode 100644
index 0000000..5137a64
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
@@ -0,0 +1,635 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\Tests\LocaleCompareTest.
+ */
+
+namespace Drupal\locale\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests for comparing status of existing project translations with available translations.
+ */
+class LocaleUpdateTest extends WebTestBase {
+
+  /**
+   * The path of the translations directory where local translations are stored.
+   *
+   * @var string
+   */
+  private $tranlations_directory;
+
+  /**
+   * Timestamp for a translation file or existing translations.
+   *
+   * @var integer
+   */
+  private $timestamp_old;
+
+  /**
+   * Timestamp for a translation file or existing translations.
+   *
+   * @var integer
+   */
+  private $timestamp_medium;
+
+  /**
+   * Timestamp for a translation file or existing translations.
+   *
+   * @var integer
+   */
+  private $timestamp_new;
+
+  /**
+   * Timestamp for a translation file or existing translations.
+   *
+   * @var integer
+   */
+  private $timestamp_current;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('update', 'locale', 'locale_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Update Interface translations',
+      'description' => 'Tests for updating the interface translations of projects.',
+      'group' => 'Locale',
+    );
+  }
+
+  /**
+   * Setup the test environment.
+   *
+   * We use German as default test language. Due to hardcoded configurations in
+   * the locale_test module, the language can not be chosen randomly.
+   */
+  function setUp() {
+    parent::setUp();
+    module_load_include('compare.inc', 'locale');
+    module_load_include('fetch.inc', 'locale');
+    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'translate interface'));
+    $this->drupalLogin($admin_user);
+    $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;
+  }
+
+  /**
+   * Set 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);
+    variable_set('locale_translate_file_directory', $path);
+  }
+
+  /**
+   * 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.
+   */
+  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 an 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->uri, $po_header . $text);
+    touch(drupal_realpath($file->uri), $timestamp);
+    $file->save();
+  }
+
+  /**
+   * Setup the environment containting local and remote translation files.
+   *
+   * Update test 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.
+   */
+  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.
+    state()->set('locale_translation_test_projects', TRUE);
+
+    // Set up 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 setup 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',
+    );
+    $customized = 0;
+    foreach ($non_customized_translations as $source => $translation) {
+      $string = locale_storage()->createString(array('source' => $source, 'context' => $context))
+        ->save();
+      $target = locale_storage()->createTranslation(array(
+        'lid' => $string->getId(),
+        'language' => $langcode,
+        'translation' => $translation,
+        'customized' => $customized,
+      ))->save();
+    }
+
+    // Add non customized translations to the database.
+    $customized_translations = array(
+      'January' => 'Januar_customized',
+      'February' => 'Februar_customized',
+      'May' => 'Mai_customized',
+    );
+    $customized = 1;
+    foreach ($customized_translations as $source => $translation) {
+      $string = locale_storage()->createString(array('source' => $source, 'context' => $context))
+        ->save();
+      $target = locale_storage()->createTranslation(array(
+        'lid' => $string->getId(),
+        'language' => $langcode,
+        'translation' => $translation,
+        'customized' => $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);
+    }
+  }
+
+  /**
+   * Test for translation status storage and translation status comparison.
+   */
+  function testUpdateCompare() {
+    // Create and login user.
+    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages'));
+    $this->drupalLogin($admin_user);
+
+    module_load_include('compare.inc', 'locale');
+
+    // Check if hidden modules are not included.
+    $projects = locale_translation_project_list();
+    $this->assertFalse(isset($projects['locale_test']), 'Hidden module not found');
+
+    // Make the test modules look like a normal custom module. i.e. make the
+    // modules not hidden. locale_test_system_info_alter() modifies the project
+    // info of the locale_test and locale_test_disabled modules.
+    state()->set('locale_translation_test_system_info_alter', TRUE);
+
+    // Reset static system list caches to reflect info changes.
+    drupal_static_reset('locale_translation_project_list');
+    system_list_reset();
+
+    // Check if interface translation data is collected from hook_info.
+    $projects = locale_translation_project_list();
+    $this->assertEqual($projects['locale_test']['info']['interface translation server pattern'], 'core/modules/locale/test/test.%language.po', 'Interface translation parameter found in project info.');
+    $this->assertEqual($projects['locale_test']['name'] , 'locale_test', format_string('%key found in project info.', array('%key' => 'interface translation project')));
+
+    // Get the locale settings.
+    $config = config('locale.settings');
+
+    // Check if disabled modules are detected.
+    $config->set('translation.check_disabled_modules', TRUE)->save();
+    drupal_static_reset('locale_translation_project_list');
+    $projects = locale_translation_project_list();
+    $this->assertTrue(isset($projects['locale_test_disabled']), 'Disabled module found');
+
+    // Check the fully processed list of project data of both enabled and
+    // disabled modules.
+    $config->set('translation.check_disabled_modules', TRUE)->save();
+    drupal_static_reset('locale_translation_project_list');
+    $projects = locale_translation_get_projects();
+    $this->assertEqual($projects['drupal']->name, 'drupal', 'Core project found');
+    $this->assertEqual($projects['locale_test']->server_pattern, 'core/modules/locale/test/test.%language.po', 'Interface translation parameter found in project info.');
+    $this->assertEqual($projects['locale_test_disabled']->status, '0', 'Disabled module found');
+    $config->delete('translation.check_disabled_modules');
+
+    // Return the locale test modules back to their hidden state.
+    state()->delete('locale_translation_test_system_info_alter');
+  }
+
+  /**
+   * Checks if local or remote translation sources are detected.
+   *
+   * The translation status process by default checks the status of the
+   * installed projects. For testing purpose a predefined set of modules with
+   * fixed file names and release versions is used. Using a
+   * hook_locale_translation_projects_alter implementation in the locale_test
+   * module this custom project definition is applied.
+   *
+   * This test generates a set of local and remote translation files in their
+   * respective local and remote translation directory. The test checks whether
+   * the most recent files are selected in the different check scenarios: check
+   * for local files only, check for remote files only, check for both local and
+   * remote files.
+   */
+  function testUpdateCheckStatus() {
+    $config = config('locale.settings');
+    // A flag is set to let the locale_test module replace the project data with
+    // a set of test projects.
+    state()->set('locale_translation_test_projects', TRUE);
+
+    // Create local and remote translations files.
+    $this->setTranslationFiles();
+    $config->set('translation.default_filename', '%project-%version.%language._po')->save();
+
+    // Get status of translation sources at local file system.
+    $config->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)->save();
+    $this->drupalGet('admin/reports/translations/check');
+    $result = state()->get('locale_translation_status');
+    $this->assertEqual($result['contrib_module_one']['de']->type, '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, 'local', 'Translation of contrib_module_two found');
+    $this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestamp_new, 'Translation timestamp found');
+    $this->assertEqual($result['locale_test']['de']->type, 'local', 'Translation of locale_test found');
+    $this->assertEqual($result['custom_module_one']['de']->type, 'local', 'Translation of custom_module_one found');
+
+    // Get status of translation sources at both local and remote the locations.
+    $config->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL)->save();
+    $this->drupalGet('admin/reports/translations/check');
+    $result = state()->get('locale_translation_status');
+    $this->assertEqual($result['contrib_module_one']['de']->type, '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, 'local', 'Translation of contrib_module_two found');
+    $this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestamp_new, 'Translation timestamp found');
+    $this->assertEqual($result['contrib_module_three']['de']->type, 'local', 'Translation of contrib_module_three found');
+    $this->assertEqual($result['contrib_module_three']['de']->timestamp, $this->timestamp_old, 'Translation timestamp found');
+    $this->assertEqual($result['locale_test']['de']->type, 'local', 'Translation of locale_test found');
+    $this->assertEqual($result['custom_module_one']['de']->type, 'local', 'Translation of custom_module_one found');
+  }
+
+  /**
+   * Test translation import from remote sources
+   *
+   * Test conditions:
+   *  - Source: remote
+   *  - Overwrite: all
+   *  - Translation directory: available
+   * Test results after download:
+   *  - Source in status array: contrib_module_one = current source, contrib_module_two = current source, contrib_module_three = current source.
+   *  - Timestamp in import_files table: contrib_module_one = now, contrib_module_two = now, contrib_module_three = medium.
+   *  - Translation in table locales_target: non-customized translations are overwritten, customized translations are overwritten.
+   */
+  function testUpdateImportSourceRemote() {
+    $config = config('locale.settings');
+
+    // Build the test environment.
+    $this->setTranslationFiles();
+    $this-> setCurrentTranslations();
+    $config->set('translation.default_filename', '%project-%version.%language._po');
+
+    // Set the test conditions.
+    $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 translation update.
+    $this->drupalGet('admin/reports/translations/check');
+    $this->drupalPost('admin/reports/translations', array(), t('Update'));
+
+    // @todo
+    $status = state()->get('locale_translation_status');
+    $this->assertEqual($status['contrib_module_one']['de']->type, 'current', 'Translation of contrib_module_one found');
+    $this->assertEqual($status['contrib_module_two']['de']->type, 'current', 'Translation of contrib_module_two found');
+    $this->assertEqual($status['contrib_module_three']['de']->type, 'current', 'Translation of contrib_module_three found');
+
+    // @todo
+    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_new, 'Translation of contrib_module_two is imported');
+    $this->assertTrue($history['contrib_module_two']['de']->last_checked >= $this->timestamp_now, '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');
+
+    // @todo
+    $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_1', 'Translation of January');
+    $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_2', 'Translation of February');
+    $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
+    $this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
+    $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');
+  }
+
+  /**
+   * Test translation import from local sources
+   *
+   * @todo Finish documentation.
+   * Starting point:
+   *   Time stamp of translations:
+   *     contrib_module_one
+   *        remote: new
+   *        local: old
+   *        current: medium
+   *      contrib_module_two
+   *        remote: old
+   *        local: new
+   *        current: medium
+   *      contrib_module_three
+   *        remote: old
+   *        local: old
+   *        current: medium
+   *   Translations per project
+   *   Customized translations
+   *
+   * Test conditions:
+   *  - Source: local
+   *  - Overwrite: all
+   *  - Translation directory: available
+   * Test results:
+   *  - In status array: contrib_module_one = local source, contrib_module_two = local source, contrib_module_three = current (or no) source.
+   *  - In import_files table: contrib_module_one = medium, contrib_module_two = now, contrib_module_three = medium.
+   *  - In table locales_target: non-customized translations are overwritten, customized translations are overwritten.
+   */
+  function testUpdateImportSourceLocal() {
+    $config = config('locale.settings');
+
+    // Build the test environment.
+    $this->setTranslationFiles();
+    $this-> setCurrentTranslations();
+    $config->set('translation.default_filename', '%project-%version.%language._po');
+
+    // Set the test conditions.
+    $edit = array(
+      'use_source' => LOCALE_TRANSLATION_USE_SOURCE_LOCAL,
+      'overwrite' => LOCALE_TRANSLATION_OVERWRITE_ALL,
+    );
+    $this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration'));
+
+    // Execute translation update.
+    $this->drupalGet('admin/reports/translations/check');
+    $this->drupalPost('admin/reports/translations', array(), t('Update'));
+
+    // @todo
+    $status = state()->get('locale_translation_status');
+    $this->assertEqual($status['contrib_module_one']['de']->type, 'current', 'Translation of contrib_module_one found');
+    $this->assertEqual($status['contrib_module_two']['de']->type, 'current', 'Translation of contrib_module_two found');
+    $this->assertEqual($status['contrib_module_three']['de']->type, 'current', 'Translation of contrib_module_three found');
+
+    // @todo
+    drupal_static_reset('locale_translation_get_file_history');
+    $history = locale_translation_get_file_history();
+    $this->assertTrue($history['contrib_module_one']['de']->timestamp >= $this->timestamp_medium, 'Translation of contrib_module_one is imported');
+    $this->assertEqual($history['contrib_module_one']['de']->last_checked, $this->timestamp_medium, 'Translation of contrib_module_one is updated');
+    $this->assertEqual($history['contrib_module_two']['de']->timestamp, $this->timestamp_new, 'Translation of contrib_module_two is imported');
+    $this->assertTrue($history['contrib_module_two']['de']->last_checked >= $this->timestamp_now, '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');
+
+    // @todo
+    $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January');
+    $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_2', 'Translation of February');
+    $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
+    $this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
+    $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');
+  }
+
+  /**
+   * Test translation import without a translations directory.
+   *
+   * Test conditions:
+   *  - Source: remote
+   *  - Overwrite: all
+   *  - Translation directory: not available
+   * Test results:
+   *  - Source in status array: contrib_module_one = remote source, contrib_module_two = remote source, contrib_module_three = current (or no) source.
+   *  - Timestamp in import_files table: contrib_module_one = now, contrib_module_two = now, contrib_module_three = medium.
+   */
+  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 test conditions.
+    $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 translation update.
+    $this->drupalGet('admin/reports/translations/check');
+    $this->drupalPost('admin/reports/translations', array(), t('Update'));
+
+    // @todo
+    $status = state()->get('locale_translation_status');
+    $this->assertEqual($status['contrib_module_one']['de']->type, 'current', 'Translation of contrib_module_one found');
+    $this->assertEqual($status['contrib_module_two']['de']->type, 'current', 'Translation of contrib_module_two found');
+    $this->assertEqual($status['contrib_module_three']['de']->type, 'current', 'Translation of contrib_module_three found');
+
+    // @todo
+    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');
+
+    // @todo
+    $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');
+  }
+
+  /**
+   * Test translation import with and only overwrite non-customized translations.
+   *
+   * Test conditions:
+   *  - Source: remote
+   *  - Overwrite: non-customized
+   *  - Translation directory: available
+   * Test results:
+   *  - In table locales_target: non-customized translations are overwritten, customized translations are not overwritten
+   */
+  function testUpdateImportModeNonCustomized() {
+    $config = config('locale.settings');
+
+    // Build the test environment.
+    $this->setTranslationFiles();
+    $this-> setCurrentTranslations();
+    $config->set('translation.default_filename', '%project-%version.%language._po');
+
+    // Set the test conditions.
+    $edit = array(
+      'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
+      'overwrite' => LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED,
+    );
+    $this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration'));
+
+    // Execute translation update.
+    $this->drupalGet('admin/reports/translations/check');
+    $this->drupalPost('admin/reports/translations', array(), t('Update'));
+
+    // @todo
+    $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January');
+    $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_customized', 'Translation of February');
+    $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz_2', 'Translation of March');
+    $this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
+    $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');
+  }
+
+  /**
+   * Test translation import with and don't overwrite any translation.
+   *
+   * Test conditions:
+   *  - Source: remote
+   *  - Overwrite: none
+   *  - Translation directory: available
+   * Test results:
+   *  - In table locales_target: non-customized translations are not overwritten, customized translations are not overwritten
+   */
+  function testUpdateImportModeNone() {
+    $config = config('locale.settings');
+
+    // Build the test environment.
+    $this->setTranslationFiles();
+    $this-> setCurrentTranslations();
+    $config->set('translation.default_filename', '%project-%version.%language._po');
+
+    // Set the test conditions.
+    $edit = array(
+      'use_source' => LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL,
+      'overwrite' => LOCALE_TRANSLATION_OVERWRITE_NONE,
+    );
+    $this->drupalPost('admin/config/regional/translate/settings', $edit, t('Save configuration'));
+
+    // Execute translation update.
+    $this->drupalGet('admin/reports/translations/check');
+    $this->drupalPost('admin/reports/translations', array(), t('Update'));
+
+    // @todo
+    $this->assertEqual(t('January', array(), array('langcode' => 'de')), 'Januar_customized', 'Translation of January');
+    $this->assertEqual(t('February', array(), array('langcode' => 'de')), 'Februar_customized', 'Translation of February');
+    $this->assertEqual(t('March', array(), array('langcode' => 'de')), 'Marz', 'Translation of March');
+    $this->assertEqual(t('April', array(), array('langcode' => 'de')), 'April_2', 'Translation of April');
+    $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');
+  }
+}
diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc
index a287e16..7fda10f 100644
--- a/core/modules/locale/locale.batch.inc
+++ b/core/modules/locale/locale.batch.inc
@@ -6,42 +6,6 @@
  */
 
 /**
- * Build 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
- * 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
- * in the state variable 'locale_translation_status_last_update'.
- *
- * @param array $sources
- *   Array of translation source objects for which to check the state of
- *   translation source files.
- */
-function locale_translation_batch_status_build($sources) {
-  $operations = array();
-
-  // Set the batch processes for remote sources.
-  foreach ($sources as $source) {
-    $operations[] = array('locale_translation_batch_status_fetch_remote', array($source));
-  }
-
-  // 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());
-
-  $batch = array(
-    'operations' => $operations,
-    'title' => t('Checking available translations'),
-    'finished' => 'locale_translation_batch_status_finished',
-    'error_message' => t('Error checking available interface translation updates.'),
-    'file' => drupal_get_path('module', 'locale') . '/locale.batch.inc',
-  );
-  return $batch;
-}
-
-/**
  * Batch operation callback: Check the availability of a remote po file.
  *
  * Checks the presence and creation time of one po file per batch process. The
@@ -62,16 +26,16 @@ function locale_translation_batch_status_fetch_remote($source, &$context) {
   // data with the remote status.
   if (isset($source->files['remote'])) {
     $remote_file = $source->files['remote'];
-    $result = locale_translation_http_check($remote_file->url);
+    $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 url.
+    // store the resulting uri.
     if ($result && !empty($result->updated)) {
-      $remote_file->url = isset($result->redirect_url) ? $result->redirect_url : $remote_file->url;
+      $remote_file->uri = isset($result->redirect_uri) ? $result->redirect_uri : $remote_file->uri;
       $remote_file->timestamp = $result->updated;
       $source->files['remote'] = $remote_file;
     }
-    $context['results'][$source->name][$source->language] = $source;
+    $context['results']['sources'][$source->name][$source->language] = $source;
   }
 }
 
@@ -102,10 +66,10 @@ function locale_translation_batch_status_fetch_local($sources, &$context) {
 
       // If remote data was collected before, we merge it into the newly
       // collected result.
-      if (isset($context['results'][$source->name][$source->language])) {
-        $source->files['remote'] = $context['results'][$source->name][$source->language]->files['remote'];
+      if (isset($context['results']['sources'][$source->name][$source->language])) {
+        $source->files['remote'] = $context['results']['sources'][$source->name][$source->language]->files['remote'];
       }
-      $context['results'][$source->name][$source->language] = $source;
+      $context['results']['sources'][$source->name][$source->language] = $source;
     }
   }
 }
@@ -128,9 +92,10 @@ function locale_translation_batch_status_fetch_local($sources, &$context) {
  */
 function locale_translation_batch_status_compare(&$context) {
   module_load_include('compare.inc', 'locale');
+  $history = locale_translation_get_file_history();
   $results = array();
 
-  foreach ($context['results'] as $project => $langcodes) {
+  foreach ($context['results']['sources'] as $project => $langcodes) {
     foreach ($langcodes as $langcode => $source) {
       $local = isset($source->files['local']) ? $source->files['local'] : NULL;
       $remote = isset($source->files['remote']) ? $source->files['remote'] : NULL;
@@ -141,13 +106,35 @@ function locale_translation_batch_status_compare(&$context) {
       if (isset($file->timestamp)) {
         $source->type = $file->type;
         $source->timestamp = $file->timestamp;
-        $results[$project][$langcode] = $source;
       }
+
+      // 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->language])) {
+        $current = $history[$source->project][$source->language];
+        // Add the current translation to the source object to save it in
+        // the status cache.
+        $source->files['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;
     }
   }
 
-  state()->set('locale_translation_status', $results);
-  state()->set('locale_translation_status_last_update', REQUEST_TIME);
+  locale_translation_status_save($results);
 }
 
 /**
@@ -160,10 +147,10 @@ function locale_translation_batch_status_compare(&$context) {
  */
 function locale_translation_batch_status_finished($success, $results) {
   $t = get_t();
-  if($success) {
-    if ($results) {
+  if ($success) {
+    if ($results['sources']) {
       drupal_set_message(format_plural(
-        count($results),
+        count($results['sources']),
         'Checked available interface translation updates for one project.',
         'Checked available interface translation updates for @count projects.'
       ));
@@ -175,18 +162,184 @@ function locale_translation_batch_status_finished($success, $results) {
 }
 
 /**
+ * Batch operation: Download a remote translation file.
+ *
+ * @param $translation source object
+ *   @todo
+ * @param $context
+ *   Batch context array.
+ */
+function locale_translation_batch_fetch_download($source, &$context) {
+  $t = get_t();
+  module_load_include('fetch.inc', 'locale');
+  if ($file = locale_translation_download_source($source->files['remote'])) {
+    $context['message'] = $t('Importing: %name.', array('%name' => $file->filename));
+    $source->files['download'] = $file;
+  }
+  else {
+    // @todo Error handling when download fails.
+    watchdog('locale', 'Unable to download translation file @uri.', array('@uri' => $source->files['remote']->uri), WATCHDOG_ERROR);
+    $context['results']['failed_files'][] = $source->files['remote'];
+  }
+  $context['results']['sources'][$source->name][$source->language] = $source;
+}
+
+/**
+ * Batch process: Import translation file.
+ *
+ * This takes a file parameter or continues from previous batch
+ * which should have downloaded a file.
+ *
+ * @param $id
+ *   Batch id to identify batch results.
+ *   Result of this batch function are stored in $context['result']
+ *   identified by this $id.
+ * @param $file
+ *   File to be imported. If empty, the file will be taken from $context['results']['sources'].
+ * @param $mode
+ *   Import mode. How to treat existing and modified translations.
+ * @param $context
+ *   Batch context array.
+ */
+function locale_translation_batch_fetch_import($source, $options, &$context) {
+  $t = get_t();
+  // If we are working on a remote file we will import the downloaded file and
+  // use the result data from the
+  if ($source->type == 'remote') {
+    if (isset($context['results']['sources'][$source->project][$source->language]->files['download'])) {
+      $import_type = 'download';
+      $source_type = 'remote';
+      $source_result = $context['results']['sources'][$source->project][$source->language];
+    }
+  }
+  if ($source->type == 'local') {
+    $import_type = 'local';
+    $source_type = 'local';
+    $source_result = $source;
+  }
+
+// @todo Fault handling using try/catch. For other batch functions too?
+
+  if (isset($import_type)) {
+    $file = $source_result->files[$import_type];
+    module_load_include('bulk.inc', 'locale');
+    $options += array(
+      'customized' => LOCALE_NOT_CUSTOMIZED,
+      'message' => 'Importing: ' . $file->filename,
+    );
+
+    locale_translate_batch_import($file, $options, $context);
+    // Import is finished.
+    if (isset($context['finished']) && $context['finished'] == 1) {
+      // Import is successfull.
+      if (isset($context['results']['files'][$file->uri])) {
+        $context['message'] = $t('Imported: %name.', array('%name' => $file->filename));
+
+        // Store the data of imported source. This data can later be saved in {locale_file}.
+        $source_result->files['import'] = $source_result->files[$source_type];
+
+        // Downloaded files are stored in the temporary files directory. On
+        // succesfull import, the files are moved to the local destination
+        // directory if files should be kept locally. Otherwise the temporary
+        // files are deleted.
+        if ($import_type == 'download' && variable_get('locale_translate_file_directory', conf_path() . '/files/translations') && isset($source_result->files['local'])) {
+          // Move the temporary file to the local translations directory.
+          // @todo What to do if the file can not be moved? If it throws an error (watchdog), should we set a batch error?
+          if (file_unmanaged_move($file->uri, $source_result->files['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 = 'local';
+          }
+        }
+        if ($import_type == 'download') {
+          // The temporary file is now imported and will not be used further.
+          // Store the timestamp and delete the file.
+          $timestamp = filemtime($source_result->files[$import_type]->uri);
+          $source_result->files['import']->timestamp = $timestamp;
+          $source_result->files['import']->last_checked = REQUEST_TIME;
+          file_unmanaged_delete($file->uri);
+        }
+        if ($import_type == 'local') {
+          // Import type is 'local'.
+          $timestamp = filemtime($source_result->files[$import_type]->uri);
+          $source_result->files['local']->timestamp = $timestamp;
+          $source_result->files['import']->timestamp = $timestamp;
+          $source_result->files['import']->last_checked = REQUEST_TIME;
+
+        }
+      }
+      else {
+        // File import failed. We can delete the temporary file.
+        file_unmanaged_delete($file->uri);
+      }
+    }
+    $context['results']['sources'][$source->project][$source->language] = $source_result;
+  }
+}
+
+/**
+ * Batch process: Update the download history table.
+ *
+ * @param $id
+ *   Batch id to identify batch results.
+ *   Result of this batch function are stored in $context['result']
+ *   identified by this $id.
+ * @param $context
+ *   Batch context array.
+ */
+function locale_translation_batch_fetch_update_status(&$context) {
+  $results = array();
+
+  foreach ($context['results']['sources'] as $project => $langcodes) {
+    foreach ($langcodes as $langcode => $source) {
+
+      // Store the state of the translation in {locale_file} table. During the
+      // batch execution the data of imported file is kept in
+      // $context['results']['sources'] will now be stored in the database.
+      if (isset($source->files['import'])) {
+        $file = $source->files['import'];
+        locale_translation_update_file_history($file);
+        // @todo Unset. Leave it disabled for debugging.
+        //unset($source->files['import']);
+      }
+      // @todo Unset. Leave it disabled for debugging.
+      //unset($source->files['download']);
+      // 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 use to update the translation status.
+      $context['results']['sources'][$project][$langcode] = $source;
+    }
+  }
+}
+
+/**
+ * Batch finished callback: Set result message.
+ *
+ * @param $success
+ *   TRUE if batch succesfully completed.
+ * @param $results
+ *   Batch results.
+ */
+function locale_translation_batch_fetch_finished($success, $results) {
+  // @todo move locale_translate_batch_finished() into locale.batch.inc.
+  module_load_include('bulk.inc', 'locale');
+  return locale_translate_batch_finished($success, $results);
+}
+
+/**
  * Check if remote file exists and when it was last updated.
  *
- * @param string $url
- *   URL of remote file.
+ * @param string $uri
+ *   URI of remote file.
  * @param array $headers
  *   HTTP request headers.
  * @return stdClass
  *   Result object containing the HTTP request headers, response code, headers,
  *   data, redirect status and updated timestamp.
  */
-function locale_translation_http_check($url, $headers = array()) {
-  $result = drupal_http_request($url, array('headers' => $headers, 'method' => 'HEAD'));
+function locale_translation_http_check($uri, $headers = array()) {
+  $result = drupal_http_request($uri, array('headers' => $headers, 'method' => 'HEAD'));
   if ($result && $result->code == '200') {
     $result->updated = isset($result->headers['last-modified']) ? strtotime($result->headers['last-modified']) : 0;
   }
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 0be2aec..bf10dc5 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -405,9 +405,25 @@ function locale_translate_batch_build($files, $options) {
   if (count($files)) {
     $operations = array();
     foreach ($files as $file) {
+      // @todo: Check if regex works with missing project and version.
+      // @todo: move the language part to the form submit function?
+      // The filename is either {langcode}.po or {prefix}.{langcode}.po.
+      // Try to get the project name, project version and language code file name.
+      // The filename is either {langcode}.po or {project}-{version}.{langcode}.po.
+      // We use the language from either the $options or the file name.
+      preg_match('!([a-z_]+)\-([0-9a-z\.\-\+]+)\.([^\./]+)\.po$!', $file->filename, $matches);
+      if (is_array($matches)) {
+        $file->project = isset($matches[1]) ? $matches[1] : '';
+        $file->version = isset($matches[2]) ? $matches[2] : '';
+        $file->langcode = isset($options['langcode']) ? $options['langcode'] : isset($matches[3]) ? $matches[3] : LANGUAGE_NOT_SPECIFIED;
+      }
+
       // We call locale_translate_batch_import for every batch operation.
-      $operations[] = array('locale_translate_batch_import', array($file->uri, $options));
+      $operations[] = array('locale_translate_batch_import', array($file, $options));
     }
+    // Save the translation status of all files.
+    $operations[] =  array('locale_translate_batch_import_save', array());
+
     $batch = array(
       'operations'    => $operations,
       'title'         => $t('Importing interface translations'),
@@ -426,11 +442,12 @@ function locale_translate_batch_build($files, $options) {
 /**
  * Perform interface translation import as a batch step.
  *
+ * @todo Rework description.
  * The given filepath is matched against ending with '{langcode}.po'. When
  * matched the filepath is added to batch context.
  *
- * @param $filepath
- *   Path to a file to import.
+ * @param $file
+ *   File object ... @todo
  *
  * @param array $options
  *   An array with options that can have the following elements:
@@ -441,27 +458,19 @@ function locale_translate_batch_build($files, $options) {
  *     are customized translations or come from a community source. Use
  *     LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to
  *     LOCALE_NOT_CUSTOMIZED.
+ *   - 'message': Alternative message to display during import.
  *
  * @param $context
  *   Contains a list of files imported.
  */
-function locale_translate_batch_import($filepath, $options, &$context) {
+function locale_translate_batch_import($file, $options, &$context) {
   // Merge the default values in the $options array.
   $options += array(
     'overwrite_options' => array(),
     'customized' => LOCALE_NOT_CUSTOMIZED,
   );
-  // The filename is either {langcode}.po or {prefix}.{langcode}.po, so
-  // we can extract the language code to use for the import from the end.
-  if (isset($options['langcode']) && $options['langcode'] ||
-      preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $matches)) {
-    $basename = drupal_basename($filepath);
-    $file = entity_create('file', array('filename' => $basename, 'uri' => 'translations://'. $basename));
-    // We need only the last match, but only if the langcode is not explicitly
-    // specified in the $options array.
-    if (!$options['langcode'] && is_array($matches)) {
-      $options['langcode'] = array_pop($matches);
-    }
+  if (isset($file->langcode) && $file->langcode != LANGUAGE_NOT_SPECIFIED) {
+
     try {
       if (empty($context['sandbox'])) {
         $context['sandbox']['parse_state'] = array(
@@ -477,87 +486,125 @@ function locale_translate_batch_import($filepath, $options, &$context) {
       // If not yet finished with reading, mark progress based on size and
       // position.
       if ($report['seek'] < filesize($file->uri)) {
+
         $context['sandbox']['parse_state']['seek'] = $report['seek'];
         // Maximize the progress bar at 95% before completion, the batch API
         // could trigger the end of the operation before file reading is done,
         // because of floating point inaccuracies. See
         // http://drupal.org/node/1089472
         $context['finished'] = min(0.95, $report['seek'] / filesize($file->uri));
-        $context['message'] = t('Importing file: %filename (@percent%)', array('%filename' => $file->filename, '@percent' => (int) ($context['finished'] * 100)));
+        if (isset($options['message'])) {
+          $context['message'] = t('@message (@percent%)', array('@message' => $options['message'], '@percent' => (int) ($context['finished'] * 100)));
+        }
+        else {
+          $context['message'] = t('Importing file: %filename (@percent%)', array('%filename' => $file->filename, '@percent' => (int) ($context['finished'] * 100)));
+        }
       }
       else {
         // We are finished here.
         $context['finished'] = 1;
-        $file->langcode = $options['langcode'];
+
+        // Store the file data for processing by the next batch operation.
         $file->timestamp = filemtime($file->uri);
-        locale_translate_update_file_history($file);
-        $context['results']['files'][$filepath] = $filepath;
-        $context['results']['languages'][$filepath] = $file->langcode;
+        $context['results']['files'][$file->uri] = $file;
+        $context['results']['languages'][$file->uri] = $file->langcode;
       }
       // Add the values from the report to the stats for this file.
-      if (!isset($context['results']['stats']) || !isset($context['results']['stats'][$filepath])) {
-        $context['results']['stats'][$filepath] = array();
+      if (!isset($context['results']['stats']) || !isset($context['results']['stats'][$file->uri])) {
+        $context['results']['stats'][$file->uri] = array();
       }
       foreach ($report as $key => $value) {
         if (is_numeric($value)) {
-          $context['results']['stats'][$filepath] += array($key => 0);
-          $context['results']['stats'][$filepath][$key] += $value;
+          $context['results']['stats'][$file->uri] += array($key => 0);
+          $context['results']['stats'][$file->uri][$key] += $value;
         }
         elseif (is_array($value)) {
-          $context['results']['stats'][$filepath] += array($key => array());
-          $context['results']['stats'][$filepath][$key] = array_merge($context['results']['stats'][$filepath][$key], $value);
+          $context['results']['stats'][$file->uri] += array($key => array());
+          $context['results']['stats'][$file->uri][$key] = array_merge($context['results']['stats'][$file->uri][$key], $value);
+        }
+        if (is_numeric($report[$key])) {
+          if (!isset($context['results']['stats'][$file->uri][$key])) {
+            $context['results']['stats'][$file->uri][$key] = 0;
+          }
+          $context['results']['stats'][$file->uri][$key] += $report[$key];
         }
       }
     }
     catch (Exception $exception) {
-      $context['results']['files'][$filepath] = $filepath;
-      $context['results']['failed_files'][$filepath] = $filepath;
+      // Import failed. Store the data of the failing file.
+      $context['results']['failed_files'][] = $file;
+      watchdog('locale', 'Unable to import translations file: @file', array('@file' => $file->uri));
     }
   }
 }
 
 /**
+ * Batch callback: Save data of imported files.
+ *
+ * @param $context
+ *   Contains a list of imported files.
+ */
+function locale_translate_batch_import_save($context) {
+  foreach ($context['results']['files'] as $file) {
+    $file->last_checked = REQUEST_TIME;
+    locale_translation_update_file_history($file);
+  }
+}
+
+/**
  * Finished callback of system page locale import batch.
  */
 function locale_translate_batch_finished($success, $results) {
   if ($success) {
     $additions = $updates = $deletes = $skips = 0;
     $strings = $langcodes = array();
-    drupal_set_message(format_plural(count($results['files']), 'One translation file imported.', '@count translation files imported.'));
-    $skipped_files = array();
-    // If there are no results and/or no stats (eg. coping with an empty .po
-    // file), simply do nothing.
-    if ($results && isset($results['stats'])) {
-      foreach ($results['stats'] as $filepath => $report) {
-        $additions += $report['additions'];
-        $updates += $report['updates'];
-        $deletes += $report['deletes'];
-        $skips += $report['skips'];
-        if ($report['skips'] > 0) {
-          $skipped_files[] = $filepath;
+    if (isset($results['failed_files'])) {
+        if (module_exists('dblog')) {
+          $message = format_plural(count($results['failed_files']), 'One translation file could not be imported. <a href="@url">See the log</a> for details.', '@count translation files could not be imported. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog')));
         }
-        $strings = array_merge($strings, $report['strings']);
-      }
-      // Get list of unique string identifiers and language codes updated.
-      $strings = array_unique($strings);
-      $langcodes = array_unique(array_values($results['languages']));
+        else {
+          $message = format_plural(count($results['failed_files']), 'One translation files could not be imported. See the log for details.', '@count translation files could not be imported. See the log for details.');
+        }
+        drupal_set_message($message, 'error');
     }
-    drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
-    watchdog('locale', 'The translation was succesfully imported. %number new strings added, %update updated and %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
-    if ($skips) {
-      if (module_exists('dblog')) {
-        $skip_message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog')));
+    if (isset($results['files'])) {
+      drupal_set_message(format_plural(count($results['files']), 'One translation file imported.', '@count translation files imported.'));
+
+      $skipped_files = array();
+      // If there are no results and/or no stats (eg. coping with an empty .po
+      // file), simply do nothing.
+      if ($results && isset($results['stats'])) {
+        foreach ($results['stats'] as $filepath => $report) {
+          $additions += $report['additions'];
+          $updates += $report['updates'];
+          $deletes += $report['deletes'];
+          $skips += $report['skips'];
+          if ($report['skips'] > 0) {
+            $skipped_files[] = $filepath;
+          }
+          $strings = array_merge($strings, $report['strings']);
+        }
+        // Get list of unique string identifiers and language codes updated.
+        $strings = array_unique($strings);
+        $langcodes = array_unique(array_values($results['languages']));
       }
-      else {
-        $skip_message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.');
+      drupal_set_message(t('The translations were successfully imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
+      watchdog('locale', 'The translations were successfully imported. %number translations added, %update updated and %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+      if ($skips) {
+        if (module_exists('dblog')) {
+          $message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog')));
+        }
+        else {
+          $message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.');
+        }
+        drupal_set_message($message, 'error');
+        watchdog('locale', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING);
       }
-      drupal_set_message($skip_message, 'error');
-      watchdog('locale', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING);
-    }
 
-    if ($strings) {
-      // Clear cache and force refresh of JavaScript translations.
-      _locale_refresh_translations($langcodes, $strings);
+      if ($strings) {
+        // Clear cache and force refresh of JavaScript translations.
+        _locale_refresh_translations($langcodes, $strings);
+      }
     }
   }
 }
@@ -580,28 +627,6 @@ function locale_translate_file_create($filepath) {
 }
 
 /**
- * Update the {locale_file} table.
- *
- * @param $file
- *   Object representing the file just imported.
- *
- * @return integer
- *   FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED.
- *
- * @see drupal_write_record()
- */
-function locale_translate_update_file_history($file) {
-  // Update or write new record.
-  if (db_query("SELECT uri FROM {locale_file} WHERE uri = :uri AND langcode = :langcode", array(':uri' => $file->uri, ':langcode' => $file->langcode))->fetchField()) {
-    $update = array('uri', 'langcode');
-  }
-  else {
-    $update = array();
-  }
-  return drupal_write_record('locale_file', $file, $update);
-}
-
-/**
  * Deletes all interface translation files depending on the langcode.
  *
  * @param $langcode
diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc
index efe944b..b788cca 100644
--- a/core/modules/locale/locale.compare.inc
+++ b/core/modules/locale/locale.compare.inc
@@ -12,6 +12,7 @@
  * timestamp in seconds. The download time is stored in the database in
  * {locale_file}.timestamp.
  */
+// @todo Is this still required since we use filemtime() to get the timestamp of downloaded and imported files?
 const LOCALE_TRANSLATION_TIMESTAMP_THRESHOLD = 2;
 
 /**
@@ -260,7 +261,7 @@ function _locale_translation_prepare_project_list($data, $type) {
  *
  * @return array
  *   Array of server parameters:
- *   - "server_pattern": URL containing po file pattern.
+ *   - "server_pattern": URI containing po file pattern.
  */
 function locale_translation_default_translation_server() {
   $config = config('locale.settings');
@@ -308,6 +309,7 @@ function locale_translation_build_server_pattern($project, $template) {
 function locale_translation_check_projects($projects, $langcodes = NULL) {
   module_load_include('batch.inc', 'locale');
 
+// @todo What happens if LOCALE_TRANSLATION_USE_SOURCE_LOCAL and no local file is available??
   if (config('locale.settings')->get('translation.use_source') == LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL) {
     // Retrieve the status of both remote and local translation sources by
     // using a batch process.
@@ -343,12 +345,47 @@ function locale_translation_check_projects_batch($projects, $langcodes = NULL) {
   }
 
   // Build and set the batch process.
-  module_load_include('batch.inc', 'locale');
   $batch = locale_translation_batch_status_build($sources);
   batch_set($batch);
 }
 
 /**
+ * Build 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
+ * 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
+ * in the state variable 'locale_translation_status_last_update'.
+ *
+ * @param array $sources
+ *   Array of translation source objects for which to check the state of
+ *   translation source files.
+ */
+function locale_translation_batch_status_build($sources) {
+  $operations = array();
+
+  // Set the batch processes for remote sources.
+  foreach ($sources as $source) {
+    $operations[] = array('locale_translation_batch_status_fetch_remote', array($source));
+  }
+
+  // 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());
+
+  $batch = array(
+    'operations' => $operations,
+    'title' => t('Checking available translations'),
+    'finished' => 'locale_translation_batch_status_finished',
+    'error_message' => t('Error checking available interface translation updates.'),
+    'file' => drupal_get_path('module', 'locale') . '/locale.batch.inc',
+  );
+  return $batch;
+}
+
+/**
  * Check and store the status and timestamp of local po files.
  *
  * Only po files in the local file system are checked. Any remote translation
@@ -368,6 +405,7 @@ function locale_translation_check_projects_batch($projects, $langcodes = NULL) {
  */
 function locale_translation_check_projects_local($projects, $langcodes = NULL) {
   $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
@@ -379,13 +417,34 @@ function locale_translation_check_projects_local($projects, $langcodes = NULL) {
       if (locale_translation_source_check_file($source)) {
         $source->type = 'local';
         $source->timestamp = $source->files['local']->timestamp;
-        $results[$name][$langcode] = $source;
       }
+
+      // 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->language])) {
+        $current = $history[$source->project][$source->language];
+        // Add the current translation to the source object to save it in
+        // the status cache.
+        $source->files['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;
     }
   }
-
-  state()->set('locale_translation_status', $results);
-  state()->set('locale_translation_status_last_update', REQUEST_TIME);
+  locale_translation_status_save($results);
 }
 
 /**
@@ -397,9 +456,9 @@ function locale_translation_check_projects_local($projects, $langcodes = NULL) {
  *
  * The "local" files property of the source object contains the definition of a
  * po file we are looking for. The file name defaults to
- * LOCALE_TRANSLATION_DEFAULT_FILENAME. Per project this value
- * can be overridden using the server_pattern directive in the module's .info
- * file or by using hook_locale_translation_projects_alter().
+ * %project-%version.%language.po. Per project this value can be overridden
+ * using the server_pattern directive in the module's .info file or by using
+ * hook_locale_translation_projects_alter().
  *
  * @param stdClass $source
  *   Translation source object.
@@ -425,7 +484,7 @@ function locale_translation_source_check_file(&$source) {
       $directory = str_replace($scheme . '://', drupal_realpath($scheme . '://'), $directory);
     }
 
-    if ($files = file_scan_directory($directory, $filename, array('key' => 'name'))) {
+    if ($files = file_scan_directory($directory, $filename, array('key' => 'name', 'recurse' => FALSE))) {
       $file = current($files);
       $source->files['local']->uri = $file->uri;
       $source->files['local']->timestamp = filemtime($file->uri);
@@ -459,19 +518,24 @@ function locale_translation_source_check_file(&$source) {
  *   - "type": Most recent file type 'remote' or 'local'. Corresponding with
  *     a key of the "files" array.
  *   - "timestamp": Timestamp of the most recent translation file.
- *   The "files" array contains file objects with the following properties:
- *   - "uri": Local file path.
- *   - "url": Remote file URL for downloads.
+ *   The "files" array can hold file objects of type: 'local', 'remote',
+ *   'download', 'import' and 'current'. Each contains following properties:
+ *   - "type": The object type ('local', 'remote', etc. see above).
+ *   - "project": Project name.
+ *   - "langcode": Language code.
+ *   - "version": Project version.
+ *   - "uri": Local or remote file path.
  *   - "directory": Directory of the local po file.
  *   - "filename": File name.
  *   - "timestamp": Timestamp of the file.
  *   - "keep": TRUE to keep the downloaded file.
  */
-// @todo Move this file?
+// @todo Move this function?
 function locale_translation_source_build($project, $langcode, $filename = NULL) {
   // Create a source object with data of the project object.
   $source = clone $project;
   $source->project = $project->name;
+  // @todo Change this to langcode.
   $source->language = $langcode;
 
   $filename = $filename ? $filename : config('locale.settings')->get('translation.default_filename');
@@ -483,24 +547,35 @@ function locale_translation_source_build($project, $langcode, $filename = NULL)
   $files = array();
   if (_locale_translation_file_is_remote($source->server_pattern)) {
     $files['remote'] = (object) array(
+      'project' => $project->name,
+      'langcode' => $langcode,
+      'version' => $project->version,
       'type' => 'remote',
       'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)),
-      'url' => locale_translation_build_server_pattern($source, $source->server_pattern),
+      'uri' => locale_translation_build_server_pattern($source, $source->server_pattern),
     );
     if (variable_get('locale_translate_file_directory', conf_path() . '/files/translations')) {
       $files['local'] = (object) array(
+        'project' => $project->name,
+        'langcode' => $langcode,
+        'version' => $project->version,
         'type' => 'local',
-        'directory' => 'translations://',
         'filename' => locale_translation_build_server_pattern($source, $filename),
+        'directory' => 'translations://',
       );
+      $files['local']->uri = $files['local']->directory . $files['local']->filename;
     }
   }
   else {
     $files['local'] = (object) array(
+      'project' => $project->name,
+      'langcode' => $langcode,
+      'version' => $project->version,
       'type' => 'local',
-      'directory' => locale_translation_build_server_pattern($source, drupal_dirname($source->server_pattern)),
       'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)),
+      'directory' => locale_translation_build_server_pattern($source, drupal_dirname($source->server_pattern)),
     );
+    $files['local']->uri = $files['local']->directory . '/' . $files['local']->filename;
   }
   $source->files = $files;
 
@@ -510,14 +585,14 @@ function locale_translation_source_build($project, $langcode, $filename = NULL)
 /**
  * Determine if a file is a remote file.
  *
- * @param string $url
- *   The URL or URL pattern of the file.
+ * @param string $uri
+ *   The URI or URI pattern of the file.
  *
  * @return boolean
- *   TRUE if the $url is a remote file.
+ *   TRUE if the $uri is a remote file.
  */
-function _locale_translation_file_is_remote($url) {
-  $scheme = file_uri_scheme($url);
+function _locale_translation_file_is_remote($uri) {
+  $scheme = file_uri_scheme($uri);
   if ($scheme) {
     return !drupal_realpath($scheme . '://');
   }
diff --git a/core/modules/locale/locale.fetch.inc b/core/modules/locale/locale.fetch.inc
new file mode 100755
index 0000000..e628faa
--- /dev/null
+++ b/core/modules/locale/locale.fetch.inc
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * The API for download and import of translations from remote and local sources.
+ */
+
+/**
+ * Build a batch to download and import project translations.
+ *
+ * @param array $sources
+ *   Array of project translation states.
+ */
+function locale_translation_batch_fetch_build($sources) {
+  $t = get_t();
+  $batch = array(
+    'operations' => _locale_translation_fetch_operations($sources),
+    'title' => $t('Updating translation.'),
+    'init_message' => $t('Downloading and importing files.'),
+    'error_message' => $t('Error importing interface translations'),
+    'finished' => 'locale_translation_batch_fetch_finished',
+    'file' => drupal_get_path('module', 'locale') . '/locale.batch.inc',
+  );
+  return $batch;
+}
+
+/**
+ * Helper function to construct the batch operations to fetch translations.
+ *
+ * @param array $sources
+ *   Array of project translation states.
+ */
+function _locale_translation_fetch_operations($sources) {
+  $operations = array();
+  $config = config('locale.settings');
+  // @todo Make follow up issue for different UI options. Back to radio's, not current checkboxes.
+  $options = array(
+    'overwrite_options' => array(
+      'not_customized' => $config->get('translation.overwrite_not_customized'),
+      'customized' => $config->get('translation.overwrite_customized'),
+    ),
+  );
+
+  foreach ($sources as $source) {
+    if (isset($source->type)) {
+      if ($source->type == 'remote') {
+        // Download + import
+        $operations[] = array('locale_translation_batch_fetch_download', array($source));
+        $operations[] = array('locale_translation_batch_fetch_import', array($source, $options));
+      }
+      elseif ($source->type == 'local') {
+        // Import local files.
+        $operations[] = array('locale_translation_batch_fetch_import', array($source, $options));
+      }
+    }
+  }
+  if (count($operations)) {
+    // 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;
+}
+
+/**
+ * Download source file from a remote server.
+ *
+ * The downloaded file is stored in the temporary files directory.
+ *
+ * @param object $source_file
+ *   Source file object with at least:
+ *   - "uri": uri to download the file from.
+ *   - "project": Project name.
+ *   - "langcode": Translation language.
+ *   - "version": Project version.
+ *   - "filename": File name.
+ * @return object
+ *   File object if download 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 = 'download';
+    $file->uri = $uri;
+    $file->filename = $source_file->filename;
+    return $file;
+  }
+  return FALSE;
+}
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index ce6e1e5..67b7db5 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -171,34 +171,55 @@ function locale_schema() {
   $schema['locale_file'] = array(
     'description' => 'File import status information for interface translation files.',
     'fields' => array(
+      'project' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'A unique short name to identify the project the file belongs to.',
+      ),
       'langcode' => array(
-        'description' => 'Reference to the {languages}.langcode for this translation.',
         'type' => 'varchar',
         'length' => '12',
         'not null' => TRUE,
+        'default' => '',
+        'description' => 'Language code of this translation. References {language}.langcode.',
       ),
       'filename' => array(
-        'description' => 'Filename for importing the file.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
         'default' => '',
+        'description' => 'Filename of the imported file.',
+      ),
+      'version' => array(
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Version tag of the imported file.',
       ),
       'uri' => array(
-        'description' => 'File system path for importing the file.',
         'type' => 'varchar',
         'length' => 255,
         'not null' => TRUE,
         'default' => '',
+        'description' => 'URI of the remote file, the resulting local file or the locally imported file.',
       ),
       'timestamp' => array(
-        'description' => 'Unix timestamp of the file itself from the point when it was last imported.',
         'type' => 'int',
         'not null' => FALSE,
         'default' => 0,
+        'description' => 'Unix timestamp of the imported file.',
+      ),
+      'last_checked' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+        'default' => 0,
+        'description' => 'Unix timestamp of the last time this translation was confirmed to be the most recent release available.',
       ),
     ),
-    'primary key' => array('uri', 'langcode'),
+    'primary key' => array('project', 'langcode'),
   );
 
   $schema['locale_project'] = array(
@@ -796,6 +817,72 @@ function locale_update_8014() {
 }
 
 /**
+ * Build a new {locale_file} table.
+ */
+function locale_update_8015() {
+  // The existing table has a primary key on uri and langcode. The new key
+  // is on project and langcode. There is no project data in the existing table,
+  // and it may not be possible to generate this reliably. Therefore we drop
+  // the table and build it again.
+  db_drop_table('locale_file');
+
+  $table = array(
+    'description' => 'File import status information for interface translation files.',
+    'fields' => array(
+      'project' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'A unique short name to identify the project the file belongs to.',
+      ),
+      'langcode' => array(
+        'type' => 'varchar',
+        'length' => '12',
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Language code of this translation. References {language}.langcode.',
+      ),
+      'filename' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Filename of the imported file.',
+      ),
+      'version' => array(
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Version tag of the imported file.',
+      ),
+      'uri' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'URI of the remote file, the resulting local file or the locally imported file.',
+      ),
+      'timestamp' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+        'default' => 0,
+        'description' => 'Unix timestamp of the imported file.',
+      ),
+      'last_checked' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+        'default' => 0,
+        'description' => 'Unix timestamp of the last time this translation was confirmed to be the most recent release available.',
+      ),
+    ),
+    'primary key' => array('project', 'langcode'),
+  );
+  db_create_table('locale_file', $table);
+}
+
+/**
  * @} End of "addtogroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 3ebe97d..8578985 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -94,13 +94,25 @@
 const LOCALE_TRANSLATION_DEFAULT_SERVER_PATTERN = 'http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po';
 
 /**
- * Default file name of translation files stored in the local file system.
- *
- * The file name containing placeholders which are also used by the server
- * pattern. See locale_translation_build_server_pattern() for supported
- * placeholders.
+ * The number of seconds that the translations status entry should be considered.
+ */
+const LOCALE_TRANSLATION_STATUS_TTL = 600;
+
+/**
+ * @todo
+ */
+const LOCALE_TRANSLATION_OVERWRITE_ALL = 'all';
+
+/**
+ * @todo
+ */
+const LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED = 'non_customized';
+
+/**
+ * @todo
  */
-const LOCALE_TRANSLATION_DEFAULT_FILENAME = '%project-%version.%language.po';
+const LOCALE_TRANSLATION_OVERWRITE_NONE = 'none';
+
 
 /**
  * Implements hook_help().
@@ -176,10 +188,20 @@ function locale_menu() {
     'type' => MENU_LOCAL_TASK,
     'file' => 'locale.bulk.inc',
   );
+  $items['admin/config/regional/translate/settings'] = array(
+    'title' => 'Settings',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('locale_translate_settings'),
+    'access arguments' => array('translate interface'),
+    'weight' => 40,
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'locale.pages.inc',
+  );
   $items['admin/reports/translations'] = array(
     'title' => 'Available translation updates',
     'description' => 'Get a status report about available interface translations for your installed modules and themes.',
-    'page callback' => 'locale_translation_status',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('locale_translation_status_form'),
     'access arguments' => array('translate interface'),
     'file' => 'locale.pages.inc',
   );
@@ -698,7 +720,7 @@ function locale_form_system_file_system_settings_alter(&$form, $form_state) {
     '#title' => t('Interface translations directory'),
     '#default_value' => variable_get('locale_translate_file_directory', conf_path() . '/files/translations'),
     '#maxlength' => 255,
-    '#description' => t('A local file system path where interface translation files are looked for. This directory must exist.'),
+    '#description' => t('A local file system path where interface translation files will be stored.'),
     '#after_build' => array('system_check_directory'),
     '#weight' => 10,
   );
@@ -745,6 +767,83 @@ function locale_preprocess_node(&$variables) {
 }
 
 /**
+ * Get current translation status from the {locale_file} table.
+ *
+ * @return array
+ *   Array of translation file objects.
+ */
+// @todo Is this the right place for this function?
+function locale_translation_get_file_history() {
+  $history = &drupal_static(__FUNCTION__, array());
+
+  if (empty($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 = 'current';
+      $history[$file->project][$file->langcode] = $file;
+    }
+  }
+  return $history;
+}
+
+/**
+ * Update the {locale_file} table.
+ *
+ * @param $file
+ *   Object representing the file just imported.
+ *
+ * @return integer
+ *   FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED.
+ *
+ * @see drupal_write_record()
+ */
+// @todo Is this the right place for this function?
+function locale_translation_update_file_history($file) {
+  // Update or write new record.
+  if (db_query("SELECT project FROM {locale_file} WHERE project = :project AND langcode = :langcode", array(':project' => $file->project, ':langcode' => $file->langcode))->fetchField()) {
+    $update = array('project', 'langcode');
+  }
+  else {
+    $update = array();
+  }
+  return drupal_write_record('locale_file', $file, $update);
+}
+
+/**
+ * Delete the history of downloaded translations.
+ *
+ * @param string $langcode
+ *   Language code of the file history to be deleted.
+ */
+function locale_translation_file_history_delete($langcode) {
+  db_delete('locale_file')
+    ->condition('language', $langcode)
+    ->execute();
+}
+
+/**
+ * Save the status of translation sources in static cache.
+ *
+ * @param array $data
+ *   Array of translation source data, structured by project name and langcode.
+ */
+function locale_translation_status_save($data) {
+  $status = 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;
+    }
+  }
+
+  state()->set('locale_translation_status', $status);
+  state()->set('locale_translation_status_last_update', REQUEST_TIME);
+}
+
+/**
  * Clear the translation status cache.
  */
 function locale_translation_clear_status() {
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 8ea8780..6cec6fa 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -467,19 +467,258 @@ function locale_translation_manual_status() {
 }
 
 /**
+ * Page callback: Configuration for interface translation.
+ *
+ * @see locale_menu()
+ */
+function locale_translate_settings($form, &$form_state) {
+  $config = config('locale.settings');
+
+  $form['update_interval_days'] = array(
+    '#type' => 'radios',
+    '#title' => t('Check for updates'),
+    '#default_value' => $config->get('translation.update_interval_days'),
+    '#options' => array(
+      '0' => t('Never (manually)'),
+      '1' => t('Daily'),
+      '7' => t('Weekly'),
+    ),
+    '#description' => t('Select how frequently you want to check for new interface translations for your currently installed modules and themes.'),
+  );
+
+  $form['check_disabled_modules'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Check for updates of disabled modules and themes'),
+    '#default_value' => $config->get('translation.check_disabled_modules'),
+  );
+
+  if ($directory = variable_get('locale_translate_file_directory', conf_path() . '/files/translations')) {
+    $description = t('Translation files are stored locally in the  %path directory. You can change this directory on the <a href="@url">File system</a> configuration page.', array('%path' => $directory, '@url' => url('admin/config/media/file-system')));
+  }
+  else {
+    $description = t('Translation files will not be stored locally. Change the Interface translation directory on the <a href="@url">File system configuration</a> page.', array('@url' => url('admin/config/media/file-system')));
+  }
+  $form['#translation_directory'] = $directory;
+  $form['use_source'] = array(
+    '#type' => 'radios',
+    '#title' => t('Translation source'),
+    '#default_value' => $config->get('translation.use_source'),
+    '#options' => array(
+      LOCALE_TRANSLATION_USE_SOURCE_REMOTE_AND_LOCAL => t('Remote and local'),
+      LOCALE_TRANSLATION_USE_SOURCE_LOCAL => t('Local only'),
+    ),
+    '#description' => t('The source of translation files for automatic interface translation.') . ' ' . $description,
+  );
+
+  if ($config->get('translation.overwrite_not_customized') == FALSE) {
+    $default = LOCALE_TRANSLATION_OVERWRITE_NONE;
+  }
+  elseif ($config->get('translation.overwrite_customized') == TRUE) {
+    $default = LOCALE_TRANSLATION_OVERWRITE_ALL;
+  }
+  else {
+    $default = LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED;
+  }
+  $form['overwrite'] = array(
+    '#type' => 'radios',
+    '#title' => t('Import behaviour'),
+    '#default_value' => $default,
+    '#options' => array(
+      LOCALE_TRANSLATION_OVERWRITE_NONE => t("Don't overwrite existing translations."),
+      LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED => t('Only overwrite imported translations, customized translations are kept.'),
+      LOCALE_TRANSLATION_OVERWRITE_ALL => t('Overwrite existing translations.'),
+    ),
+    '#description' => t('How to treat existing translations when automatically updating the interface translations.'),
+  );
+
+  return system_config_form($form, $form_state);
+}
+
+/**
+ * @todo
+ */
+function locale_translate_settings_validate($form, &$form_state) {
+  if (empty($form['#translation_directory']) && $form_state['values']['use_source'] == LOCALE_TRANSLATION_USE_SOURCE_LOCAL) {
+    form_set_error('use_source', t('You have selected local translation source, but no <a href="@url">Interface translation directory</a> was configured.', array('@url' => url('admin/config/media/file-system'))));
+  }
+}
+
+/**
+ * @todo
+ */
+function locale_translate_settings_submit($form, &$form_state) {
+  $values = $form_state['values'];
+
+  $config = config('locale.settings');
+  $config->set('translation.update_interval_days', $values['update_interval_days'])->save();
+  $config->set('translation.use_source', $values['use_source'])->save();
+
+  switch ($values['overwrite']) {
+    case LOCALE_TRANSLATION_OVERWRITE_ALL:
+      $config->set('translation.overwrite_customized', TRUE)->save();
+      $config->set('translation.overwrite_not_customized', TRUE)->save();
+      break;
+    case LOCALE_TRANSLATION_OVERWRITE_NON_CUSTOMIZED:
+      $config->set('translation.overwrite_customized', FALSE)->save();
+      $config->set('translation.overwrite_not_customized', TRUE)->save();
+      break;
+    case LOCALE_TRANSLATION_OVERWRITE_NONE:
+      $config->set('translation.overwrite_customized', FALSE)->save();
+      $config->set('translation.overwrite_not_customized', FALSE)->save();
+      break;
+  }
+
+  $config->set('translation.check_disabled_modules', $values['check_disabled_modules'])->save();
+
+  // Invalidate the cached translation status when the configuration setting of
+  // 'use_source' and 'check_disabled_modules' change.
+  if ($form['use_source']['#default_value'] != $form_state['values']['use_source'] ||
+      $form['check_disabled_modules']['#default_value'] != $form_state['values']['check_disabled_modules']) {
+    locale_translation_clear_status();
+  }
+}
+
+/**
  * Page callback: Display the current translation status.
  *
  * @see locale_menu()
  */
-function locale_translation_status() {
+function locale_translation_status_form($form, &$form_state) {
+  module_load_include('compare.inc', 'locale');
+  //locale_translation_flush_projects();
+  $projects = locale_translation_get_projects();
   $languages = locale_translatable_language_list();
+  $status = state()->get('locale_translation_status');
+
   if (!$languages) {
     drupal_set_message(t('No translatable languages available. <a href="@add_lanuage">Add language</a> first.', array('@add_lanuage' => url('admin/config/regional/language'))), 'warning');
   }
 
+  // === Start of temporay code. For development only ===
+  $last = state()->get('locale_translation_status_last_update');
+  $markup = '<p class="locale checked">';
+  $markup .= $last ? t('Last checked: @time ago', array('@time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never');
+  $markup .= ' <span class="check-manually">(' . l(t('Check manually'), 'admin/reports/translations/check', array('query' => drupal_get_destination())) . ')</span>';
+  $markup .= '</p>';
+  $form['last_checked'] = array(
+    '#markup' => $markup,
+  );
+
+  $table_header = array(t('Project'), t('Version'), t('Status'), t('Current version'), t('Timestamp'), t('Recommended version'), t('Timestamp'), t('Source'));
+  $rows = array();
+  foreach ($projects as $name => $project) {
+    foreach ($languages as $langcode => $language) {
+      if (isset($status[$name][$langcode])) {
+        $source = $status[$name][$langcode];
+        $row = array(
+          'project' => $name,
+          'version' => $source->version,
+          'status' => isset($source->type) ? $source->type == 'current' ? t('Up to date') : t('Translations available') : t('Unknown'),
+          'current version' => '',
+          'current timestamp' => '',
+          'recommended version' => '',
+          'recommended timestamp' => '',
+          'recommended source' => '',
+        );
+        if (isset($source->files['current'])) {
+          $current = $source->files['current'];
+          $row['current version'] = $current->version;
+          $row['current timestamp'] = format_date($current->timestamp, 'custom', 'j M - G:i:s');
+        }
+        if ($row['status'] == t('Translations available')) {
+          $available = $source->files[$source->type];
+          $row['recommended version'] = $available->version;
+          $row['recommended timestamp'] = format_date($available->timestamp, 'custom', 'j M - G:i:s');
+          $row['recommended source'] = $available->type;
+        }
+      }
+      else {
+        $row = array(
+          'project' => $project,
+          'version' => $source->version,
+          'status' => t('Unknown'),
+          'current version' => '',
+          'current timestamp' => '',
+          'recommended version' => '',
+          'recommended timestamp' => '',
+          'recommended source' => '',
+        );
+      }
+      $rows[] = $row;
+    }
+  }
+  $form['table'] = array(
+    '#markup' => theme('table', array('header' => $table_header, 'rows' => $rows)),
+  );
+
   // @todo Calculate and display the translation status here. See the follow-up
   // issue for translation interface: http://drupal.org/node/1804702
-  return 'TODO: Show the translation status here';
+  // This form content is temporary and required to get the tests working.
+  $options = array();
+  foreach ($projects as $name => $project) {
+    $options[$name] = $project->name;
+  }
+  $form['projects'] = array(
+    '#title' => t('Projects'),
+    '#type' => 'checkboxes',
+    '#options' => $options,
+    '#default_value' => drupal_map_assoc(array_keys($options)),
+    '#required' => TRUE,
+  );
+
+  $options = array();
+  foreach ($languages as $langcode => $language) {
+    $options[$langcode] = $language->name;
+  }
+  $form['langcodes'] = array(
+    '#title' => t('Languages'),
+    '#type' => 'checkboxes',
+    '#options' => $options,
+    '#default_value' => drupal_map_assoc(array_keys($options)),
+    '#required' => TRUE,
+  );
+
+  // === End of temporay code. For development only ===
+
+  $form['actions'] = array(
+    '#type' => 'actions'
+  );
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Update')
+  );
+  return $form;
+}
+
+/**
+ * Form submission handler for locale_translation_status().
+ */
+function locale_translation_status_form_submit($form, &$form_state) {
+  $langcodes = array_filter($form_state['values']['langcodes']);
+  $projects = array_filter($form_state['values']['projects']);
+
+  // If the status was updated recently we can immediately start updating. If
+  // the status is not expired we will get the status first.
+  $last_checked = state()->get('locale_translation_status_last_update');
+  if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) {
+    locale_translation_clear_status();
+    // @todo Batch with check status + download + import.
+  }
+  else {
+    module_load_include('fetch.inc', 'locale');
+    $status = state()->get('locale_translation_status');
+    // Use only the selected projects and languages for update.
+    $sources = array();
+    foreach($projects as $project) {
+      foreach ($langcodes as $langcode) {
+        if (isset($status[$project][$langcode])) {
+          $sources[] = $status[$project][$langcode];
+        }
+      }
+    }
+    $batch = locale_translation_batch_fetch_build($sources);
+    batch_set($batch);
+  }
 }
 
 /**
diff --git a/core/modules/locale/tests/modules/locale_test/locale_test.module b/core/modules/locale/tests/modules/locale_test/locale_test.module
index 03e455d..e084915 100644
--- a/core/modules/locale/tests/modules/locale_test/locale_test.module
+++ b/core/modules/locale/tests/modules/locale_test/locale_test.module
@@ -52,7 +52,7 @@ function locale_test_locale_translation_projects_alter(&$projects) {
         'name' => 'drupal',
         'info' => array (
           'name' => 'Drupal',
-          'interface translation server pattern' => $remote_url . '%core/%project/%project-%version.%language.txt',
+          'interface translation server pattern' => $remote_url . '%core/%project/%project-%version.%language._po',
           'package' => 'Core',
           'version' => '8.0',
           'project' => 'drupal',
@@ -67,7 +67,7 @@ function locale_test_locale_translation_projects_alter(&$projects) {
         'name' => 'contrib_module_one',
         'info' => array (
           'name' => 'Contributed module one',
-          'interface translation server pattern' => $remote_url . '%core/%project/%project-%version.%language.txt',
+          'interface translation server pattern' => $remote_url . '%core/%project/%project-%version.%language._po',
           'package' => 'Other',
           'version' => '8.x-1.1',
           'project' => 'contrib_module_one',
@@ -82,7 +82,7 @@ function locale_test_locale_translation_projects_alter(&$projects) {
         'name' => 'contrib_module_two',
         'info' => array (
           'name' => 'Contributed module two',
-          'interface translation server pattern' => $remote_url . '%core/%project/%project-%version.%language.txt',
+          'interface translation server pattern' => $remote_url . '%core/%project/%project-%version.%language._po',
           'package' => 'Other',
           'version' => '8.x-2.0-beta4',
           'project' => 'contrib_module_two',
@@ -97,7 +97,7 @@ function locale_test_locale_translation_projects_alter(&$projects) {
         'name' => 'contrib_module_three',
         'info' => array (
           'name' => 'Contributed module three',
-          'interface translation server pattern' => $remote_url . '%core/%project/%project-%version.%language.txt',
+          'interface translation server pattern' => $remote_url . '%core/%project/%project-%version.%language._po',
           'package' => 'Other',
           'version' => '8.x-1.0',
           'project' => 'contrib_module_three',
