 core/includes/bootstrap.inc                      |    9 +--
 core/includes/language.inc                       |    3 +-
 core/includes/locale.inc                         |   55 +++++++++++++++++----
 core/modules/locale/locale.admin.inc             |   36 +++-----------
 core/modules/locale/locale.install               |   47 +++++++++++++------
 core/modules/locale/locale.module                |   50 ++++++++++++++++++++
 core/modules/locale/locale.test                  |   17 ++++--
 core/modules/simpletest/drupal_web_test_case.php |    8 +++-
 core/modules/translation/translation.test        |    7 ++-
 9 files changed, 162 insertions(+), 70 deletions(-)

diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 9b4667e..acc6be6 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2686,21 +2686,18 @@ function language_list($field = 'language') {
  *   A language object.
  */
 function language_default() {
-  return variable_get(
+  $default = variable_get(
     'language_default',
     (object) array(
       'language' => 'en',
       'name' => 'English',
       'direction' => 0,
       'enabled' => 1,
-      'plurals' => 0,
-      'formula' => '',
-      'domain' => '',
-      'prefix' => '',
       'weight' => 0,
-      'javascript' => ''
     )
   );
+  $default->default = TRUE;
+  return $default;
 }
 
 /**
diff --git a/core/includes/language.inc b/core/includes/language.inc
index 685a4d4..073c694 100644
--- a/core/includes/language.inc
+++ b/core/includes/language.inc
@@ -424,8 +424,9 @@ function language_url_split_prefix($path, $languages) {
   $prefix = array_shift($args);
 
   // Search prefix within enabled languages.
+  $prefixes = locale_language_negotiation_url_prefixes();
   foreach ($languages as $language) {
-    if (!empty($language->prefix) && $language->prefix == $prefix) {
+    if (isset($prefixes[$language->language]) && $prefixes[$language->language] == $prefix) {
       // Rebuild $path with the language removed.
       return array($language, implode('/', $args));
     }
diff --git a/core/includes/locale.inc b/core/includes/locale.inc
index e9cd427..eb91576 100644
--- a/core/includes/locale.inc
+++ b/core/includes/locale.inc
@@ -279,12 +279,13 @@ function locale_language_from_url($languages) {
       break;
 
     case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN:
+      $domains = locale_language_negotiation_url_domains();
       foreach ($languages as $language) {
         // Skip check if the language doesn't have a domain.
-        if ($language->domain) {
+        if (!empty($domains[$language->language])) {
           // Only compare the domains not the protocols or ports.
           // Remove protocol and add http:// so parse_url works
-          $host = 'http://' . str_replace(array('http://', 'https://'), '', $language->domain);
+          $host = 'http://' . str_replace(array('http://', 'https://'), '', $domains[$language->language]);
           $host = parse_url($host, PHP_URL_HOST);
           if ($_SERVER['HTTP_HOST'] == $host) {
             $language_url = $language->language;
@@ -337,7 +338,9 @@ function locale_language_url_fallback($language = NULL, $language_type = LANGUAG
   // If the default language is not configured to convey language information,
   // a missing URL language information indicates that URL language should be
   // the default one, otherwise we fall back to an already detected language.
-  if (($prefix && empty($default->prefix)) || (!$prefix && empty($default->domain))) {
+  $domains = locale_language_negotiation_url_domains();
+  $prefixes = locale_language_negotiation_url_prefixes();
+  if (($prefix && empty($prefixes[$default->language])) || (!$prefix && empty($domains[$default->language]))) {
     return $default->language;
   }
   else {
@@ -428,16 +431,18 @@ function locale_language_url_rewrite_url(&$path, &$options) {
   if (isset($options['language'])) {
     switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) {
       case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN:
-        if ($options['language']->domain) {
+        $domains = locale_language_negotiation_url_domains();
+        if (!empty($domains[$options['language']->language])) {
           // Ask for an absolute URL with our modified base_url.
           $options['absolute'] = TRUE;
-          $options['base_url'] = $options['language']->domain;
+          $options['base_url'] = $domains[$options['language']->language];
         }
         break;
 
       case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX:
-        if (!empty($options['language']->prefix)) {
-          $options['prefix'] = $options['language']->prefix . '/';
+        $prefixes = locale_language_negotiation_url_prefixes();
+        if (!empty($prefixes[$options['language']->language])) {
+          $options['prefix'] = $prefixes[$options['language']->language] . '/';
         }
         break;
     }
@@ -445,6 +450,38 @@ function locale_language_url_rewrite_url(&$path, &$options) {
 }
 
 /**
+ * Helper to read language prefixes, and use langcode if no prefix is set.
+ *
+ * @TODO: Review prefix storage once configuration management is in core.
+ */
+function locale_language_negotiation_url_prefixes() {
+  return variable_get('locale_language_negotiation_url_prefixes', array());
+}
+
+/**
+ * Helper to save language prefix settings.
+ */
+function locale_language_negotiation_url_prefixes_save(array $prefixes) {
+  variable_set('locale_language_negotiation_url_prefixes', $prefixes);
+}
+
+/**
+ * Helper to read language domains.
+ *
+ * @TODO: Review domain storage once configuration management is in core.
+ */
+function locale_language_negotiation_url_domains() {
+  return variable_get('locale_language_negotiation_url_domains', array());
+}
+
+/**
+ * Helper to save the language domain settings.
+ */
+function locale_language_negotiation_url_domains_save(array $domains) {
+  variable_set('locale_language_negotiation_url_domains', $domains);
+}
+
+/**
  * Rewrite URLs for the Session language provider.
  */
 function locale_language_url_rewrite_session(&$path, &$options) {
@@ -509,10 +546,6 @@ function locale_string_is_safe($string) {
  */
 function locale_language_save($language) {
   $language->is_new = !(bool) db_query_range('SELECT 1 FROM {languages} WHERE language = :language', 0, 1, array(':language' => $language->language))->fetchField();
-  // Default prefix on language code if not provided otherwise.
-  if (!isset($language->prefix)) {
-    $language->prefix = $language->language;
-  }
 
   // If name was not set, we add a predefined language.
   if (!isset($language->name)) {
diff --git a/core/modules/locale/locale.admin.inc b/core/modules/locale/locale.admin.inc
index 97d6b64..5732697 100644
--- a/core/modules/locale/locale.admin.inc
+++ b/core/modules/locale/locale.admin.inc
@@ -173,15 +173,6 @@ function locale_language_overview_form_submit($form, &$form_state) {
     }
     $language->enabled = (int) !empty($form_state['values']['languages'][$langcode]['enabled']);
 
-    // If language URL prefixes are enabled we must clear language domains and
-    // assign a valid prefix to each non-default language.
-    if ($url_prefixes) {
-      $language->domain = '';
-      if (empty($language->prefix) && !$language->default) {
-        $language->prefix = $langcode;
-      }
-    }
-
     locale_language_save($language);
   }
 
@@ -704,20 +695,21 @@ function locale_language_providers_url_form($form, &$form_state) {
   );
 
   $languages = language_list('enabled');
+  $prefixes = locale_language_negotiation_url_prefixes();
+  $domains = locale_language_negotiation_url_domains();
   foreach ($languages[1] as $langcode => $language) {
     $form['prefix'][$langcode] = array(
       '#type' => 'textfield',
       '#title' => t('%language (%langcode) path prefix', array('%language' => $language->name, '%langcode' => $language->language)),
       '#maxlength' => 64,
-      '#default_value' => $language->prefix,
-      '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=')
-
+      '#default_value' => isset($prefixes[$langcode]) ? $prefixes[$langcode] : '',
+      '#field_prefix' => url('', array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=')
     );
     $form['domain'][$langcode] = array(
       '#type' => 'textfield',
       '#title' => t('%language (%langcode) domain', array('%language' => $language->name, '%langcode' => $language->language)),
       '#maxlength' => 128,
-      '#default_value' => $language->domain,
+      '#default_value' => isset($domains[$langcode]) ? $domains[$langcode] : '',
     );
   }
 
@@ -784,21 +776,9 @@ function locale_language_providers_url_form_submit($form, &$form_state) {
   // Save selected format (prefix or domain).
   variable_set('locale_language_negotiation_url_part', $form_state['values']['locale_language_negotiation_url_part']);
 
-  $languages = language_list('enabled');
-  $default = language_default();
-
-  foreach ($languages[1] as $langcode => $language) {
-
-    // Add new prefix and domain settings to the language object.
-    foreach (array('prefix', 'domain') as $type) {
-      if (isset($form_state['values'][$type][$langcode])) {
-        $language->$type = $form_state['values'][$type][$langcode];
-      }
-    }
-
-    // Save the prefix and domain settings for each language.
-    locale_language_save($language);
-  }
+  // Save new domain and prefix values.
+  locale_language_negotiation_url_prefixes_save($form_state['values']['prefix']);
+  locale_language_negotiation_url_domains_save($form_state['values']['domain']);
 
   drupal_set_message(t('Configuration saved.'));
 }
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index 9ca0d6c..f82eb51 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -39,6 +39,8 @@ function locale_uninstall() {
   variable_del('language_count');
   variable_del('language_types');
   variable_del('locale_language_negotiation_url_part');
+  variable_del('locale_language_negotiation_url_prefixes');
+  variable_del('locale_language_negotiation_url_domains');
   variable_del('locale_language_negotiation_session_param');
   variable_del('language_content_type_default');
   variable_del('language_content_type_negotiation');
@@ -99,20 +101,6 @@ function locale_schema() {
         'default' => 0,
         'description' => 'Enabled flag (1 = Enabled, 0 = Disabled).',
       ),
-      'domain' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'Domain to use for this language.',
-      ),
-      'prefix' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'Path prefix to use for this language.',
-      ),
       'weight' => array(
         'type' => 'int',
         'not null' => TRUE,
@@ -258,6 +246,37 @@ function locale_update_8001() {
 }
 
 /**
+ * Remove prefix and domain columns from language table.
+ *
+ * Load all prefixes and domains from the languages table, save the values in
+ * two new variables. Then remove the prefix and domain columns from the
+ * languages table.
+ */
+function locale_update_8002() {
+
+  // Look up languages directly instead of using language_list().
+  $languages = db_select('languages', 'l')
+    ->fields('l')
+    ->execute();
+
+  // Collect all domains and prefixes.
+  $prefixes = array();
+  $domains = array();
+  foreach ($languages as $language) {
+    $prefixes[$language->language] = $language->prefix;
+    $domains[$language->language] = $language->domain;
+  }
+
+  // Save the prefix and domain values in a variable.
+  variable_set('locale_language_negotiation_url_prefixes', $prefixes);
+  variable_set('locale_language_negotiation_url_domains', $domains);
+
+  // Remove the prefix and domain columns.
+  db_drop_field('languages', 'prefix');
+  db_drop_field('languages', 'domain');
+}
+
+/**
  * @} 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 103c284..a207bd2 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -607,6 +607,56 @@ function locale_modules_disabled($modules) {
   locale_modules_enabled($modules);
 }
 
+/**
+ * Implements hook_locale_language_insert().
+ */
+function locale_locale_language_insert($language) {
+  // Add new language to the list of language prefixes.
+  $prefixes = locale_language_negotiation_url_prefixes();
+  $prefixes[$language->language] = (empty($language->default) ? $language->language : '');
+  locale_language_negotiation_url_prefixes_save($prefixes);
+
+  // Add language to the list of language domains.
+  $domains = locale_language_negotiation_url_domains();
+  $domains[$language->language] = '';
+  locale_language_negotiation_url_domains_save($domains);
+}
+
+/**
+ * Implements hook_locale_language_update().
+ */
+function locale_locale_language_update($language) {
+
+  // If the language is the default, then ensure that no other languages have
+  // blank prefix codes.
+  if (!empty($language->default)) {
+    $prefixes = locale_language_negotiation_url_prefixes();
+    foreach ($prefixes as $langcode => $prefix) {
+      if ($prefix == '' && $langcode != $language->language) {
+        $prefixes[$langcode] = $langcode;
+      }
+    }
+    locale_language_negotiation_url_prefixes_save($prefixes);
+  }
+
+}
+
+/**
+ * Implements hook_locale_language_delete().
+ */
+function locale_locale_language_delete($language) {
+  // Remove language from language prefix list.
+  $prefixes = locale_language_negotiation_url_prefixes();
+  unset($prefixes[$language->language]);
+  locale_language_negotiation_url_prefixes_save($prefixes);
+
+  // Remove language from language domain list.
+  $domains = locale_language_negotiation_url_domains();
+  unset($domains[$language->language]);
+  locale_language_negotiation_url_domains_save($domains);
+}
+
+
 // ---------------------------------------------------------------------------------
 // Locale core functionality
 
diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test
index 3474d78..e322af1 100644
--- a/core/modules/locale/locale.test
+++ b/core/modules/locale/locale.test
@@ -2267,7 +2267,6 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase {
     // Check URL rewriting with a non-installed language.
     $non_existing = language_default();
     $non_existing->language = $this->randomName();
-    $non_existing->prefix = $this->randomName();
     $this->checkUrl($non_existing, t('Path language is ignored if language is not installed.'), t('URL language negotiation does not work with non-installed languages'));
   }
 
@@ -2285,11 +2284,16 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase {
     $segments = explode('/', $rewritten_path, 2);
     $prefix = $segments[0];
     $path = isset($segments[1]) ? $segments[1] : $prefix;
-    // If the rewritten URL has not a language prefix we pick the right one from
-    // the language object so we can always check the prefixed URL.
-    if ($this->assertNotEqual($language->prefix, $prefix, $message1)) {
-      $prefix = $language->prefix;
+
+
+    // If the rewritten URL has not a language prefix we pick a random prefix so
+    // we can always check the prefixed URL.
+    $prefixes = locale_language_negotiation_url_prefixes();
+    $stored_prefix = isset($prefixes[$language->language]) ? $prefixes[$language->language] : $this->randomName();
+    if ($this->assertNotEqual($stored_prefix, $prefix, $message1)) {
+      $prefix = $stored_prefix;
     }
+
     $this->drupalGet("$prefix/$path");
     $this->assertResponse(404, $message2);
   }
@@ -2489,9 +2493,10 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase {
       $this->drupalPost("node/add/article", $edit, t('Save'));
       $node = $this->drupalGetNodeByTitle($title);
 
+      $prefixes = locale_language_negotiation_url_prefixes();
       foreach (language_list() as $langcode => $language) {
         // Post a comment with content language $langcode.
-        $prefix = empty($language->prefix) ? '' : $language->prefix . '/';
+        $prefix = empty($prefixes[$langcode]) ? '' : $prefixes[$langcode] . '/';
         $edit = array("comment_body[$langcode_none][0][value]" => $this->randomName());
         $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save'));
 
diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php
index 9d993d4..8da19f1 100644
--- a/core/modules/simpletest/drupal_web_test_case.php
+++ b/core/modules/simpletest/drupal_web_test_case.php
@@ -1262,7 +1262,13 @@ class DrupalWebTestCase extends DrupalTestCase {
     // Set to English to prevent exceptions from utf8_truncate() from t()
     // during install if the current language is not 'en'.
     // The following array/object conversion is copied from language_default().
-    $language = (object) array('language' => 'en', 'name' => 'English', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => '', 'weight' => 0, 'javascript' => '');
+    $language = (object) array(
+      'language' => 'en',
+      'name' => 'English',
+      'direction' => 0,
+      'enabled' => 1,
+      'weight' => 0,
+    );
 
     // Save and clean shutdown callbacks array because it static cached and
     // will be changed by the test run. If we don't, then it will contain
diff --git a/core/modules/translation/translation.test b/core/modules/translation/translation.test
index c8a1ccd..0e801c1 100644
--- a/core/modules/translation/translation.test
+++ b/core/modules/translation/translation.test
@@ -77,8 +77,9 @@ class TranslationTestCase extends DrupalWebTestCase {
 
     // Check that the "add translation" link uses a localized path.
     $languages = language_list();
+    $prefixes = locale_language_negotiation_url_prefixes();
     $this->drupalGet('node/' . $node->nid . '/translate');
-    $this->assertLinkByHref($languages['es']->prefix . '/node/add/' . str_replace('_', '-', $node->type), 0, t('The "add translation" link for %language points to the localized path of the target language.', array('%language' => $languages['es']->name)));
+    $this->assertLinkByHref($prefixes['es'] . '/node/add/' . str_replace('_', '-', $node->type), 0, t('The "add translation" link for %language points to the localized path of the target language.', array('%language' => $languages['es']->name)));
 
     // Submit translation in Spanish.
     $node_translation_title = $this->randomName();
@@ -88,8 +89,8 @@ class TranslationTestCase extends DrupalWebTestCase {
     // Check that the "edit translation" and "view node" links use localized
     // paths.
     $this->drupalGet('node/' . $node->nid . '/translate');
-    $this->assertLinkByHref($languages['es']->prefix . '/node/' . $node_translation->nid . '/edit', 0, t('The "edit" link for the translation in %language points to the localized path of the translation language.', array('%language' => $languages['es']->name)));
-    $this->assertLinkByHref($languages['es']->prefix . '/node/' . $node_translation->nid, 0, t('The "view" link for the translation in %language points to the localized path of the translation language.', array('%language' => $languages['es']->name)));
+    $this->assertLinkByHref($prefixes['es'] . '/node/' . $node_translation->nid . '/edit', 0, t('The "edit" link for the translation in %language points to the localized path of the translation language.', array('%language' => $languages['es']->name)));
+    $this->assertLinkByHref($prefixes['es'] . '/node/' . $node_translation->nid, 0, t('The "view" link for the translation in %language points to the localized path of the translation language.', array('%language' => $languages['es']->name)));
 
     // Attempt to submit a duplicate translation by visiting the node/add page
     // with identical query string.
