diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareTest.php
new file mode 100644
index 0000000..7f8882d
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleCompareTest.php
@@ -0,0 +1,77 @@
+<?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 {
+
+  /**
+   * 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',
+    );
+  }
+
+  /**
+   * 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']), t('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.
+    variable_set('locale_translation_test_system_info_alter', TRUE);
+
+    // Check if interface translation data is collected from hook_info.
+    drupal_static_reset('locale_translation_project_list');
+    $projects = locale_translation_project_list();
+    $this->assertEqual($projects['locale_test']['info']['interface translation server pattern'], 'core/modules/locale/test/modules/locale_test/%project-%version.%language.po', t('Interface translation parameter found in project info.'));
+    $this->assertEqual($projects['locale_test']['name'] , 'locale_test', t('%key found in project info.', array('%key' => 'interface translation project')));
+
+    // Check if disabled modules are detected.
+    variable_set('locale_translation_check_disabled', TRUE);
+    drupal_static_reset('locale_translation_project_list');
+    $projects = locale_translation_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_translation_check_disabled', TRUE);
+    drupal_static_reset('locale_translation_project_list');
+    $projects = locale_translation_get_projects();
+    $this->assertEqual($projects['drupal']->name, 'drupal', t('Core project found'));
+    $this->assertEqual($projects['locale_test']->server_pattern, 'core/modules/locale/test/modules/locale_test/%project-%version.%language.po', t('Interface translation parameter found in project info.'));
+    $this->assertEqual($projects['locale_test_disabled']->status, '0', t('Disabled module found'));
+    variable_del('locale_translation_check_disabled');
+
+    // Return the locale test modules back to their hidden state.
+    variable_del('locale_translation_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..81a8a6b
--- /dev/null
+++ b/core/modules/locale/locale.api.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Locale module.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Alter the list of projects to be updated by locale's interface translation.
+ *
+ * Locale module attempts to update the translation of those modules returned
+ * by update_get_projects(). Using this hook the data returned by 
+ * update_get_projects() can be altered or extended.
+ *
+ * Modules or distributions that use a dedicated translation server should use
+ * this hook to specify the translation sever parameters. These parameters are:
+ * - "interface translation server pattern": URL of the .po translation files
+ *   used to download the files from. The URL contains tokens which will be
+ *   replaced by appropriate values.
+ * The following tokens are available for the server pattern:
+ * - "%core": Core version. Value example: "8.x".
+ * - "%project": Project name. Value examples: "drupal", "media_gallery".
+ * - "%release": Project version release. Value examples: "8.1", "8.x-1.0".
+ * - "%language": Language code. Value examples: "fr", "pt-pt".
+ *
+ * @param array $projects
+ *   Project data as returned by update_get_projects().
+ *
+ * @see locale_project_list().
+ */
+function hook_locale_translation_projects_alter(&$projects) {
+  // The translations are located at a custom translation sever.
+  $projects['existing_project'] = array(
+    'info' => array(
+      'interface translation server pattern' => 'http://example.com/files/translations/%core/%project/%project-%release.%language.po',
+    ),
+  );
+}
+
+function hook_locale_translation_translation_servers() {
+  
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
+
+/**
+ * .info file properties for interface translation settings.
+ *
+ * Modules hosted on drupal.org, a project definition is automatically added to
+ * the .info file. Only modules with this project defintion are discovered by
+ * the update module and use it to check for new releases. Locale module uses
+ * the same data to build a list of module to check for new tranlations.
+ * Therefore modules not hosted at drupal.org, such as custom modules, custom
+ * themes, features and distributions, need a way to identify themselves to
+ * the Locale module if they have translations that require to be updated.
+ *
+ * Custom module which contain new strings should provide po file(s) containing
+ * source strings and string translations in gettext format. The translation
+ * file can be located both local and remote. Use the following .info file
+ * properties to inform Locale module to load and import the translations.
+ *
+ * Example .info file properties for a custom module with a po file located in
+ * the module's folder.
+ * @code
+ * interface translation project = example_module
+ * interface translation server pattern = sites/example.com/modules/custom/example_module/%project-%version.%language.po
+ * @endcode
+ *
+ * Multiple custom modules or themes sharing the same po file should have
+ * matching definitions. Such as modules and sub-modules or multiple modules in
+ * the same project/code tree. Both "interface translation project" and
+ * "interface translation server pattern" definitions of these modules should match.
+ *
+ * Example .info file properties for a custom module with a po file located on
+ * a remote translation server.
+ * @code
+ * interface translation project = example_module
+ * interface translation server pattern = http://example.com/files/translations/%core/%project/%project-%version.%language.po
+ * @endcode
+ *
+ * Custom themes, features and distributions can implement these .info file
+ * properties in their .info file too.
+ *
+ * To change the interface translation settings of modules and themes hosted at
+ * drupal.org use hook_locale_translation_projects_alter(). Possible changes
+ * include changing the po file location (server pattern) or removing the
+ * project from the translation update list.
+ *
+ * Available .info file properties:
+ * - "interface translation project": project name. Required.
+ *   Name of the project a (sub-)module belongs to. Multiple modules sharing
+ *   the same project name will be listed as one the translation status list.
+ * - "interface translation server pattern": URL of the .po translation files
+ *   used to download the files from. The URL contains tokens which will be
+ *   replaced by appropriate values. The file can be locate both at a local
+ *   relateve path, a local absolute path and a remote server location.
+ *
+ * The following tokens are available for the server pattern:
+ * - "%core": Core version. Value example: "8.x".
+ * - "%project": Project name. Value examples: "drupal", "media_gallery".
+ * - "%version": Project version release. Value examples: "8.1", "8.x-1.0".
+ * - "%language": Language code. Value examples: "fr", "pt-pt".
+ */
diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc
new file mode 100644
index 0000000..56b701a
--- /dev/null
+++ b/core/modules/locale/locale.compare.inc
@@ -0,0 +1,267 @@
+<?php
+
+/**
+ * @file
+ * The API for comparing project translation status with available translation.
+ */
+
+/**
+ * Default location of gettext file on the translation server.
+ *
+ * @see locale_translation_default_translation_server().
+ */
+const LOCALE_TRANSLATION_DEFAULT_SERVER_PATTERN = 'http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po';
+
+use Drupal\Core\Cache;
+
+/**
+ * Get array of projects which are available for interface translation.
+ *
+ * This project data contains all projects which will be checked for available
+ * interface translations.
+ *
+ * For full functionality this function depends on Update module.
+ * When Update module is enabled the project data will contain the most recent
+ * module status; both in enabled status as in version. When Update module is
+ * disabled this function will return the last known module state. The status
+ * will only be updated once Update module is enabled.
+ *
+ * @see locale_translation_build_projects().
+ *
+ * @return array
+ *   Array of project data for translation update. See
+ *   locale_translation_build_projects() for details.
+ */
+function locale_translation_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 && module_exists('update')) {
+      // At least the core project should be in the database, so we build the
+      // data if none are found.
+      locale_translation_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_translation_flush_projects() {
+  db_truncate('locale_project')->execute();
+}
+
+/**
+ * Builds list of projects and stores the result in the database.
+ *
+ * The project data is based on the project list supplied by the Update module.
+ * Only the properties required by Locale module is included and additional
+ * (custom) modules and translation server data is added.
+ *
+ * In case the Update module is disabled this function will return an empty
+ * array.
+ *
+ * @return array
+ *   Array of project data:
+ *   - "name": Project system name.
+ *   - "project_type": Project type, e.g. 'module', 'theme'.
+ *   - "core": Core release version, e.g. 8.x
+ *   - "version": Project release version, e.g. 8.x-1.0
+ *   - "server_pattern": Translation server po file pattern.
+ *   - "status": Project status, 1 = enabled.
+ */
+function locale_translation_build_projects() {
+  // This function depends on Update module. We degrade gracefully.
+  if (!module_exists('update')) {
+    return array();
+  }
+
+  // Get the project list based on .info files.
+  $projects = locale_translation_project_list();
+
+  $transaction = db_transaction();
+
+  // Mark all previous projects as disabled and store new project data.
+  db_update('locale_project')
+    ->fields(array(
+      'status' => 0,
+    ))
+    ->execute();
+
+  $default_server = locale_translation_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,
+      // A project can provide the path and filename pattern to download the
+      // gettext file. Use the default if not.
+      'server_pattern' => isset($data['info']['interface translation server pattern']) ? $data['info']['interface translation server pattern'] : $default_server['pattern'],
+      'status' => $data['project_status'] ? 1 : 0,
+    );
+    $project = (object) $data;
+    $projects[$name] = $project;
+
+    // Create or update the project record.
+    db_merge('locale_project')
+      ->key(array('name' => $project->name))
+      ->fields(array(
+        'name' => $project->name,
+        'project_type' => $project->project_type,
+        'core' => $project->core,
+        'version' => $project->version,
+        'server_pattern' => $project->server_pattern,
+        'status' => $project->status,
+      ))
+      ->execute();
+  }
+  return $projects;
+}
+
+/**
+ * Fetch an array of projects for translation update.
+ *
+ * @return array
+ *   Array of project data including .info file data.
+ */
+function locale_translation_project_list() {
+  // This function depends on Update module. We degrade gracefully.
+  if (!module_exists('update')) {
+    return array();
+  }
+
+  $projects = &drupal_static(__FUNCTION__, array());
+  if (empty($projects)) {
+    module_load_include('compare.inc', 'update');
+    $projects = array();
+
+    $additional_whitelist = array(
+      'interface translation project',
+      'interface translation server pattern',
+    );
+    $module_data = _locale_translation_prepare_project_list(system_rebuild_module_data(), 'module');
+    $theme_data = _locale_translation_prepare_project_list(system_rebuild_theme_data(), 'theme');
+    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_translation_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_translation_projects', $projects);
+  }
+  return $projects;
+}
+
+/**
+ * Prepare module and theme data.
+ *
+ * Modify .info file data before it is processed by update_process_info_list().
+ * In order for update_process_info_list() to recognize a project, it requires
+ * the 'project' parameter in the .info file data.
+ * Custom modules or themes can bring their own gettext translation file. To
+ * enable import of this file the module or theme defines "interface translation
+ * project = myproject" in its .info file. This function will add a project
+ * "myproject" to the info data.
+ *
+ * @param array $data
+ *   Array of .info file data.
+ * @param string $type
+ *   The project type. i.e. module, theme.
+ *
+ * @return array
+ *   Array of .info file data.
+ */
+function _locale_translation_prepare_project_list($data, $type) {
+  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;
+}
+
+/**
+ * Retrieve data for default server.
+ *
+ * @return array
+ *   Array of server parameters:
+ *   - "server_pattern": URL containing po file pattern.
+ */
+function locale_translation_default_translation_server() {
+  return array(
+    'pattern' => variable_get('locale_translation_default_server_pattern', LOCALE_TRANSLATION_DEFAULT_SERVER_PATTERN),
+  );
+}
+
+/**
+ * Build path to translation source, out of a server path replacement pattern.
+ *
+ * @param stdClass $project
+ *   Project object containing data to be inserted in the template.
+ * @param string $template
+ *   String containing place holders. Available placeholders:
+ *   - "%project": Project name.
+ *   - "%version": Poject version.
+ *   - "%core": Project core version.
+ *   - "%language": Language code.
+ *   - "%filename": Project file name.
+ *
+ * @return string
+ *   String with replaced place holders.
+ */
+function locale_translation_build_server_pattern($project, $template) {
+  $variables = array(
+    '%project' => $project->name,
+    '%version' => $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.install b/core/modules/locale/locale.install
index 334b401..ed77dda 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -32,6 +32,9 @@ function locale_uninstall() {
   variable_del('locale_cache_length');
   variable_del('locale_translation_plurals');
   variable_del('locale_translation_javascript');
+  variable_del('locale_translation_test_system_info_alter');
+  variable_del('locale_translation_check_disabled');
+  variable_del('locale_translation_default_server_pattern');
 
   // Remove all node type language variables. Node module might have been
   // enabled, but may be disabled, so use a wildcard delete.
@@ -159,6 +162,55 @@ function locale_schema() {
     'primary key' => array('uri', 'langcode'),
   );
 
+  $schema['locale_project'] = array(
+    'description' => 'Translation status information for projects and server data.',
+    '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_pattern' => array(
+        'description' => 'Pattern of path and name of the gettext file at the translation server.',
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      '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.';
+
   return $schema;
 }
 
@@ -623,6 +675,63 @@ function locale_update_8010() {
 }
 
 /**
+ * Add a cache table and locale_project table for the locale module.
+ */
+function locale_update_8011() {
+  // Add a 'locale' cache table.
+  $table = drupal_get_schema_unprocessed('system', 'cache');
+  $table['description'] = 'Cache table for the locale module to store various data.';
+  db_create_table('cache_locale', $table);
+
+  // Add locale_project table.
+  db_create_table('locale_project', array(
+    'description' => 'Translation status information for projects and server data.',
+    '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_pattern' => array(
+        'description' => 'Pattern of path and name of the gettext file at the translation server.',
+        'type' => 'varchar',
+        'length' => '255',
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'status' => array(
+        'description' => 'Status flag. TBD',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+    ),
+    'primary key' => array('name'),
+  ));
+}
+
+/**
  * @} 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-1.2.nl.po b/core/modules/locale/tests/modules/locale_test/locale_test-1.2.nl.po
new file mode 100644
index 0000000..e69de29
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..1ec7508
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test/locale_test.info
@@ -0,0 +1,10 @@
+name = Locale test
+description = Support module for locale module testing.
+package = Testing
+version = 1.2
+core = 8.x
+hidden = TRUE
+
+; Definitions for interface translations.
+interface translation project = locale_test
+interface translation server pattern = core/modules/locale/test/modules/locale_test/%project-%version.%language.po
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..6044a07
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test/locale_test.module
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Simulate a custom module with a local po file.
+ */
+
+/**
+ * Implements hook_system_info_alter().
+ *
+ * Make the test scripts to be believe this is not a hidden test module, but
+ * a regular custom module.
+ */
+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_translation_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..7eddf25
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test_disabled/locale_test_disabled.info
@@ -0,0 +1,10 @@
+name = Disabled locale test
+description = Disabled support module for locale module testing.
+package = Testing
+version = VERSION
+core = 8.x
+hidden = TRUE
+project = locale_test_disabled
+
+; Definitions for interface translation.
+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..a80d9da
--- /dev/null
+++ b/core/modules/locale/tests/modules/locale_test_disabled/locale_test_disabled.module
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Simulate a disabled contrib for Locale test scripts.
+ */
