diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareUnitTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareUnitTest.php
new file mode 100644
index 0000000..25861b5
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareUnitTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\Tests\LocaleCompareUnitTest.
+ */
+
+namespace Drupal\locale\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests for comparing status of existing project translations with available translations.
+ */
+class LocaleCompareUnitTest extends WebTestBase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Comparare project states',
+      'description' => 'Tests for comparing status of existing project translations with available translations.',
+      'group' => 'Locale',
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('locale', 'locale_test'));
+
+    // Create Article node type.
+    $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
+
+    // Create and login user.
+    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content'));
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Unit tests for translation status storage and translation status comparison.
+   */
+  function testLocaleCompare() {
+    module_load_include('compare.inc', 'locale');
+
+    // Check if hidden modules are not included.
+    $projects = locale_project_list();
+    $this->assertFalse(isset($projects['locale_test']), t('Hidden module not found'));
+
+    // Make the test modules look like a normal custom module. i.e. the module
+    // is not hidden. locale_test_system_info_alter() modifies the project info
+    // of the locale_test and locale_test_disabled modules.
+    variable_set('locale_test_system_info_alter', TRUE);
+
+    // Check if interface translation data is collected from .info file.
+    drupal_static_reset('locale_project_list');
+    $projects = locale_project_list();
+    $this->assertEqual($projects['locale_test']['info']['interface translation server url'], 'http://locale_example.com/files/translations/l10n_server.xml', t('%key found in project info.', array('%key' => 'interface translation url')));
+    $this->assertEqual($projects['locale_test']['info']['interface translation server pattern'], 'http://locale_example.com/files/translations/%core/%project/%project-%release.%language.po', t('%key found in project info.', array('%key' => 'interface translation server pattern')));
+    $this->assertEqual($projects['locale_test']['name'] , 'locale_test', t('%key found in project info.', array('%key' => 'interface translation project')));
+    $this->assertFalse(isset($projects['locale_test_disabled']), t('Disabled module not found'));
+
+    // Check if disabled modules are detected.
+    variable_set('locale_check_disabled', TRUE);
+    drupal_static_reset('locale_project_list');
+    $projects = locale_project_list();
+    $this->assertTrue(isset($projects['locale_test_disabled']), t('Disabled module found'));
+
+    // Check the fully processed list of project data of both
+    // enabled and disabled modules.
+    variable_set('locale_check_disabled', TRUE);
+    drupal_static_reset('locale_project_list');
+    $projects = locale_get_projects();
+    $this->assertEqual($projects['drupal']->name, 'drupal', t('Core project found'));
+    $this->assertEqual($projects['locale_test']->server_url, 'http://locale_example.com/files/translations/l10n_server.xml', t('Server data for custom module found.'));
+    $this->assertEqual($projects['locale_test_disabled']->status, 0, t('Disabled module found'));
+    variable_del('locale_check_disabled');
+
+    // Return the locale test modules back to their hidden state.
+    variable_del('locale_test_system_info_alter');
+  }
+
+}
diff --git a/core/modules/locale/locale.api.php b/core/modules/locale/locale.api.php
new file mode 100644
index 0000000..21f786a
--- /dev/null
+++ b/core/modules/locale/locale.api.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @todo
+ *
+ * @see locale_project_list().
+ */
+function hook_locale_projects_alter(&$projects) {
+  // @todo
+}
+
+// @todo Remove this list.
+// Whish list
+// ==========
+// * Let the translation server provide the name and pattern details, instead
+//   of defining it in the .info file or in code.
\ No newline at end of file
diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc
new file mode 100644
index 0000000..bbb0fcc
--- /dev/null
+++ b/core/modules/locale/locale.compare.inc
@@ -0,0 +1,286 @@
+<?php
+
+/**
+ * @file
+ *   @todo
+ */
+
+/**
+ * Default translation server name. 
+ * @see locale_default_translation_server().
+ */
+const LOCALE_DEFAULT_SERVER = 'localize.drupal.org';
+
+/**
+ * Default URL of the xml file at the translation server containing all available languages.
+ * @see locale_default_translation_server().
+ */
+const LOCALE_DEFAULT_SERVER_URL = 'http://localize.drupal.org/l10n_server.xml';
+
+/**
+ * Default pattern of path and name of the gettext file at the translation server.
+ * @see locale_default_translation_server().
+ */
+const LOCALE_DEFAULT_SERVER_PATTERN = 'http://ftp.drupal.org/files/translations/%core/%project/%project-%release.%language.po';
+
+use Drupal\Core\Cache;
+
+/**
+ * Get array of projects which are available for interface translation.
+ */
+function locale_get_projects() {
+  $projects = &drupal_static(__FUNCTION__, array());
+
+  if (empty($projects)) {
+    // Get project data from the database.
+    $projects = array();
+    $result = db_query('SELECT * FROM {locale_project}');
+
+    if ($result->rowCount() == 0) {
+      // At least the core project should be in the database, so we
+      // build the data if none are found.
+      locale_build_projects();
+      $result = db_query('SELECT * FROM {locale_project}');
+    }
+
+    foreach ($result as $project) {
+      $projects[$project->name] = $project;
+    }
+  }
+  return $projects;
+}
+
+/**
+ * Clear the project data table.
+ */
+function locale_flush_projects() {
+  db_truncate('locale_project')->execute();
+}
+
+/**
+ * Build list of projects and stores the result in the database.
+ *
+ * Based on l10n_update_build_projects().
+ */
+function locale_build_projects() {
+  // Get all currently stored projects.
+  // We need this data for drupal_write_record() to ensure an existing project
+  // is not written twice to the database.
+  // @todo Is there an alternative for this?
+  $current = array();
+  $result = db_query('SELECT name FROM {locale_project}');
+  foreach ($result as $project) {
+    $current[$project->name] = $project->name;
+  }
+
+  // Get the project list based on .info files.
+  $projects = locale_project_list();
+
+  // Mark all previous projects as disabled and store new project data.
+  db_update('locale_project')
+    ->fields(array(
+      'status' => 0,
+    ))
+    ->execute();
+
+  $default_server = locale_default_translation_server();
+
+  $project_updates = update_get_available(TRUE);
+  foreach ($projects as $name => $data) {
+    if (isset($project_updates[$name]['releases']) && $project_updates[$name]['project_status'] != 'not-fetched') {
+      // Find out if a dev version is installed.
+      if (preg_match("/^[0-9]+\.x-([0-9]+)\..*-dev$/", $data['info']['version'], $matches)) {
+        // Find a suitable release to use as alternative translation.
+        foreach ($project_updates[$name]['releases'] as $project_release) {
+          // The first release with the same major release number which is not
+          // a dev release is the one. Releases are sorted the most recent first.
+          if ($project_release['version_major'] == $matches[1] &&
+              (!isset($project_release['version_extra']) || $project_release['version_extra'] != 'dev')) {
+            $release = $project_release;
+            break;
+          }
+        }
+      }
+      elseif ($name == "drupal" || preg_match("/HEAD/", $data['info']['version'], $matches)) {
+        // Pick latest available release.
+        $release = array_shift($project_updates[$name]['releases']);
+      }
+
+      if (!empty($release['version'])) {
+        $data['info']['version'] = $release['version'];
+      }
+
+      unset($release);
+    }
+
+    $data += array(
+      'version' => isset($data['info']['version']) ? $data['info']['version'] : '',
+      'core' => isset($data['info']['core']) ? $data['info']['core'] : DRUPAL_CORE_COMPATIBILITY,
+      // The project can have its own interface translation server, we use default if not.
+      'server' => isset($data['info']['interface translation server']) ? $data['info']['interface translation server'] : NULL,
+      // A project can provide the server url to fetch metadata, or the path and filename pattern to download the gettext file.
+      'server_url' => isset($data['info']['interface translation server url']) ? $data['info']['interface translation server url'] : NULL,
+      'server_pattern' => isset($data['info']['interface translation server pattern']) ? $data['info']['interface translation server pattern'] : NULL,
+      'status' => $data['project_status'],
+    );
+    $project = (object) $data;
+    // Unless the project provides a full filename pattern, we try to build one.
+    if (!isset($project->server_pattern)) {
+      $server = NULL;
+      if ($project->server || $project->server_url) {
+        $server = locale_translation_server($project->server, $project->server_url);
+      }
+      else {
+        // Use the default server.
+        $server = locale_translation_server($default_server['server'], $default_server['server_url']);
+      }
+      if ($server) {
+        // Build the update path for this project, with project name and release replaced.
+        $project->server_pattern = locale_build_server_pattern($project, $server['update_url']);
+      }
+    }
+    // Create or update the project record.
+    $update = empty($current[$name]) ? array() : array('name');
+    drupal_write_record('locale_project', $project, $update);
+    $projects[$name] = $project;
+  }
+  return $projects;
+}
+
+/**
+ * @todo
+ * Based on l10n_update_project_list().
+ */
+function locale_project_list() {
+  $projects = &drupal_static(__FUNCTION__, array());
+  if (empty($projects)) {
+    module_load_include('compare.inc', 'update');
+    $projects = array();
+
+    $additional_whitelist = array(
+      'interface translation server',
+      'interface translation server url',
+      'interface translation server pattern',
+      'interface translation project',
+    );
+    $module_data = _locale_prepare_project_list($foo = system_rebuild_module_data());
+    $theme_data = _locale_prepare_project_list(system_rebuild_theme_data());
+    update_process_info_list($projects, $module_data, 'module', TRUE, $additional_whitelist);
+    update_process_info_list($projects, $theme_data, 'theme', TRUE, $additional_whitelist);
+    if (variable_get('locale_check_disabled', 0)) {
+      update_process_info_list($projects, $module_data, 'module', FALSE, $additional_whitelist);
+      update_process_info_list($projects, $theme_data, 'theme', FALSE, $additional_whitelist);
+    }
+
+    // Allow other modules to alter projects before fetching and comparing.
+    drupal_alter('locale_projects', $projects);
+  }
+  return $projects;
+}
+
+/**
+ * Prepare module and theme data.
+ * @todo
+ */
+function _locale_prepare_project_list($data) {
+  foreach ($data as $name => $file) {
+    // Include interface translation projects.
+    // Custom modules can bring their own gettext translation file.
+    // To enable import of this file the module must define
+    // 'interface translation project = myproject' in its .info file.
+    // To allow update_process_info_list() to identify this as a project
+    // the 'project' property is filled with the 'interface translation project'
+    // value.
+    if (isset($file->info['interface translation project'])) {
+      $data[$name]->info['project'] = $file->info['interface translation project'];
+    }
+  }
+  return $data;
+}
+
+/**
+ * @todo
+ * based on l10n_update_default_server().
+ */
+// @todo: Rework the individual vars to a hook_locale_translation_server_info() with sets of server definitions?
+function locale_default_translation_server() {
+  return array(
+    'server' => variable_get('locale_translation_default_server', LOCALE_DEFAULT_SERVER),
+    'server_url' => variable_get('locale_translation_default_server_url', LOCALE_DEFAULT_SERVER_URL),
+    'server_pattern' => variable_get('locale_translation_default_server_pattern', LOCALE_DEFAULT_SERVER_PATTERN),
+  );
+}
+
+/**
+ * @todo
+ * Based on l10n_update_server().
+ */
+function locale_translation_server($name = NULL, $url = NULL, $refresh = FALSE) {
+  $info = &drupal_static(__FUNCTION__, array());
+  $server_list = &drupal_static(__FUNCTION__ . ':server_list', array());
+
+  // Retrieve server list from modules
+  if (!isset($server_list) || $refresh) {
+    $server_list = module_invoke_all('locale_translation_servers');
+  }
+  // We need at least the server url to fetch all the information
+  if (!$url && $name && isset($server_list[$name])) {
+    $url = $server_list[$name]['server_url'];
+  }
+  // If we still don't have an url, cannot find this server, return false
+  if (!$url) {
+    return FALSE;
+  }
+  // Cache server information based on the url, refresh if asked
+  $cid = 'interface_translation_server:' . $url;
+  // @todo Clear cache using a separate function?
+  if ($refresh) {
+    unset($info);
+    cache('locale')->delete($cid);
+  }
+  if (!isset($info[$url])) {
+    if ($cache = cache('locale')->get($cid)) {
+      $info[$url] = $cache->data;
+    }
+    else {
+      module_load_include('fetch.inc', 'locale');
+      if ($name && !empty($server_list[$name])) {
+        // The name is in our list, it can be full data or just an url
+        $server = $server_list[$name];
+      }
+      else {
+        // This may be a new server provided by a module / package
+        $server = array('name' => $name, 'server_url' => $url);
+        // If searching by name, store the name => url mapping
+        if ($name) {
+          $server_list[$name] = $server;
+        }
+      }
+      // Now fetch server meta information form the server itself
+      if ($server = locale_get_server($server)) {
+        cache('locale')->set($cid, $server);
+        $info[$url] = $server;
+      }
+      else {
+        // If no server information, this will be FALSE. We won't search a server twice
+        $info[$url] = FALSE;
+      }
+    }
+  }
+  return $info[$url];
+}
+
+/**
+ * @todo
+ * Based on l10n_update_build_string().
+ */
+function locale_build_server_pattern($project, $template) {
+  $variables = array(
+    '%project' => $project->name,
+    '%release' => $project->version,
+    '%core' => $project->core,
+    '%language' => isset($project->language) ? $project->language : '%language',
+    '%filename' => isset($project->filename) ? $project->filename : '%filename',
+  );
+  return strtr($template, $variables);
+}
diff --git a/core/modules/locale/locale.fetch.inc b/core/modules/locale/locale.fetch.inc
new file mode 100644
index 0000000..8f6d01c
--- /dev/null
+++ b/core/modules/locale/locale.fetch.inc
@@ -0,0 +1,9 @@
+<?php
+
+/**
+ * @todo
+ * Based on l10n_update_get_server().
+ */
+function locale_get_server($url) {
+  // @todo convert l10n_update.parser.inc to PHP5 code. See update.fetch.inc.
+}
diff --git a/core/modules/locale/locale.info b/core/modules/locale/locale.info
index e0749db..287084a 100644
--- a/core/modules/locale/locale.info
+++ b/core/modules/locale/locale.info
@@ -4,3 +4,5 @@ package = Core
 version = VERSION
 core = 8.x
 dependencies[] = language
+dependencies[] = update
+files[] = locale.test
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index ac03baa..ac97649 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -127,6 +127,74 @@ function locale_schema() {
     ),
   );
 
+  $schema['locale_project'] = array(
+    'description' => 'Update information for project translations.',
+    'fields' => array(
+      'name' => array(
+        'description' => 'A unique short name to identify the project.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'project_type' => array(
+        'description' => 'Project type, may be core, module, theme',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'core' => array(
+        'description' => 'Core compatibility string for this project.',
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'version' => array(
+        'description' => 'Human readable name for project used on the interface.',
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'server' => array(
+        'description' => 'Translation server for this project. If empty, the default is used.',
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'server_url' => array(
+        'description' => 'URL of the xml file at the translation server containing all available languages.',
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'server_pattern' => array(
+        'description' => 'Pattern of path and name of the gettext file at the translation server.',
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      // @todo Is the status being used?
+      'status' => array(
+        'description' => 'Status flag. TBD',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+    ),
+    'primary key' => array('name'),
+  );
+
+  $schema['cache_locale'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_locale']['description'] = 'Cache table for the locale module to store various data.';
+
+  // @todo Enable if required.
+  //$schema['cache_locale_update'] = drupal_get_schema_unprocessed('system', 'cache');
+  //$schema['cache_locale_update']['description'] = 'Cache table for the Localization Update module to store information about available releases, fetched from central server.';
+
   return $schema;
 }
 
@@ -552,6 +620,16 @@ function locale_update_8009() {
 }
 
 /**
+ * Add a cache table for the locale module.
+ */
+function locale_update_8010() {
+  // Add a 'locale' cache.
+  $schema['cache_locale'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_locale']['description'] = 'Cache table for the locale module to store various data.';
+  drupal_install_schema($schema);
+}
+
+/**
  * @} End of "addtogroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/locale/tests/modules/locale_test/locale_test.info b/core/modules/locale/tests/modules/locale_test/locale_test.info
new file mode 100644
index 0000000..f8d47fc
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test/locale_test.info
@@ -0,0 +1,12 @@
+name = Locale test
+description = Support module for locale module testing.
+package = Testing
+version = VERSION
+core = 8.x
+hidden = TRUE
+
+; Definitions for interface translations.
+interface translation server = http://locale_example.com
+interface translation server url = http://locale_example.com/files/translations/l10n_server.xml
+interface translation server pattern = http://locale_example.com/files/translations/%core/%project/%project-%release.%language.po
+interface translation project = locale_test
diff --git a/core/modules/locale/tests/modules/locale_test/locale_test.module b/core/modules/locale/tests/modules/locale_test/locale_test.module
new file mode 100644
index 0000000..8e4235e
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test/locale_test.module
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Implements hook_system_info_alter().
+ */
+function locale_test_system_info_alter(&$info, $file, $type) {
+  // Only modify the system info if required.
+  // By default the locale_test modules are hidden and have a project specified.
+  // To test the module detection proces by locale_project_list() the
+  // test modules should mimic a custom module. I.e. be non-hidden.
+  if (!variable_get('locale_test_system_info_alter', FALSE)) {
+    return;
+  }
+
+  if ($file->name == 'locale_test' || $file->name == 'locale_test_disabled') {
+    // Make the module appear as not-disabled.
+    $info['hidden'] = FALSE;
+  }
+}
diff --git a/core/modules/locale/tests/modules/locale_test_disabled/locale_test_disabled.info b/core/modules/locale/tests/modules/locale_test_disabled/locale_test_disabled.info
new file mode 100644
index 0000000..249e855
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test_disabled/locale_test_disabled.info
@@ -0,0 +1,9 @@
+name = Disabled locale test
+description = Disabled support module for locale module testing.
+package = Testing
+version = VERSION
+core = 8.x
+hidden = TRUE
+
+; Definitions for interface translations.
+interface translation project = locale_test_disabled
diff --git a/core/modules/locale/tests/modules/locale_test_disabled/locale_test_disabled.module b/core/modules/locale/tests/modules/locale_test_disabled/locale_test_disabled.module
new file mode 100644
index 0000000..9d9cab3
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test_disabled/locale_test_disabled.module
@@ -0,0 +1,8 @@
+<?php
+
+/**
+ * @file
+ *   Nothing to do here.
+ *   This is just a disabled module for the locale test script.
+ */
+
