 core/includes/language.inc                       |    3 +-
 core/includes/locale.inc                         |   51 +++++++++++++++++-----
 core/modules/locale/locale.admin.inc             |   40 ++++++-----------
 core/modules/locale/locale.install               |   50 +++++++++++++++------
 core/modules/locale/locale.module                |   50 +++++++++++++++++++++
 core/modules/locale/locale.test                  |   17 +++++---
 core/modules/simpletest/drupal_web_test_case.php |    2 +-
 core/modules/translation/translation.test        |    7 ++-
 8 files changed, 157 insertions(+), 63 deletions(-)

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 6222bda..14bee45 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,22 +431,52 @@ 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 (isset($prefixes[$options['language']->language]) && ($prefix = $prefixes[$options['language']->language])) {
+          $options['prefix'] = $prefix . '/';
         }
         break;
     }
   }
 }
 
+ /**
+ * Helper to read language prefixes, and use langcode if no prefix is set.
+ */
+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.
+ */
+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.
  */
@@ -509,10 +542,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 82397c8..b7a2bc8 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);
   }
 
@@ -705,20 +696,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] : '',
     );
   }
 
@@ -782,24 +774,18 @@ function locale_language_providers_url_form_validate($form, &$form_state) {
  */
 function locale_language_providers_url_form_submit($form, &$form_state) {
 
+  $languages = language_list();
+
   // 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();
+  // Drop values that aren't in the language list.
+  $prefixes = array_intersect_key($form_state['values']['prefix'], $languages);
+  $domains = array_intersect_key($form_state['values']['domain'], $languages);
 
-  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($prefixes);
+  locale_language_negotiation_url_domains_save($domains);
 
   drupal_set_message(t('Configuration saved.'));
 }
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index 77da39c..f54adc5 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -12,6 +12,16 @@ function locale_install() {
   // Add the default language to the database too.
   include_once DRUPAL_ROOT . '/core/includes/locale.inc';
   locale_language_save(language_default());
+
+  // Initialize the domain and prefix settings.
+  $prefixes = array(
+    language_default()->language => '',
+  );
+  locale_language_negotiation_url_prefixes_save($prefixes);
+  $domains = array(
+    language_default()->language => '',
+  );
+  locale_language_negotiation_url_domains_save($domains);
 }
 
 /**
@@ -110,20 +120,6 @@ function locale_schema() {
         'default' => '',
         'description' => 'Plural formula in PHP code to evaluate to get plural indexes.',
       ),
-      '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,
@@ -250,6 +246,32 @@ function locale_update_8000() {
 }
 
 /**
+ * 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_8001() {
+
+  // Collect all domains and prefixes.
+  $prefixes = array();
+  $domains = array();
+  foreach (language_list() as $langcode => $language) {
+    $prefixes[$langcode] = $language->prefix;
+    $domains[$langcode] = $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 18cf99d..076e891 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 7506242..cb8eef9 100644
--- a/core/modules/locale/locale.test
+++ b/core/modules/locale/locale.test
@@ -2270,7 +2270,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'));
   }
 
@@ -2288,11 +2287,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);
   }
@@ -2492,9 +2496,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..c49de40 100644
--- a/core/modules/simpletest/drupal_web_test_case.php
+++ b/core/modules/simpletest/drupal_web_test_case.php
@@ -1262,7 +1262,7 @@ 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', 'native' => 'English', 'direction' => 0, 'enabled' => 1, 'plurals' => 0, 'formula' => '', 'weight' => 0, 'javascript' => '');
 
     // 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.
