diff --git a/core/includes/file.inc b/core/includes/file.inc
index 6ac7b922269156420c13f86589a7565265188aa3..9e85ba9ee677746e5db4d957ffbacd9e58da86ab 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -569,6 +569,13 @@ function file_load($fid) {
 function file_save(stdClass $file) {
   $file->timestamp = REQUEST_TIME;
   $file->filesize = filesize($file->uri);
+  if (!isset($file->langcode)) {
+    // Default the file's language code to none, because files are language
+    // neutral more often than language dependent. Until we have better flexible
+    // settings.
+    // @todo See http://drupal.org/node/258785 and followups.
+    $file->langcode = LANGUAGE_NONE;
+  }
 
   // Load the stored entity, if any.
   if (!empty($file->fid) && !isset($file->original)) {
diff --git a/core/includes/locale.inc b/core/includes/locale.inc
index 8a381edbfe417db3235f2bf5266b2f59bf86dd49..43bac8d3093e88aec4b5cadb03e84ea706a6bb8a 100644
--- a/core/includes/locale.inc
+++ b/core/includes/locale.inc
@@ -215,7 +215,7 @@ function locale_language_from_user($languages) {
   global $user;
 
   if ($user->uid) {
-    return $user->language;
+    return $user->preferred_langcode;
   }
 
   // No language preference from the user.
diff --git a/core/modules/entity/tests/entity_crud_hook_test.test b/core/modules/entity/tests/entity_crud_hook_test.test
index 7c8e42ee8d344759b060dcef11352fb9b41c4175..97f44622dc1e8dee8be1423c1a7196ea15b85229 100644
--- a/core/modules/entity/tests/entity_crud_hook_test.test
+++ b/core/modules/entity/tests/entity_crud_hook_test.test
@@ -248,6 +248,7 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
     $vocabulary = (object) array(
       'name' => 'Test vocabulary',
       'machine_name' => 'test',
+      'langcode' => LANGUAGE_NONE,
       'description' => NULL,
       'module' => 'entity_crud_hook_test',
     );
@@ -256,6 +257,7 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
     $term = (object) array(
       'vid' => $vocabulary->vid,
       'name' => 'Test term',
+      'langcode' => LANGUAGE_NONE,
       'description' => NULL,
       'format' => 1,
     );
@@ -306,6 +308,7 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
     $vocabulary = (object) array(
       'name' => 'Test vocabulary',
       'machine_name' => 'test',
+      'langcode' => LANGUAGE_NONE,
       'description' => NULL,
       'module' => 'entity_crud_hook_test',
     );
diff --git a/core/modules/field_ui/field_ui.test b/core/modules/field_ui/field_ui.test
index c2e3a9820cc3aa49dc6166c6c4fd88c289d8768f..4f7369232ea1457cbfaa65e7c0a76cf64ff13c50 100644
--- a/core/modules/field_ui/field_ui.test
+++ b/core/modules/field_ui/field_ui.test
@@ -163,6 +163,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
     $vocabulary = (object) array(
       'name' => 'Tags',
       'machine_name' => 'tags',
+      'langcode' => LANGUAGE_NONE,
     );
     taxonomy_vocabulary_save($vocabulary);
 
diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install
index 2eebd7fba3ee2087bc06f8e152c39910222d1068..946f9c4b0511a5247b786da47df1397a95b85011 100644
--- a/core/modules/forum/forum.install
+++ b/core/modules/forum/forum.install
@@ -33,6 +33,7 @@ function forum_enable() {
     $edit = array(
       'name' => t('Forums'),
       'machine_name' => 'forums',
+      'langcode' => language_default()->langcode,
       'description' => t('Forum navigation vocabulary'),
       'hierarchy' => 1,
       'module' => 'forum',
@@ -62,6 +63,7 @@ function forum_enable() {
     // Create a default forum so forum posts can be created.
     $edit = array(
       'name' => t('General discussion'),
+      'langcode' => language_default()->langcode,
       'description' => '',
       'parent' => array(0),
       'vid' => $vocabulary->vid,
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 7cadf53ff2b6559736a31b4e9cc9a970082d6e1e..5c2cdbdf7eb8347b25e42db6198f9c7c3ba56383 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -236,16 +236,40 @@ function locale_language_selector_form($user) {
     '#title' => t('Language settings'),
     '#weight' => 1,
   );
-  $form['locale']['language'] = array(
+  $form['locale']['preferred_langcode'] = array(
     '#type' => (count($names) <= 5 ? 'radios' : 'select'),
     '#title' => t('Language'),
     '#default_value' => $user_preferred_language->langcode,
     '#options' => $names,
-    '#description' => $mode ? t("This account's default language for e-mails, and preferred language for site presentation.") : t("This account's default language for e-mails."),
+    '#description' => $mode ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."),
+  );
+  // User entities contain both a langcode property (for identifying the
+  // language of the entity data) and a preferred_langcode property (see above).
+  // Rather than provide a UI forcing the user to choose both separately,
+  // assume that the user profile data is in the user's preferred language. This
+  // element provides that synchronization. For use-cases where this
+  // synchronization is not desired, a module can alter or remove this element.
+  $form['locale']['langcode'] = array(
+    '#type' => 'value',
+    '#value_callback' => '_locale_language_selector_langcode_value',
+    // For the synchronization to work, this element must have a larger weight
+    // than the preferred_langcode element. Set a large weight here in case
+    // a module alters the weight of the other element.
+    '#weight' => 100,
   );
   return $form;
 }
 
+/**
+ * Sets the value of the user register and profile forms' langcode element.
+ *
+ * @see locale_language_selector_form()
+ */
+function _locale_language_selector_langcode_value($element, $input, &$form_state) {
+  $form_state['complete_form']['locale']['preferred_langcode']['#description'] .= ' ' . t("This is also assumed to be the primary language of this account's profile information.");
+  return $form_state['values']['preferred_langcode'];
+}
+
 /**
  * Implements hook_form_alter().
  *
diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test
index 208c5e6882d028eb7d46338e312f538146291283..7d1e55cd6e0a5358e3c60eed7b91d1bcaaf2d7cc 100644
--- a/core/modules/locale/locale.test
+++ b/core/modules/locale/locale.test
@@ -1644,13 +1644,13 @@ class LocaleUserLanguageFunctionalTest extends DrupalWebTestCase {
     $this->assertNoText($name_disabled, t('Disabled language not present on form.'));
     // Switch to our custom language.
     $edit = array(
-      'language' => $langcode,
+      'preferred_langcode' => $langcode,
     );
     $this->drupalPost($path, $edit, t('Save'));
     // Ensure form was submitted successfully.
     $this->assertText(t('The changes have been saved.'), t('Changes were saved.'));
     // Check if language was changed.
-    $elements = $this->xpath('//input[@id=:id]', array(':id' => 'edit-language-' . $langcode));
+    $elements = $this->xpath('//input[@id=:id]', array(':id' => 'edit-preferred-langcode-' . $langcode));
     $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Default language successfully updated.'));
 
     $this->drupalLogout();
@@ -1702,7 +1702,7 @@ class LocaleUserCreationTest extends DrupalWebTestCase {
     // Check if the language selector is available on admin/people/create and
     // set to the currently active language.
     $this->drupalGet($langcode . '/admin/people/create');
-    $this->assertFieldChecked("edit-language-$langcode", t('Global language set in the language selector.'));
+    $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Global language set in the language selector.'));
 
     // Create a user with the admin/people/create form and check if the correct
     // language is set.
@@ -1717,7 +1717,8 @@ class LocaleUserCreationTest extends DrupalWebTestCase {
     $this->drupalPost($langcode . '/admin/people/create', $edit, t('Create new account'));
 
     $user = user_load_by_name($username);
-    $this->assertEqual($user->language, $langcode, t('New user has correct language set.'));
+    $this->assertEqual($user->preferred_langcode, $langcode, t('New user has correct preferred language set.'));
+    $this->assertEqual($user->langcode, $langcode, t('New user has correct profile language set.'));
 
     // Register a new user and check if the language selector is hidden.
     $this->drupalLogout();
@@ -1734,7 +1735,8 @@ class LocaleUserCreationTest extends DrupalWebTestCase {
     $this->drupalPost($langcode . '/user/register', $edit, t('Create new account'));
 
     $user = user_load_by_name($username);
-    $this->assertEqual($user->language, $langcode, t('New user has correct language set.'));
+    $this->assertEqual($user->preferred_langcode, $langcode, t('New user has correct preferred language set.'));
+    $this->assertEqual($user->langcode, $langcode, t('New user has correct profile language set.'));
 
     // Test if the admin can use the language selector and if the
     // correct language is was saved.
@@ -1742,7 +1744,7 @@ class LocaleUserCreationTest extends DrupalWebTestCase {
 
     $this->drupalLogin($admin_user);
     $this->drupalGet($user_edit);
-    $this->assertFieldChecked("edit-language-$langcode", t('Language selector is accessible and correct language is selected.'));
+    $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Language selector is accessible and correct language is selected.'));
 
     // Set pass_raw so we can login the new user.
     $user->pass_raw = $this->randomName(10);
@@ -1755,7 +1757,7 @@ class LocaleUserCreationTest extends DrupalWebTestCase {
 
     $this->drupalLogin($user);
     $this->drupalGet($user_edit);
-    $this->assertFieldChecked("edit-language-$langcode", t('Language selector is accessible and correct language is selected.'));
+    $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Language selector is accessible and correct language is selected.'));
   }
 }
 
@@ -2682,7 +2684,7 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase {
 
     // Change user language preference, this way interface language is always
     // French no matter what path prefix the URLs have.
-    $edit = array('language' => 'fr');
+    $edit = array('preferred_langcode' => 'fr');
     $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save'));
   }
 
diff --git a/core/modules/openid/openid.module b/core/modules/openid/openid.module
index d29d40578832ded4513aff161f2d3c351fc1bae0..a8e99a06e5b98d4ddeddecb43f4720bc968f9d70 100644
--- a/core/modules/openid/openid.module
+++ b/core/modules/openid/openid.module
@@ -268,8 +268,8 @@ function openid_form_user_register_form_alter(&$form, &$form_state) {
       // specific) strings.
       foreach (array_reverse($candidate_languages) as $candidate_language) {
         if (isset($enabled_languages[$candidate_language])) {
-          $form['locale']['language']['#type'] = 'hidden';
-          $form['locale']['language']['#value'] = $candidate_language;
+          $form['locale']['preferred_langcode']['#type'] = 'hidden';
+          $form['locale']['preferred_langcode']['#value'] = $candidate_language;
         }
       }
     }
diff --git a/core/modules/openid/openid.test b/core/modules/openid/openid.test
index 7f87b4b54514b3d6ca677c5dae59d61fae4f1a38..9d2b3352a6deaa51cd7f2e003301dc28d62d88b3 100644
--- a/core/modules/openid/openid.test
+++ b/core/modules/openid/openid.test
@@ -453,7 +453,7 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase {
     $this->assertTrue($user, t('User was registered with right username.'));
     $this->assertEqual($user->mail, 'john@example.com', t('User was registered with right email address.'));
     $this->assertEqual($user->timezone, 'Europe/London', t('User was registered with right timezone.'));
-    $this->assertEqual($user->language, 'en', t('User was registered with right language.'));
+    $this->assertEqual($user->preferred_langcode, 'en', t('User was registered with right language.'));
     $this->assertFalse($user->data, t('No additional user info was saved.'));
 
     $this->submitLoginForm($identity);
@@ -495,7 +495,7 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase {
     $this->assertTrue($user, t('User was registered with right username.'));
     $this->assertEqual($user->mail, 'john@example.com', t('User was registered with right email address.'));
     $this->assertEqual($user->timezone, 'Europe/London', t('User was registered with right timezone.'));
-    $this->assertEqual($user->language, 'en', t('User was registered with right language.'));
+    $this->assertEqual($user->preferred_langcode, 'en', t('User was registered with right language.'));
     $this->assertFalse($user->data, t('No additional user info was saved.'));
 
     $this->drupalLogout();
@@ -540,7 +540,7 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase {
 
     $user = user_load_by_name('john');
     $this->assertTrue($user, t('User was registered with right username.'));
-    $this->assertFalse($user->language, t('No user language was saved.'));
+    $this->assertFalse($user->preferred_langcode, t('No user language was saved.'));
     $this->assertFalse($user->data, t('No additional user info was saved.'));
 
     // Follow the one-time login that was sent in the welcome e-mail.
@@ -580,7 +580,7 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase {
 
     $user = user_load_by_name('john');
     $this->assertTrue($user, t('User was registered with right username.'));
-    $this->assertFalse($user->language, t('No user language was saved.'));
+    $this->assertFalse($user->preferred_langcode, t('No user language was saved.'));
     $this->assertFalse($user->data, t('No additional user info was saved.'));
 
     // Follow the one-time login that was sent in the welcome e-mail.
@@ -625,7 +625,7 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase {
     $this->assertTrue($user, t('User was registered with right username.'));
     $this->assertEqual($user->mail, 'john@example.com', t('User was registered with right email address.'));
     $this->assertEqual($user->timezone, 'Europe/London', t('User was registered with right timezone.'));
-    $this->assertEqual($user->language, 'en', t('User was registered with right language.'));
+    $this->assertEqual($user->preferred_langcode, 'en', t('User was registered with right language.'));
   }
 }
 
diff --git a/core/modules/path/path.test b/core/modules/path/path.test
index 7b6b2f2e9e55c55e70cab01af2d5ff3bd1b953db..3679553ad8f87d8128870554cc95755d87082e89 100644
--- a/core/modules/path/path.test
+++ b/core/modules/path/path.test
@@ -334,7 +334,7 @@ class PathLanguageTestCase extends DrupalWebTestCase {
     $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
 
     // Change user language preference.
-    $edit = array('language' => 'fr');
+    $edit = array('preferred_langcode' => 'fr');
     $this->drupalPost("user/{$this->web_user->uid}/edit", $edit, t('Save'));
 
     // Check that the English alias works. In this situation French is the
diff --git a/core/modules/simpletest/tests/common.test b/core/modules/simpletest/tests/common.test
index eeb5e497f6d2305756f35a89f0b33ac68b4ef44d..353ab1c7028ede2101cc4244b923919aaeb54181 100644
--- a/core/modules/simpletest/tests/common.test
+++ b/core/modules/simpletest/tests/common.test
@@ -2400,7 +2400,7 @@ class CommonFormatDateTestCase extends DrupalWebTestCase {
     // Create a test user to carry out the tests.
     $test_user = $this->drupalCreateUser();
     $this->drupalLogin($test_user);
-    $edit = array('language' => self::LANGCODE, 'mail' => $test_user->mail, 'timezone' => 'America/Los_Angeles');
+    $edit = array('preferred_langcode' => self::LANGCODE, 'mail' => $test_user->mail, 'timezone' => 'America/Los_Angeles');
     $this->drupalPost('user/' . $test_user->uid . '/edit', $edit, t('Save'));
 
     // Disable session saving as we are about to modify the global $user.
@@ -2409,7 +2409,7 @@ class CommonFormatDateTestCase extends DrupalWebTestCase {
     $real_user = $user;
     $user = user_load($test_user->uid, TRUE);
     $real_language = $language_interface->langcode;
-    $language_interface->langcode = $user->language;
+    $language_interface->langcode = $user->preferred_langcode;
     // Simulate a Drupal bootstrap with the logged-in user.
     date_default_timezone_set(drupal_get_user_timezone());
 
diff --git a/core/modules/simpletest/tests/file.test b/core/modules/simpletest/tests/file.test
index a369a44ab6ef9367f44b0463e6a2d24e624f3fea..a186360f8ce0fafbd6e46c08039498a97cc0a3db 100644
--- a/core/modules/simpletest/tests/file.test
+++ b/core/modules/simpletest/tests/file.test
@@ -2011,11 +2011,12 @@ class FileSaveTest extends FileHookTestCase {
     $this->assertEqual($loaded_file->status, $file->status, t("Status was saved correctly."));
     $this->assertEqual($saved_file->filesize, filesize($file->uri), t("File size was set correctly."), 'File');
     $this->assertTrue($saved_file->timestamp > 1, t("File size was set correctly."), 'File');
-
+    $this->assertEqual($loaded_file->langcode, LANGUAGE_NONE, t("Langcode was defaulted correctly."));
 
     // Resave the file, updating the existing record.
     file_test_reset();
     $saved_file->status = 7;
+    $saved_file->langcode = 'en';
     $resaved_file = file_save($saved_file);
 
     // Check that the correct hooks were called.
@@ -2026,6 +2027,7 @@ class FileSaveTest extends FileHookTestCase {
     $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ);
     $this->assertNotNull($loaded_file, t("Record still exists in the database."), 'File');
     $this->assertEqual($loaded_file->status, $saved_file->status, t("Status was saved correctly."));
+    $this->assertEqual($loaded_file->langcode, 'en', t("Langcode was saved correctly."));
   }
 }
 
diff --git a/core/modules/simpletest/tests/upgrade/drupal-7.language.database.php b/core/modules/simpletest/tests/upgrade/drupal-7.language.database.php
index c231f84e23ac50258e665bd9e5c305a3aba711e4..c1b9cc7801516b962538e45847636da973ca839a 100644
--- a/core/modules/simpletest/tests/upgrade/drupal-7.language.database.php
+++ b/core/modules/simpletest/tests/upgrade/drupal-7.language.database.php
@@ -878,3 +878,26 @@
   'comment_body_format' => 'filtered_html',
 ))
 ->execute();
+
+// Add a managed file.
+db_insert('file_managed')->fields(array(
+  'fid',
+  'uid',
+  'filename',
+  'uri',
+  'filemime',
+  'filesize',
+  'status',
+  'timestamp'
+))
+->values(array(
+  'fid' => '1',
+  'uid' => '1',
+  'filename' => 'foo.txt',
+  'uri' => 'public://foo.txt',
+  'filemime' => 'text/plain',
+  'filesize' => 0,
+  'status' => 1,
+  'timestamp' => '1314997642',
+))
+->execute();
diff --git a/core/modules/simpletest/tests/upgrade/upgrade.language.test b/core/modules/simpletest/tests/upgrade/upgrade.language.test
index b132e1077a1bd1109fa41f4f249229c0953427e6..0dbbf2e99c6f1aaa830298b4a95852a45b23fc96 100644
--- a/core/modules/simpletest/tests/upgrade/upgrade.language.test
+++ b/core/modules/simpletest/tests/upgrade/upgrade.language.test
@@ -32,6 +32,7 @@ class LanguageUpgradePathTestCase extends UpgradePathTestCase {
    * Tests a successful upgrade.
    */
   public function testLanguageUpgrade() {
+    db_update('users')->fields(array('language' => 'ca'))->condition('uid', '1')->execute();
     $this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.'));
 
     // Ensure Catalan was properly upgraded to be the new default language.
@@ -77,5 +78,23 @@ class LanguageUpgradePathTestCase extends UpgradePathTestCase {
     $this->assertFieldByName('langcode');
     $this->drupalGet('node/add/page');
     $this->assertNoFieldByName('langcode');
+
+    // Check that the user language value was retained in both langcode and
+    // preferred_langcode.
+    $user = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject();
+    $this->assertEqual($user->langcode, 'ca');
+    $this->assertEqual($user->preferred_langcode, 'ca');
+
+    // A langcode property was added to vocabularies and terms. Check that
+    // existing vocabularies and terms got assigned the site default language.
+    $vocabulary = db_query('SELECT * FROM {taxonomy_vocabulary} WHERE vid = :vid', array(':vid' => 1))->fetchObject();
+    $this->assertEqual($vocabulary->langcode, 'ca');
+    $term = db_query('SELECT * FROM {taxonomy_term_data} WHERE tid = :tid', array(':tid' => 1))->fetchObject();
+    $this->assertEqual($term->langcode, 'ca');
+
+    // A langcode property was added to files. Check that existing files got
+    // assigned LANGUAGE_NONE.
+    $file = db_query('SELECT * FROM {file_managed} WHERE fid = :fid', array(':fid' => 1))->fetchObject();
+    $this->assertEqual($file->langcode, LANGUAGE_NONE);
   }
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 03116583761edc6ef55674c6b36da1e591fe3a8d..8ab35e102b2dbc7d2d0b162bd136c8547b430d35 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -838,6 +838,13 @@ function system_schema() {
         'not null' => TRUE,
         'default' => '',
       ),
+      'langcode' => array(
+        'description' => 'The {language}.langcode of this file.',
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
       'filemime' => array(
         'description' => "The file's MIME type.",
         'type' => 'varchar',
@@ -1719,6 +1726,40 @@ function system_update_8003() {
   ));
 }
 
+/**
+ * Adds {file_managed}.langcode field.
+ *
+ * @see http://drupal.org/node/1454538
+ */
+function system_update_8003() {
+  $langcode_field = array(
+    'description' => 'The {language}.langcode of this file.',
+    'type' => 'varchar',
+    'length' => 12,
+    'not null' => TRUE,
+    'default' => '',
+  );
+
+  // If a Drupal 7 contrib module already added a langcode field to support
+  // internationalization, keep it, but standardize the specification.
+  // Otherwise, add the field.
+  if (db_field_exists('file_managed', 'langcode')) {
+    // According to the documentation of db_change_field(), indeces using the
+    // field should be dropped first; if the contrib module created any indeces,
+    // it is its responsibility to drop them in an update function that runs
+    // before this one, which it can enforce via hook_update_dependencies().
+    db_change_field('file_managed', 'langcode', 'langcode', $langcode_field);
+  }
+  else {
+    // Files can be language-specific (e.g., a scanned document) or not (e.g.,
+    // a photograph). For a site being updated, Drupal does not have a way to
+    // determine which existing files are language-specific and in what
+    // language. Our best guess is to set all of them to LANGUAGE_NONE.
+    $langcode_field['initial'] = LANGUAGE_NONE;
+    db_add_field('file_managed', 'langcode', $langcode_field);
+  }
+}
+
 /**
  * @} End of "defgroup updates-7.x-to-8.x"
  * The next series of updates should start at 9000.
diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc
index bf8a52861e33285e72f7c7f4c7cd072f5db4627d..beb87f9d5a8153617e762178b2f4bb94663ec9c2 100644
--- a/core/modules/taxonomy/taxonomy.admin.inc
+++ b/core/modules/taxonomy/taxonomy.admin.inc
@@ -117,6 +117,10 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
       'description' => '',
       'hierarchy' => TAXONOMY_HIERARCHY_DISABLED,
       'weight' => 0,
+      // Default the new vocabulary to the site's default language. This is
+      // the most likely default value until we have better flexible settings.
+      // @todo See http://drupal.org/node/258785 and followups.
+      'langcode' => language_default()->langcode,
     );
     foreach ($defaults as $key => $value) {
       if (!isset($vocabulary->$key)) {
@@ -659,6 +663,10 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary =
       'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL,
       'tid' => NULL,
       'weight' => 0,
+      // Default the new term to the site's default language. This is the most
+      // likely default value until we have better flexible settings.
+      // @todo See http://drupal.org/node/258785 and followups.
+      'langcode' => language_default()->langcode,
     );
     foreach ($defaults as $key => $value) {
       if (!isset($term->$key)) {
diff --git a/core/modules/taxonomy/taxonomy.install b/core/modules/taxonomy/taxonomy.install
index 3e07259f4118e2630b30a5a7ffaf8e3bc5e29a67..14b69568db648ef9d880957a2f3bd02c67613f39 100644
--- a/core/modules/taxonomy/taxonomy.install
+++ b/core/modules/taxonomy/taxonomy.install
@@ -39,6 +39,13 @@ function taxonomy_schema() {
         'default' => 0,
         'description' => 'The {taxonomy_vocabulary}.vid of the vocabulary to which the term is assigned.',
       ),
+      'langcode' => array(
+        'description' => 'The {language}.langcode of this term.',
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
       'name' => array(
         'type' => 'varchar',
         'length' => 255,
@@ -120,6 +127,13 @@ function taxonomy_schema() {
         'not null' => TRUE,
         'description' => 'Primary Key: Unique vocabulary ID.',
       ),
+      'langcode' => array(
+        'description' => 'The {language}.langcode of this vocabulary.',
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
       'name' => array(
         'type' => 'varchar',
         'length' => 255,
@@ -245,4 +259,44 @@ function taxonomy_field_schema($field) {
  */
 function taxonomy_update_8000() {
   db_drop_field('taxonomy_vocabulary', 'module');
-}
\ No newline at end of file
+}
+
+/**
+ * Adds langcode field to {taxonomy_term_data} and {taxonomy_vocabulary}.
+ *
+ * @see http://drupal.org/node/1454538
+ */
+function taxonomy_update_8001() {
+  $descriptions = array(
+    'taxonomy_term_data' => 'The {language}.langcode of this term.',
+    'taxonomy_vocabulary' => 'The {language}.langcode of this vocabulary.',
+  );
+  foreach ($descriptions as $table => $description) {
+    $langcode_field = array(
+      'description' => $description,
+      'type' => 'varchar',
+      'length' => 12,
+      'not null' => TRUE,
+      'default' => '',
+    );
+
+    // If a Drupal 7 contrib module already added a langcode field to support
+    // internationalization, keep it, but standardize the specification.
+    // Otherwise, add the field.
+    if (db_field_exists($table, 'langcode')) {
+      // According to the documentation of db_change_field(), indeces using the
+      // field should be dropped first; if the contrib module created any
+      // indeces, it is its responsibility to drop them in an update function
+      // that runs before this one, which it can enforce via
+      // hook_update_dependencies().
+      db_change_field($table, 'langcode', 'langcode', $langcode_field);
+    }
+    else {
+      // When updating from a site that did not already have taxonomy
+      // internationalization, initialize all existing vocabularies and terms as
+      // being in the site's default language.
+      $langcode_field['initial'] = language_default()->langcode;
+      db_add_field($table, 'langcode', $langcode_field);
+    }
+  }
+}
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index eac6bed3fb250e621a5870945319aca25881aabb..23a0a6bc5d37c1a6aed5ab9961d2445d79ba40fe 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -434,6 +434,7 @@ function taxonomy_admin_vocabulary_title_callback($vocabulary) {
  *   - vid: The ID of the vocabulary.
  *   - name: The human-readable name of the vocabulary.
  *   - machine_name: The machine name of the vocabulary.
+ *   - langcode: The language code of the vocabulary.
  *   - description: (optional) The vocabulary's description.
  *   - hierarchy: The hierarchy level of the vocabulary.
  *   - module: (optional) The module altering the vocabulary.
@@ -638,6 +639,7 @@ function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
  *   The taxonomy term object with the following properties:
  *   - vid: The ID of the vocabulary the term is assigned to.
  *   - name: The name of the term.
+ *   - langcode: The language code of the term.
  *   - tid: (optional) The unique ID for the term being saved. If $term->tid is
  *     empty or omitted, a new term will be inserted.
  *   - description: (optional) The term's description.
@@ -1829,6 +1831,7 @@ function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langc
     if ($item['tid'] == 'autocreate') {
       $term = (object) $item;
       unset($term->tid);
+      $term->langcode = $langcode;
       taxonomy_term_save($term);
       $items[$delta]['tid'] = $term->tid;
     }
diff --git a/core/modules/taxonomy/taxonomy.test b/core/modules/taxonomy/taxonomy.test
index b262b86ed18223b3c92216211372d2704b2e6e38..845855d14ac6266befefe9d5aa6d278543b2d082 100644
--- a/core/modules/taxonomy/taxonomy.test
+++ b/core/modules/taxonomy/taxonomy.test
@@ -19,6 +19,7 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
     $vocabulary->name = $this->randomName();
     $vocabulary->description = $this->randomName();
     $vocabulary->machine_name = drupal_strtolower($this->randomName());
+    $vocabulary->langcode = LANGUAGE_NONE;
     $vocabulary->help = '';
     $vocabulary->nodes = array('article' => 'article');
     $vocabulary->weight = mt_rand(0, 10);
@@ -36,6 +37,7 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
     // Use the first available text format.
     $term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField();
     $term->vid = $vocabulary->vid;
+    $term->langcode = LANGUAGE_NONE;
     taxonomy_term_save($term);
     return $term;
   }
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index f7175c3d22f05364fc9a0bfd9759469e11292e14..9b70e09fdca64b3655a17570f9063e6ca6ff244e 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -137,6 +137,13 @@ function user_schema() {
         'default' => '',
         'description' => 'Unique user name.',
       ),
+      'langcode' => array(
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => "The {language}.langcode of the user's profile.",
+      ),
       'pass' => array(
         'type' => 'varchar',
         'length' => 128,
@@ -202,12 +209,12 @@ function user_schema() {
         'not null' => FALSE,
         'description' => "User's time zone.",
       ),
-      'language' => array(
+      'preferred_langcode' => array(
         'type' => 'varchar',
         'length' => 12,
         'not null' => TRUE,
         'default' => '',
-        'description' => "User's default language.",
+        'description' => 'The {language}.langcode that the user prefers for receiving emails and viewing the site.',
       ),
       'picture' => array(
         'type' => 'int',
@@ -353,6 +360,41 @@ function user_update_8000() {
   }
 }
 
+/**
+ * Splits {users}.language field to langcode and preferred_langcode.
+ *
+ * @see http://drupal.org/node/1454538
+ */
+function user_update_8001() {
+  // The former language field is the language preference of the user. Rename
+  // this to preferred_langcode in order to distinguish it from the langcode
+  // field common to all entity types, used for identifying the language of the
+  // entity itself.
+  $preferred_langcode_field = array(
+    'type' => 'varchar',
+    'length' => 12,
+    'not null' => TRUE,
+    'default' => '',
+    'description' => 'The {language}.langcode that the user prefers for receiving emails and viewing the site.',
+  );
+  db_change_field('users', 'language', 'preferred_langcode', $preferred_langcode_field);
+
+  // Add the langcode field.
+  $langcode_field = array(
+    'type' => 'varchar',
+    'length' => 12,
+    'not null' => TRUE,
+    'default' => '',
+    'description' => "The {language}.langcode of the user's profile.",
+  );
+  db_add_field('users', 'langcode', $langcode_field);
+
+  // Since distinguishing the language of the user entity from the user's
+  // preferred language is a new feature in Drupal 8, assume that for updated
+  // sites, existing user entities are in the user's preferred language.
+  db_update('users')->expression('langcode', 'preferred_langcode')->execute();
+}
+
 /**
  * @} End of "addtogroup updates-7.x-to-8.x"
  */
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 7fb442f15d070e64da0f86b758ec19eeb541fad4..00c91ac31e0dbeb205917bb4755d658422c9f4ff 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -422,6 +422,10 @@ function user_save($account, $edit = array()) {
     foreach ($edit as $key => $value) {
       $account->$key = $value;
     }
+    // Default the user entity language to the user's preferred language.
+    if (!isset($account->langcode) && isset($account->preferred_langcode)) {
+      $account->langcode = $account->preferred_langcode;
+    }
     field_attach_presave('user', $account);
     module_invoke_all('entity_presave', $account, 'user');
 
@@ -3429,8 +3433,8 @@ function theme_user_signature($variables) {
  */
 function user_preferred_language($account, $default = NULL) {
   $language_list = language_list();
-  if (!empty($account->language) && isset($language_list[$account->language])) {
-    return $language_list[$account->language];
+  if (!empty($account->preferred_langcode) && isset($language_list[$account->preferred_langcode])) {
+    return $language_list[$account->preferred_langcode];
   }
   else {
     return $default ? $default : language_default();
diff --git a/core/modules/user/user.test b/core/modules/user/user.test
index 5b97e8b3e1ae87f8c83074d1fad29dd0bafbfb5d..a2f7ab10b2489c0e20a2db0ec05d2db11fb740f7 100644
--- a/core/modules/user/user.test
+++ b/core/modules/user/user.test
@@ -166,7 +166,8 @@ class UserRegistrationTestCase extends DrupalWebTestCase {
     $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), t('Correct creation time.'));
     $this->assertEqual($new_user->status, variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS ? 1 : 0, t('Correct status field.'));
     $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), t('Correct time zone field.'));
-    $this->assertEqual($new_user->language, '', t('Correct language field.'));
+    $this->assertEqual($new_user->langcode, '', t('Correct language field.'));
+    $this->assertEqual($new_user->preferred_langcode, '', t('Correct preferred language field.'));
     $this->assertEqual($new_user->picture, '', t('Correct picture field.'));
     $this->assertEqual($new_user->init, $mail, t('Correct init field.'));
   }
diff --git a/core/modules/user/user.tokens.inc b/core/modules/user/user.tokens.inc
index 76ec4a2c873afd11a568fd16e62a1b3c25f28416..833e7636a173a6da44210756b7c9bd22ff012652 100644
--- a/core/modules/user/user.tokens.inc
+++ b/core/modules/user/user.tokens.inc
@@ -65,10 +65,10 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
   $url_options = array('absolute' => TRUE);
   if (isset($options['language'])) {
     $url_options['language'] = $options['language'];
-    $language_code = $options['language']->langcode;
+    $langcode = $options['language']->langcode;
   }
   else {
-    $language_code = NULL;
+    $langcode = NULL;
   }
   $sanitize = !empty($options['sanitize']);
 
@@ -103,12 +103,12 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
 
         // These tokens are default variations on the chained tokens handled below.
         case 'last-login':
-          $replacements[$original] = !empty($account->login) ? format_date($account->login, 'medium', '', NULL, $language_code) : t('never');
+          $replacements[$original] = !empty($account->login) ? format_date($account->login, 'medium', '', NULL, $langcode) : t('never');
           break;
 
         case 'created':
           // In the case of user_presave the created date may not yet be set.
-          $replacements[$original] = !empty($account->created) ? format_date($account->created, 'medium', '', NULL, $language_code) : t('not yet created');
+          $replacements[$original] = !empty($account->created) ? format_date($account->created, 'medium', '', NULL, $langcode) : t('not yet created');
           break;
       }
     }
diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install
index 1c8636d7454ed2606f613724f254235f6ba9b65c..d62b8d3003133c335e8e96859a62ccb84b6e0461 100644
--- a/profiles/standard/standard.install
+++ b/profiles/standard/standard.install
@@ -289,6 +289,7 @@ function standard_install() {
     'name' => st('Tags'),
     'description' => $description,
     'machine_name' => 'tags',
+    'langcode' => language_default()->langcode,
     'help' => $help,
 
   );