diff --git a/core/modules/comment/comment.entity.inc b/core/modules/comment/comment.entity.inc
index bce8b56..479f25a 100644
--- a/core/modules/comment/comment.entity.inc
+++ b/core/modules/comment/comment.entity.inc
@@ -6,6 +6,91 @@
  */
 
 /**
+ * Implements hook_entity_property_info().
+ */
+function comment_entity_property_info() {
+  $info = array();
+
+  $info['comment']['properties']['cid'] = array(
+    'label' => t('Comment ID'),
+    'description' => t('The unique ID of the comment.'),
+    'type' => 'integer',
+  );
+  $info['comment']['properties']['hostname'] = array(
+    'label' => t('IP Address'),
+    'description' => t('The IP address of the computer the comment was posted from.'),
+    'type' => 'text',
+  );
+  $info['comment']['properties']['name'] = array(
+    'label' => t('Name'),
+    'description' => t('The name left by the comment author.'),
+    'type' => 'text',
+  );
+  $info['comment']['properties']['mail'] = array(
+    'label' => t('Email address'),
+    'description' => t('The email address left by the comment author.'),
+    'type' => 'text',
+  );
+  $info['comment']['properties']['homepage'] = array(
+    'label' => t('Home page'),
+    'description' => t('The home page URL left by the comment author.'),
+    'type' => 'text',
+  );
+  $info['comment']['properties']['subject'] = array(
+    'label' => t('Subject'),
+    'description' => t('The subject of the comment.'),
+    'type' => 'text',
+    'required' => TRUE,
+  );
+  $info['comment']['properties']['created'] = array(
+    'label' => t('Date created'),
+    'description' => t('The date the comment was posted.'),
+    'type' => 'date',
+  );
+  $info['comment']['properties']['parent'] = array(
+    'label' => t('Parent'),
+    'description' => t("The comment's parent, if comment threading is active."),
+    'type' => 'entity',
+    'entity type' => 'comment',
+    'storage field' => 'pid',
+  );
+  $info['comment']['properties']['node_type'] = array(
+    'label' => t('Comment node type'),
+    'description' => t('The type of the node the comment was posted to.'),
+    'type' => 'token',
+    'storage field' => FALSE,
+  );
+  $info['comment']['properties']['node'] = array(
+    'label' => t('Node'),
+    'description' => t('The node the comment was posted to.'),
+    'type' => 'entity',
+    'entity type' => 'node',
+    'required' => TRUE,
+    'storage field' => 'nid',
+  );
+  $info['comment']['properties']['user'] = array(
+    'label' => t('User'),
+    'description' => t('The author of the comment.'),
+    'type' => 'entity',
+    'entity type' => 'user',
+    'required' => TRUE,
+    'storage field' => 'uid',
+  );
+  $info['comment']['properties']['status'] = array(
+    'label' => t('Status'),
+    'description' => t('Whether the comment is published or unpublished.'),
+    'type' => 'boolean',
+  );
+  $info['comment']['properties']['language'] = array(
+    'label' => t('Language'),
+    'description' => t('The language the comment was posted in.'),
+    'type' => 'token',
+    'storage field' => 'langcode',
+  );
+  return $info;
+}
+
+/**
  * Defines the comment entity class.
  */
 class Comment extends Entity {
@@ -84,6 +169,23 @@ class Comment extends Entity {
 class CommentStorageController extends EntityDatabaseStorageController {
 
   /**
+   * Overrides EntityDatabaseStorageController::create().
+   */
+  public function create(array $values) {
+    // Care about setting the bundle key first.
+    if (!is_object($values['node'])) {
+      $values['node'] = node_load($values['node']);
+    }
+    $entity = parent::create(array('node_type' => $values['node']->type));
+
+    // Set the remaining values.
+    foreach ($values as $key => $value) {
+      $entity->set($key, $value);
+    }
+    return $entity;
+  }
+
+  /**
    * Overrides EntityDatabaseStorageController::buildQuery().
    */
   protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
@@ -123,11 +225,6 @@ class CommentStorageController extends EntityDatabaseStorageController {
     if (!isset($comment->status)) {
       $comment->status = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED;
     }
-    // Make sure we have a proper bundle name.
-    if (!isset($comment->node_type)) {
-      $node = node_load($comment->nid);
-      $comment->node_type = 'comment_node_' . $node->type;
-    }
     if (!$comment->cid) {
       // Add the comment to database. This next section builds the thread field.
       // Also see the documentation for comment_view().
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 1a3580f..0d04725 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -764,7 +764,7 @@ function comment_node_page_additions($node) {
 
   // Append comment form if needed.
   if (user_access('post comments') && $node->comment == COMMENT_NODE_OPEN && (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_BELOW)) {
-    $comment = entity_create('comment', array('nid' => $node->nid));
+    $comment = entity_create('comment', array('node' => $node));
     $additions['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", $comment);
   }
 
diff --git a/core/modules/comment/comment.pages.inc b/core/modules/comment/comment.pages.inc
index 344e757..895aa6d 100644
--- a/core/modules/comment/comment.pages.inc
+++ b/core/modules/comment/comment.pages.inc
@@ -35,7 +35,7 @@ function comment_reply($node, $pid = NULL) {
   // The user is previewing a comment prior to submitting it.
   if ($op == t('Preview')) {
     if (user_access('post comments')) {
-      $comment = entity_create('comment', array('nid' => $node->nid, 'pid' => $pid));
+      $comment = entity_create('comment', array('node' => $node, 'parent' => $pid));
       $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", $comment);
     }
     else {
@@ -87,7 +87,7 @@ function comment_reply($node, $pid = NULL) {
       drupal_goto("node/$node->nid");
     }
     elseif (user_access('post comments')) {
-      $comment = entity_create('comment', array('nid' => $node->nid, 'pid' => $pid));
+      $comment = entity_create('comment', array('node' => $node, 'parent' => $pid));
       $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", $comment);
     }
     else {
diff --git a/core/modules/comment/comment.test b/core/modules/comment/comment.test
index 9257513..259b9e3 100644
--- a/core/modules/comment/comment.test
+++ b/core/modules/comment/comment.test
@@ -89,15 +89,15 @@ class CommentHelperCase extends DrupalWebTestCase {
     }
 
     if (isset($match[1])) {
-      return entity_create('comment', array('id' => $match[1], 'subject' => $subject, 'comment' => $comment));
+      return (object) array('id' => $match[1], 'subject' => $subject, 'comment' => $comment);
     }
   }
 
   /**
    * Checks current page for specified comment.
    *
-   * @param object $comment
-   *   The comment object.
+   * @param stdClass $comment
+   *   A comment object (not a full entity).
    * @param boolean $reply
    *   Boolean indicating whether the comment is a reply to another comment.
    *
@@ -445,11 +445,9 @@ class CommentInterfaceTest extends CommentHelperCase {
     // Create a new comment. This helper function may be run with different
     // comment settings so use comment_save() to avoid complex setup.
     $comment = entity_create('comment', array(
-      'cid' => NULL,
-      'nid' => $this->node->nid,
-      'node_type' => $this->node->type,
-      'pid' => 0,
-      'uid' => $this->loggedInUser->uid,
+      'node' => $this->node,
+      'parent' => 0,
+      'user' => $this->loggedInUser->uid,
       'status' => COMMENT_PUBLISHED,
       'subject' => $this->randomName(),
       'hostname' => ip_address(),
@@ -496,8 +494,8 @@ class CommentInterfaceTest extends CommentHelperCase {
 
       // Add a comment.
       $comment = entity_create('comment', array(
-        'nid' => $node->nid,
-        'uid' => $case['comment_uid'],
+        'node' => $node,
+        'user' => $case['comment_uid'],
         'status' => $case['comment_status'],
         'subject' => $this->randomName(),
         'language' => LANGUAGE_NOT_SPECIFIED,
@@ -772,11 +770,9 @@ class CommentInterfaceTest extends CommentHelperCase {
         // Create a comment via CRUD API functionality, since
         // $this->postComment() relies on actual user permissions.
         $comment = entity_create('comment', array(
-          'cid' => NULL,
-          'nid' => $this->node->nid,
-          'node_type' => $this->node->type,
-          'pid' => 0,
-          'uid' => 0,
+          'node' => $this->node,
+          'parent' => 0,
+          'user' => 0,
           'status' => COMMENT_PUBLISHED,
           'subject' => $this->randomName(),
           'hostname' => ip_address(),
@@ -1602,7 +1598,7 @@ class CommentApprovalTest extends CommentHelperCase {
     // Get unapproved comment id.
     $this->drupalLogin($this->admin_user);
     $anonymous_comment4 = $this->getUnapprovedComment($subject);
-    $anonymous_comment4 = entity_create('comment', array('id' => $anonymous_comment4, 'subject' => $subject, 'comment' => $body));
+    $anonymous_comment4 = (object) array('id' => $anonymous_comment4, 'subject' => $subject, 'comment' => $body);
     $this->drupalLogout();
 
     $this->assertFalse($this->commentExists($anonymous_comment4), t('Anonymous comment was not published.'));
@@ -1666,7 +1662,7 @@ class CommentApprovalTest extends CommentHelperCase {
     // Get unapproved comment id.
     $this->drupalLogin($this->admin_user);
     $anonymous_comment4 = $this->getUnapprovedComment($subject);
-    $anonymous_comment4 = entity_create('comment', array('id' => $anonymous_comment4, 'subject' => $subject, 'comment' => $body));
+    $anonymous_comment4 = (object) array('id' => $anonymous_comment4, 'subject' => $subject, 'comment' => $body);
     $this->drupalLogout();
 
     $this->assertFalse($this->commentExists($anonymous_comment4), t('Anonymous comment was not published.'));
diff --git a/core/modules/entity/entity.api.php b/core/modules/entity/entity.api.php
index e983778..b85bda2 100644
--- a/core/modules/entity/entity.api.php
+++ b/core/modules/entity/entity.api.php
@@ -214,6 +214,86 @@ function hook_entity_info_alter(&$entity_info) {
 }
 
 /**
+ * Provide information about entity properties.
+ *
+ * All entity properties and fields are defined using this hook. For example,
+ * this information is leveraged by the EntityInterface::get() and
+ * EntityInterface::set() methods, as well as by many modules.
+ *
+ * @return
+ *   An array whose keys are entity type names and whose values are arrays
+ *   containing the keys:
+ *   - properties: The array describing all properties for this entity. Entries
+ *     are keyed by the property name and contain an array of metadata for each
+ *     property. The name may only contain alphanumeric lowercase characters
+ *     and underscores. Known keys are:
+ *     - label: A human readable, translated label for the property.
+ *     - description: (optional) A human readable, translated description for
+ *       the property.
+ *     - type: The data type of the property. This may either be a primitive
+ *       data type, an entity type or a data type as registered in
+ *       hook_data_type_info(). The following primitive data types are valid:
+ *        - text: Any text.
+ *        - token: A string containing only lowercase letters, numbers, and
+ *          underscores starting with a letter; e.g. as used for machine
+ *          readable names.
+ *        - integer: An integer value.
+ *        - decimal: A float or integer value.
+ *        - date: A full date and time, represented as timestamp.
+ *        - duration: A duration, represented as number of seconds.
+ *        - boolean: A boolean value, represented either as TRUE and FALSE or
+ *          1 and 0.
+ *        - uri: An absolute URI or URL.
+ *       For specifying an entity type use type 'entity' and optionally specify
+ *       the type of the entity using the 'entity type' and 'bundle' keys.
+ *     - multiple: (optional) A boolean specifying whether the property contains
+ *       a list of values, which is represented as numerically indexed array
+ *       containing the values in their usual representation. Defaults to FALSE.
+ *     - entity type: (optional) If the data type is 'entity', the type of the
+ *       entity.
+ *     - bundle: (optional) If the data type is 'entity', the bundle of the
+ *       entity.
+ *     - storage field: (optional) The storage field which stores the value of
+ *       this property, or FALSE if the property is not stored. Defaults to the
+ *       name of the property.
+ *     - required: (optional) Whether a non-NULL value is required for this
+ *       property for the entity being able to be created or saved. Defaults to
+ *       FALSE.
+ *     - module: (optional) The module providing the property. No value means
+ *       the property is provided by the entity type providing module.
+ *   - bundles: An array keyed by bundle name containing information about
+ *     further properties related to the bundles only. This array may contain
+ *     the key 'properties' with an array of info about the bundle specific
+ *     properties, structured in the same way as the entity properties array.
+ *
+ *  @see hook_entity_property_info_alter()
+ *  @see EntityInterface::propertyInfo()
+ */
+function hook_entity_property_info() {
+  $info['node']['properties']['nid'] = array(
+    'label' => t('Node ID'),
+    'type' => 'integer',
+    'description' => t('The unique content ID.'),
+  );
+  return $info;
+}
+
+/**
+ * Alter entity property information.
+ *
+ * @see hook_entity_property_info()
+ */
+function hook_entity_property_info_alter(&$info) {
+  $properties = &$info['node']['bundles']['poll']['properties'];
+
+  $properties['poll-votes'] = array(
+    'label' => t('Poll votes'),
+    'description' => t("The number of votes that have been cast on a poll node."),
+    'type' => 'integer',
+  );
+}
+
+/**
  * Act on entities when loaded.
  *
  * This is a generic load hook called for all entity types loaded via the
diff --git a/core/modules/entity/entity.class.inc b/core/modules/entity/entity.class.inc
index 7ffd496..85a594d 100644
--- a/core/modules/entity/entity.class.inc
+++ b/core/modules/entity/entity.class.inc
@@ -69,8 +69,8 @@ interface EntityInterface {
    * Returns the bundle of the entity.
    *
    * @return
-   *   The bundle of the entity. Defaults to the entity type if the entity type
-   *   does not make use of different bundles.
+   *   The bundle of the entity. Defaults to FALSE if the entity type does not
+   *   make use of different bundles.
    */
   public function bundle();
 
@@ -93,6 +93,134 @@ interface EntityInterface {
   public function uri();
 
   /**
+   * Returns the language code associated with the entity.
+   *
+   * If the entity is not specific to a certain language, the language code of
+   * an entity is one of the system language constants, e.g.
+   * LANGUAGE_NOT_SPECIFIED, LANGUAGE_NOT_APPLICABLE or LANGUAGE_MULTIPLE.
+   * Otherwise, for language-specific entities the language code refers to
+   * the default language as returned by EntityInterface::language().
+   *
+   * @return
+   *   A valid language code.
+   *
+   * @see EntityInterface::language()
+   */
+  public function languageCode();
+
+  /**
+   * Returns the default language of a language-specific entity.
+   *
+   * @return
+   *   The language object of the entity's default language, or FALSE if the
+   *   entity is not language-specific.
+   *
+   * @see EntityInterface::translations()
+   * @see EntityInterface::languageCode()
+   */
+  public function language();
+
+  /**
+   * Returns the languages the entity is translated to.
+   *
+   * @return
+   *   An array of language objects, keyed by language codes.
+   *
+   * @see EntityInterface::language()
+   */
+  public function translations();
+
+  /**
+   * Returns information about all properties of the entity.
+   *
+   * @param string $property_name
+   *   (optional) If given, the entry of the specified property is returned.
+   *   Otherwise, an array of information of all properties is returned.
+   *
+   * @return array|false
+   *   If no property name is given, an array of property information containing
+   *   entries for properties keyed by the property name. Each entry contains
+   *   keys describing the property as defined in hook_entity_property_info(),
+   *   e.g. 'label' and 'type'.
+   *   If a property name is given, the entry for this property is returned, or
+   *   FALSE if the property has no entry.
+   *
+   * @see hook_entity_property_info()
+   * @see entity_get_property_info()
+   */
+  public function propertyInfo($property_name = NULL);
+
+  /**
+   * Returns the value of an entity property.
+   *
+   * Returned property values match their property information as declared in
+   * hook_entity_property_info(). For entity references the loaded entity
+   * objects will be returned, e.g. a user entity will be returned for the node
+   * author property.
+   *
+   * @param $property_name
+   *   The name of the property to return, as described in
+   *   EntityInterface::propertyInfo(); e.g. 'title'.
+   * @param $langcode
+   *   (optional) If the property is translatable, the language code of the
+   *   language that should be used for getting the property. If set to NULL,
+   *   the entity's default language is being used.
+   *
+   * @return
+   *   The property value, or NULL if it is not defined.
+   *
+   * @see EntityInterface::getRawValue()
+   * @see EntityInterface::set()
+   * @see EntityInterface::language()
+   */
+  public function get($property_name, $langcode = NULL);
+
+  /**
+   * Returns the raw value of an entity property.
+   *
+   * Returned property values match their property information as declared in
+   * hook_entity_property_info(). However for entity references just the IDs
+   * of the entities will be returned, e.g. a user ID will be returned for the
+   * node author property.
+   *
+   * @param $property_name
+   *   The name of the property to return, as described in
+   *   EntityInterface::propertyInfo(); e.g. 'title'.
+   * @param $langcode
+   *   (optional) If the property is translatable, the language code of the
+   *   language that should be used for getting the property. If set to NULL,
+   *   the entity's default language is being used.
+   *
+   * @return
+   *   The raw property value, or NULL if it is not defined.
+   *
+   * @see EntityInterface::set()
+   * @see EntityInterface::get()
+   * @see EntityInterface::language()
+   */
+  public function getRawValue($property_name, $langcode = NULL);
+
+  /**
+   * Sets the value of an entity property.
+   *
+   * @param $property_name
+   *   The name of the property to set; e.g., 'title'.
+   * @param $value
+   *   The value to set, or NULL to unset the property. For entity references
+   *   the entity object as well as the entity ID may be passed as value, e.g.
+   *   for the node author property a user entity or a user ID may be passed.
+   * @param $langcode
+   *   (optional) If the property is translatable, the language code of the
+   *   language that should be used for getting the property. If set to NULL,
+   *   the entity's default language is being used.
+   *
+   * @see EntityInterface::get()
+   * @see EntityInterface::getRawValue()
+   * @see EntityInterface::language()
+   */
+  public function set($property_name, $value, $langcode = NULL);
+
+  /**
    * Saves an entity permanently.
    *
    * @return
@@ -139,6 +267,13 @@ interface EntityInterface {
 class Entity implements EntityInterface {
 
   /**
+   * The language code of the entity's default language.
+   *
+   * @var string
+   */
+  public $langcode = LANGUAGE_NOT_SPECIFIED;
+
+  /**
    * The entity type.
    *
    * @var string
@@ -179,9 +314,15 @@ class Entity implements EntityInterface {
   public function __construct(array $values = array(), $entity_type) {
     $this->entityType = $entity_type;
     $this->setUp();
-    // Set initial values.
+
+    // Set initial values. But start with setting the bundle property as it is
+    // necessary for being able to set bundle-specific properties.
+    if ($this->bundleKey && !empty($values[$this->bundleKey])) {
+      $this->set($this->bundleKey, $values[$this->bundleKey]);
+      unset($values[$this->bundleKey]);
+    }
     foreach ($values as $key => $value) {
-      $this->$key = $value;
+      $this->set($key, $value);
     }
   }
 
@@ -191,7 +332,7 @@ class Entity implements EntityInterface {
   protected function setUp() {
     $this->entityInfo = entity_get_info($this->entityType);
     $this->idKey = $this->entityInfo['entity keys']['id'];
-    $this->bundleKey = isset($this->entityInfo['entity keys']['bundle']) ? $this->entityInfo['entity keys']['bundle'] : NULL;
+    $this->bundleKey = !empty($this->entityInfo['entity keys']['bundle']) ? $this->entityInfo['entity keys']['bundle'] : NULL;
   }
 
   /**
@@ -226,7 +367,7 @@ class Entity implements EntityInterface {
    * Implements EntityInterface::bundle().
    */
   public function bundle() {
-    return isset($this->bundleKey) ? $this->{$this->bundleKey} : $this->entityType;
+    return isset($this->bundleKey) && !empty($this->{$this->bundleKey}) ? $this->{$this->bundleKey} : FALSE;
   }
 
   /**
@@ -276,6 +417,174 @@ class Entity implements EntityInterface {
   }
 
   /**
+   * Implements EntityInterface::languageCode().
+   */
+  public function languageCode() {
+    return $this->langcode;
+  }
+
+  /**
+   * Implements EntityInterface::language().
+   */
+  public function language() {
+    // @todo: Check for language.module instead, once Field API language
+    // handling depends upon it too.
+    return module_exists('locale') ? language_load($this->languageCode()) : FALSE;
+  }
+
+  /**
+   * Implements EntityInterface::translations().
+   */
+  public function translations() {
+    $languages = array();
+    if ($this->entityInfo['fieldable'] && ($default_language = $this->language())) {
+      // Go through translatable properties and determine all languages for
+      // which translated values are available.
+      $instances = field_info_instances($this->entityType, $this->getFieldBundle());
+
+      foreach ($instances as $field_name => $instance) {
+        $field = field_info_field($field_name);
+        if (field_is_translatable($this->entityType, $field) && isset($this->$field_name)) {
+          foreach ($this->$field_name as $langcode => $value)  {
+            $languages[$langcode] = TRUE;
+          }
+        }
+      }
+      // Remove the default language from the translations.
+      unset($languages[$default_language->langcode]);
+      $languages = array_intersect_key(language_list(), $languages);
+    }
+    return $languages;
+  }
+
+  /**
+   * Implements EntityInterface::propertyInfo().
+   */
+  public function propertyInfo($property_name = NULL) {
+    $info = entity_get_property_info($this->entityType);
+    $properties = isset($info['properties']) ? $info['properties'] : array();
+
+    // Add in bundle-specific properties.
+    $bundle = $this->bundle();
+    if ($bundle && !empty($info['bundles'][$bundle]['properties'])) {
+      $properties = $info['bundles'][$bundle]['properties'] + $properties;
+    }
+
+    if (isset($property_name)) {
+      return isset($properties[$property_name]) ? $properties[$property_name] : FALSE;
+    }
+    return $properties;
+  }
+
+  /**
+   * Implements EntityInterface::get().
+   */
+  public function get($property_name, $langcode = NULL) {
+    $value = $this->getRawValue($property_name, $langcode);
+
+    // Load entities.
+    $info = $this->propertyInfo($property_name);
+    if (isset($value) && $info['type'] == 'entity') {
+
+      if (!empty($info['multiple'])) {
+        $value = entity_load($info['entity type'], $value);
+      }
+      else {
+        $result = entity_load($info['entity type'], array($value));
+        $value = reset($result);
+      }
+    }
+
+    return $value;
+  }
+
+  /**
+   * Implements EntityInterface::getRawValue().
+   */
+  public function getRawValue($property_name, $langcode = NULL) {
+    $info = $this->propertyInfo($property_name);
+    $name = !empty($info['storage field']) ? $info['storage field'] : $property_name;
+
+    // Handle fields.
+    $is_field = !empty($info['module']) && $info['module'] == 'field';
+    if ($is_field && field_info_instance($this->entityType, $name, $this->getFieldBundle())) {
+      $field = field_info_field($name);
+      $langcode = $this->getFieldLangcode($field, $langcode);
+      $value = isset($this->{$name}[$langcode]) ? $this->{$name}[$langcode] : NULL;
+    }
+    else {
+      // Handle properties being not fields.
+      // @todo: Add support for translatable properties being not fields.
+      $value = isset($this->{$name}) ? $this->{$name} : NULL;
+    }
+    return $value;
+  }
+
+  /**
+   * Implements EntityInterface::set().
+   */
+  public function set($property_name, $value, $langcode = NULL) {
+    $info = $this->propertyInfo($property_name);
+    $name = !empty($info['storage field']) ? $info['storage field'] : $property_name;
+
+    // For entity references deal with entity objects passed as values.
+    if (isset($value) && $info['type'] == 'entity') {
+      if (!empty($info['multiple'])) {
+        foreach ($value as $delta => $entity) {
+          if ($entity instanceof EntityInterface) {
+            $value[$delta] = $entity->id();
+          }
+        }
+      }
+      elseif ($value instanceof EntityInterface) {
+        $value = $value->id();
+      }
+      // @todo: Remove once all entities have been converted.
+      elseif (is_object($value)) {
+        list($id, ,) = entity_extract_ids($info['entity type'], $value);
+        $value = $id;
+      }
+    }
+
+    // Handle fields.
+    if ($this->entityInfo['fieldable'] && field_info_instance($this->entityType, $name, $this->getFieldBundle())) {
+      $field = field_info_field($name);
+      $langcode = $this->getFieldLangcode($field, $langcode);
+      $this->{$name}[$langcode] = $value;
+    }
+    else {
+      // Handle properties being not fields.
+      // @todo: Add support for translatable properties being not fields.
+      $this->{$name} = $value;
+    }
+  }
+
+  /**
+   * Determines the language code to use for accessing a field value in a certain language.
+   */
+  protected function getFieldLangcode($field, $langcode = NULL) {
+    // Only apply the given langcode if the entity is language-specific.
+    // Otherwise translatable fields are handled as non-translatable fields.
+    if (field_is_translatable($this->entityType, $field) && ($default_language = $this->language())) {
+      // For translatable fields the values in default language are stored using
+      // the language code of the default language.
+      return isset($langcode) ? $langcode : $default_language->langcode;
+    }
+    else {
+      // Non-translatable fields always use LANGUAGE_NOT_SPECIFIED.
+      return LANGUAGE_NOT_SPECIFIED;
+    }
+  }
+
+  /**
+   * Gets the bundle as used by the field API, thus defaulting to the entity type.
+   */
+  protected function getFieldBundle() {
+    $bundle = $this->bundle();
+    return $bundle ? $bundle : $this->entityType;
+  }
+
+  /**
    * Implements EntityInterface::save().
    */
   public function save() {
diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module
index 9b2b6f2..344f5c2 100644
--- a/core/modules/entity/entity.module
+++ b/core/modules/entity/entity.module
@@ -186,8 +186,9 @@ function entity_create_stub_entity($entity_type, $ids) {
   if (!empty($info['entity keys']['bundle']) && isset($ids[2])) {
     $values[$info['entity keys']['bundle']] = $ids[2];
   }
-  // @todo Once all entities are converted, just rely on entity_create().
-  return isset($info['entity class']) ? entity_create($entity_type, $values) : (object) $values;
+  // @todo Once all entities are converted, just rely on entity_create() for
+  // creation and scratch stub entities.
+  return (object) $values;
 }
 
 /**
@@ -272,14 +273,18 @@ function entity_delete_multiple($entity_type, $ids) {
 /**
  * Constructs a new entity object, without permanently saving it.
  *
- * @param $entity_type
+ * @param string $entity_type
  *   The type of the entity.
- * @param $values
- *   An array of values to set, keyed by property name. If the entity type has
- *   bundles the bundle key has to be specified.
+ * @param array $values
+ *   An array of values to set, keyed by property name. Non-NULL values for all
+ *   properties which are marked as required via hook_entity_property_info()
+ *   have to be provided.
  *
  * @return EntityInterface
  *   A new entity object.
+ *
+ * @see hook_entity_property_info()
+ * @see EntityInterface::set()
  */
 function entity_create($entity_type, array $values) {
   return entity_get_controller($entity_type)->create($values);
@@ -415,6 +420,55 @@ function entity_label($entity_type, $entity) {
 }
 
 /**
+ * Implements hook_hook_info().
+ */
+function entity_hook_info() {
+  $hook_info['entity_property_info'] = array(
+    'group' => 'entity',
+  );
+  $hook_info['entity_property_info_alter'] = array(
+    'group' => 'entity',
+  );
+  return $hook_info;
+}
+
+/**
+ * Gets the entity property information of an entity type.
+ *
+ * @param $entity_type
+ *   The entity type, e.g. node, for which the info shall be returned, or NULL
+ *   to return an array with info about all types.
+ *
+ * @see hook_entity_property_info()
+ * @see hook_entity_property_info_alter()
+ */
+function entity_get_property_info($entity_type = NULL) {
+  // Use the advanced drupal_static() pattern, since this is called very often.
+  static $drupal_static_fast;
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['info'] = &drupal_static(__FUNCTION__);
+  }
+  $info = &$drupal_static_fast['info'];
+
+  // hook_entity_property_info() includes translated strings, so each language
+  // is cached separately.
+  $langcode = $GLOBALS['language_interface']->langcode;
+
+  if (empty($info)) {
+    if ($cache = cache()->get("entity_property_info:$langcode")) {
+      $info = $cache->data;
+    }
+    else {
+      $info = module_invoke_all('entity_property_info');
+      // Let other modules alter the entity info.
+      drupal_alter('entity_property_info', $info);
+      cache()->set("entity_property_info:$langcode", $info);
+    }
+  }
+  return empty($entity_type) ? $info : (isset($info[$entity_type]) ? $info[$entity_type] : array());
+}
+
+/**
  * Attaches field API validation to entity forms.
  */
 function entity_form_field_validate($entity_type, $form, &$form_state) {
diff --git a/core/modules/entity/tests/entity.test b/core/modules/entity/tests/entity.test
index 35b6ab1..21efc71 100644
--- a/core/modules/entity/tests/entity.test
+++ b/core/modules/entity/tests/entity.test
@@ -19,7 +19,7 @@ class EntityAPITestCase extends DrupalWebTestCase {
   }
 
   function setUp() {
-    parent::setUp('entity', 'entity_test');
+    parent::setUp('entity', 'entity_test', 'comment');
   }
 
   /**
@@ -29,11 +29,11 @@ class EntityAPITestCase extends DrupalWebTestCase {
     $user1 = $this->drupalCreateUser();
 
     // Create some test entities.
-    $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid));
+    $entity = entity_create('entity_test', array('name' => 'test', 'user' => $user1->uid));
     $entity->save();
-    $entity = entity_create('entity_test', array('name' => 'test2', 'uid' => $user1->uid));
+    $entity = entity_create('entity_test', array('name' => 'test2', 'user' => $user1->uid));
     $entity->save();
-    $entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL));
+    $entity = entity_create('entity_test', array('name' => 'test', 'user' => NULL));
     $entity->save();
 
     $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test')));
@@ -65,6 +65,156 @@ class EntityAPITestCase extends DrupalWebTestCase {
     $all = entity_test_load_multiple(FALSE);
     $this->assertTrue(empty($all), 'Deleted all entities.');
   }
+
+  /**
+   * Tests Entity getters/setters.
+   */
+  function testEntityGettersSetters() {
+    $entity = entity_create('entity_test', array('name' => 'test'));
+    $this->assertNull($entity->get('description'), 'Property is not set.');
+
+    $entity->set('description', 'Example.');
+    $this->assertEqual($entity->description, 'Example.', 'Property has been set.');
+
+    $value = $entity->get('description');
+    $this->assertEqual($value, $entity->description, 'Property has been retrieved.');
+
+    // Make sure setting/getting translations boils down to setting/getting the
+    // regular value as the entity and property are not translatable.
+    $entity->set('description', NULL, 'en');
+    $this->assertNull($entity->description, 'Language neutral property has been set.');
+
+    $value = $entity->get('description', 'en');
+    $this->assertNull($value, 'Language neutral property has been retrieved.');
+
+    // Test an entity reference.
+    $this->assertNull($entity->get('comment'), 'Entity reference is empty.');
+
+    $node = $this->drupalCreateNode();
+    $comment = entity_create('comment', array('node' => $node));
+    $comment->save();
+    $this->assertEqual($comment->nid, $node->nid, 'Entity reference has been instantiated on entity creation.');
+
+    // Test setting the comment referency by object and ID.
+    $entity->set('comment', $comment);
+    $this->assertEqual($entity->comment_id, $comment->cid, 'Comment reference has been set by entity object.');
+
+    $entity->set('comment', $comment->cid);
+    $this->assertEqual($entity->comment_id, $comment->cid, 'Comment reference has been set by ID.');
+
+    $entity->set('comment', NULL);
+    $this->assertNull($entity->comment_id, 'Comment reference has been set by ID.');
+  }
+}
+
+/**
+ * Tests entity translation.
+ */
+class EntityTranslationTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity Translation',
+      'description' => 'Tests entity translation functionality.',
+      'group' => 'Entity API',
+    );
+  }
+
+  function setUp() {
+    // Enable translations for the test entity type. We cannot use
+    // variable_set() here as variables are cleared by parent::setUp();
+    $GLOBALS['entity_test_translation'] = TRUE;
+    parent::setUp('entity_test', 'language', 'locale');
+
+    // Create a translatable test field.
+    $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
+    $field = array(
+      'field_name' => $this->field_name,
+      'type' => 'text',
+      'cardinality' => 4,
+      'translatable' => TRUE,
+    );
+    field_create_field($field);
+    $this->field = field_read_field($this->field_name);
+
+    $instance = array(
+      'field_name' => $this->field_name,
+      'entity_type' => 'entity_test',
+      'bundle' => 'entity_test',
+    );
+    field_create_instance($instance);
+    $this->instance = field_read_instance('entity_test', $this->field_name, 'entity_test');
+
+    // Create test languages.
+    $this->langcodes = array();
+    for ($i = 0; $i < 3; ++$i) {
+      $language = (object) array(
+        'langcode' => 'l' . $i,
+        'name' => $this->randomString(),
+      );
+      $this->langcodes[$i] = $language->langcode;
+      language_save($language);
+    }
+  }
+
+  /**
+   * Tests language related methods of the Entity class.
+   */
+  function testEntityLanguageMethods() {
+    $entity = entity_create('entity_test', array(
+      'name' => 'test',
+      'user' => $GLOBALS['user']->uid,
+    ));
+    $this->assertFalse($entity->language(), 'No entity language has been specified.');
+    $this->assertFalse($entity->translations(), 'No translations are available');
+
+    // Set the value in default language.
+    $entity->set($this->field_name, array(0 => array('value' => 'default value')));
+    // Get the value.
+    $value = $entity->get($this->field_name);
+    $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value retrieved.');
+
+    // Set the value in a certain language. As the entity is not
+    // language-specific it should use the default language and so ignore the
+    // specified language.
+    $entity->set($this->field_name, array(0 => array('value' => 'default value2')), $this->langcodes[1]);
+    $value = $entity->get($this->field_name);
+    $this->assertEqual($value, array(0 => array('value' => 'default value2')), 'Untranslated value updated.');
+    $this->assertFalse($entity->translations(), 'No translations are available');
+
+    // Test getting a field value using the default language for a not
+    // language-specific entity.
+    $value = $entity->get($this->field_name, $this->langcodes[1]);
+    $this->assertEqual($value, array(0 => array('value' => 'default value2')), 'Untranslated value retrieved.');
+
+    // Now, make the entity language-specific by assigning a language and test
+    // translating it.
+    $entity->langcode = $this->langcodes[0];
+    $entity->{$this->field_name} = array();
+    $this->assertEqual($entity->language(), language_load($this->langcodes[0]), 'Entity language retrieved.');
+    $this->assertFalse($entity->translations(), 'No translations are available');
+
+    // Set the value in default language.
+    $entity->set($this->field_name, array(0 => array('value' => 'default value')));
+    // Get the value.
+    $value = $entity->get($this->field_name);
+    $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value retrieved.');
+
+    // Set a translation.
+    $entity->set($this->field_name, array(0 => array('value' => 'translation 1')), $this->langcodes[1]);
+    $value = $entity->get($this->field_name, $this->langcodes[1]);
+    $this->assertEqual($value, array(0 => array('value' => 'translation 1')), 'Translated value set.');
+    // Make sure the untranslated value stays.
+    $value = $entity->get($this->field_name);
+    $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value stays.');
+
+    $translations[$this->langcodes[1]] = language_load($this->langcodes[1]);
+    $this->assertEqual($entity->translations(), $translations, 'Translations retrieved.');
+
+    // Try to get a not available translation.
+    $value = $entity->get($this->field_name, $this->langcodes[2]);
+    $this->assertNull($value, 'A translation that is not available is NULL.');
+  }
 }
 
 /**
diff --git a/core/modules/entity/tests/entity_crud_hook_test.test b/core/modules/entity/tests/entity_crud_hook_test.test
index be59e99..12a311c 100644
--- a/core/modules/entity/tests/entity_crud_hook_test.test
+++ b/core/modules/entity/tests/entity_crud_hook_test.test
@@ -75,13 +75,11 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
       'changed' => REQUEST_TIME,
     );
     node_save($node);
-    $nid = $node->nid;
 
     $comment = entity_create('comment', array(
-      'cid' => NULL,
       'pid' => 0,
-      'nid' => $nid,
-      'uid' => 1,
+      'node' => $node,
+      'user' => 1,
       'subject' => 'Test comment',
       'created' => REQUEST_TIME,
       'changed' => REQUEST_TIME,
diff --git a/core/modules/entity/tests/entity_test.entity.inc b/core/modules/entity/tests/entity_test.entity.inc
new file mode 100644
index 0000000..e87c0d2
--- /dev/null
+++ b/core/modules/entity/tests/entity_test.entity.inc
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Entity information for the entity_test entity.
+ */
+
+/**
+ * Implements hook_entity_property_info().
+ */
+function entity_test_entity_property_info() {
+  $info = array();
+  $info['entity_test']['properties']['id'] = array(
+    'label' => t('Test ID'),
+    'description' => t('The unique ID of the test.'),
+    'type' => 'integer',
+  );
+  $info['entity_test']['properties']['id'] = array(
+    'label' => t('Test ID'),
+    'description' => t('The unique ID of the test entity.'),
+    'type' => 'integer',
+  );
+  $info['entity_test']['properties']['name'] = array(
+    'label' => t('Test name'),
+    'description' => t('The name of the test entity.'),
+    'type' => 'text',
+  );
+  $info['entity_test']['properties']['description'] = array(
+    'label' => t('Test description'),
+    'description' => t('The description of the test entity.'),
+    'type' => 'text',
+  );
+  $info['entity_test']['properties']['user'] = array(
+    'label' => t('Test entity user'),
+    'description' => t('The user of the test entity.'),
+    'type' => 'entity',
+    'entity type' => 'user',
+    'storage field' => 'user_id',
+  );
+  $info['entity_test']['properties']['comment'] = array(
+    'label' => t('Test entity comment'),
+    'description' => t('The comment of the test entity.'),
+    'type' => 'entity',
+    'entity type' => 'comment',
+    'storage field' => 'comment_id',
+  );
+  $info['entity_test']['properties']['langcode'] = array(
+    'label' => t('Test entity langcode'),
+    'description' => t('The langcode of the test entity.'),
+    'type' => 'token',
+  );
+  return $info;
+}
diff --git a/core/modules/entity/tests/entity_test.install b/core/modules/entity/tests/entity_test.install
index ec2e5bd..0cd3378 100644
--- a/core/modules/entity/tests/entity_test.install
+++ b/core/modules/entity/tests/entity_test.install
@@ -50,20 +50,41 @@ function entity_test_schema() {
         'not null' => TRUE,
         'default' => '',
       ),
-      'uid' => array(
+      'description' => array(
+        'description' => 'The description of the test entity.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'default' => '',
+      ),
+      'user_id' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => FALSE,
         'default' => NULL,
         'description' => "The {users}.uid of the associated user.",
       ),
-    ),
-    'indexes' => array(
-      'uid' => array('uid'),
+      'comment_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => FALSE,
+        'default' => NULL,
+        'description' => "The {comment}.cid of the associated comment.",
+      ),
+      'langcode' => array(
+        'description' => 'The {language}.langcode of the test entity.',
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
     ),
     'foreign keys' => array(
       'uid' => array('users' => 'uid'),
     ),
+    'foreign keys' => array(
+      'cid' => array('comment' => 'cid'),
+    ),
     'primary key' => array('id'),
   );
   return $schema;
diff --git a/core/modules/entity/tests/entity_test.module b/core/modules/entity/tests/entity_test.module
index 6034b06..f7dffa0 100644
--- a/core/modules/entity/tests/entity_test.module
+++ b/core/modules/entity/tests/entity_test.module
@@ -9,19 +9,21 @@
  * Implements hook_entity_info().
  */
 function entity_test_entity_info() {
-  $return = array(
-    'entity_test' => array(
-      'label' => t('Test entity'),
-      'entity class' => 'Entity',
-      'controller class' => 'EntityDatabaseStorageController',
-      'base table' => 'entity_test',
-      'fieldable' => TRUE,
-      'entity keys' => array(
-        'id' => 'id',
-      ),
+  $items['entity_test'] = array(
+    'label' => t('Test entity'),
+    'entity class' => 'Entity',
+    'controller class' => 'EntityDatabaseStorageController',
+    'base table' => 'entity_test',
+    'fieldable' => TRUE,
+    'entity keys' => array(
+      'id' => 'id',
     ),
   );
-  return $return;
+  // Optionally specify a translation handler for testing translations.
+  if (!empty($GLOBALS['entity_test_translation'])) {
+    $items['entity_test']['translation']['entity_test'] = TRUE;
+  }
+  return $items;
 }
 
 /**
diff --git a/core/modules/node/node.test b/core/modules/node/node.test
index 1dbcaf3..3608b6d 100644
--- a/core/modules/node/node.test
+++ b/core/modules/node/node.test
@@ -2454,7 +2454,7 @@ class NodeAccessPagerTestCase extends DrupalWebTestCase {
     // Create 60 comments.
     for ($i = 0; $i < 60; $i++) {
       $comment = entity_create('comment', array(
-        'nid' => $node->nid,
+        'node' => $node,
         'subject' => $this->randomName(),
         'comment_body' => array(
           LANGUAGE_NOT_SPECIFIED => array(
