diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
index ab94c27..527a336 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
@@ -69,9 +69,6 @@ public function __construct($definition) {
     // Always add a default 'uuid' key.
     $this->entity_keys['uuid'] = 'uuid';
     $this->entity_keys['langcode'] = 'langcode';
-    if (isset($this->handlers['storage'])) {
-      $this->checkStorageClass($this->handlers['storage']);
-    }
     $this->handlers += array(
       'storage' => 'Drupal\Core\Config\Entity\ConfigEntityStorage',
     );
@@ -135,23 +132,12 @@ public function getConfigDependencyKey() {
   /**
    * {@inheritdoc}
    *
+   * @see \Drupal\Core\Config\Entity\ConfigEntityStorage.
+   *
    * @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException
    *   Exception thrown when the provided class is not an instance of
    *   \Drupal\Core\Config\Entity\ConfigEntityStorage.
    */
-  public function setStorageClass($class) {
-    $this->checkStorageClass($class);
-    parent::setStorageClass($class);
-  }
-
-  /**
-   * Checks that the provided class is an instance of ConfigEntityStorage.
-   *
-   * @param string $class
-   *   The class to check.
-   *
-   * @see \Drupal\Core\Config\Entity\ConfigEntityStorage.
-   */
   protected function checkStorageClass($class) {
     if (!is_a($class, 'Drupal\Core\Config\Entity\ConfigEntityStorage', TRUE)) {
       throw new ConfigEntityStorageClassException("$class is not \\Drupal\\Core\\Config\\Entity\\ConfigEntityStorage or it does not extend it");
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 4f11f1e..1e198fe 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -811,6 +811,13 @@ public function hasTranslation($langcode) {
   /**
    * {@inheritdoc}
    */
+  public function isNewTranslation() {
+    return $this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_CREATED;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function addTranslation($langcode, array $values = array()) {
     // Make sure we do not attempt to create a translation if an invalid
     // language is specified or the entity cannot be translated.
@@ -822,37 +829,11 @@ public function addTranslation($langcode, array $values = array()) {
       throw new \InvalidArgumentException("The entity cannot be translated since it is language neutral ({$this->defaultLangcode}).");
     }
 
-    // Instantiate a new empty entity so default values will be populated in the
-    // specified language.
-    $entity_type = $this->getEntityType();
-
-    $default_values = array(
-      $entity_type->getKey('bundle') => $this->bundle(),
-      $this->langcodeKey => $langcode,
-    );
-    $entity = $this->entityManager()
-      ->getStorage($this->getEntityTypeId())
-      ->create($default_values);
-
-    foreach ($entity as $name => $field) {
-      if (!isset($values[$name]) && !$field->isEmpty()) {
-        $values[$name] = $field->getValue();
-      }
-    }
-    $values[$this->langcodeKey] = $langcode;
-    $values[$this->defaultLangcodeKey] = FALSE;
-
+    // Initialize the translation object.
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+    $storage = $this->entityManager()->getStorage($this->getEntityTypeId());
     $this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
-    $translation = $this->getTranslation($langcode);
-    $definitions = $translation->getFieldDefinitions();
-
-    foreach ($values as $name => $value) {
-      if (isset($definitions[$name]) && $definitions[$name]->isTranslatable()) {
-        $translation->values[$name][$langcode] = $value;
-      }
-    }
-
-    return $translation;
+    return $storage->createTranslation($this, $langcode, $values);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index 2a5475e..fb97c81 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -13,7 +13,10 @@
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
-abstract class ContentEntityStorageBase extends EntityStorageBase implements DynamicallyFieldableEntityStorageInterface {
+/**
+ * Base class for content entity storage handlers.
+ */
+abstract class ContentEntityStorageBase extends EntityStorageBase implements ContentEntityStorageInterface {
 
   /**
    * The entity bundle key.
@@ -87,13 +90,32 @@ protected function doCreate(array $values) {
       $bundle = $values[$this->bundleKey];
     }
     $entity = new $this->entityClass(array(), $this->entityTypeId, $bundle);
+    $this->initFieldValues($entity, $values);
+    return $entity;
+  }
 
+  /**
+   * Initializes field values.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   An entity object.
+   * @param array $values
+   *   (optional) An associative array of initial field values keyed by field
+   *   name. If none is provided default values will be applied.
+   * @param array $field_names
+   *   (optional) An associative array of field names to be initialized. If none
+   *   is provided all fields will be initialized.
+   */
+  protected function initFieldValues(ContentEntityInterface $entity, array $values = [], array $field_names = []) {
+    // Populate field values.
     foreach ($entity as $name => $field) {
-      if (isset($values[$name])) {
-        $entity->$name = $values[$name];
-      }
-      elseif (!array_key_exists($name, $values)) {
-        $entity->get($name)->applyDefaultValue();
+      if (!$field_names || isset($field_names[$name])) {
+        if (isset($values[$name])) {
+          $entity->$name = $values[$name];
+        }
+        elseif (!array_key_exists($name, $values)) {
+          $entity->get($name)->applyDefaultValue();
+        }
       }
       unset($values[$name]);
     }
@@ -102,7 +124,23 @@ protected function doCreate(array $values) {
     foreach ($values as $name => $value) {
       $entity->$name = $value;
     }
-    return $entity;
+
+    // Make sure modules can alter field initial values.
+    $this->invokeHook('field_values_init', $entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []) {
+    $translation = $entity->getTranslation($langcode);
+    $definitions = array_filter($translation->getFieldDefinitions(), function(FieldDefinitionInterface $definition) { return $definition->isTranslatable(); });
+    $field_names = array_map(function(FieldDefinitionInterface $definition) { return $definition->getName(); }, $definitions);
+    $values[$this->langcodeKey] = $langcode;
+    $values[$this->getEntityType()->getKey('default_langcode')] = FALSE;
+    $this->initFieldValues($translation, $values, $field_names);
+    $this->invokeHook('translation_create', $entity);
+    return $translation;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php
new file mode 100644
index 0000000..f62e0ac
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\ContentEntityStorageInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * A storage that supports content entity types.
+ */
+interface ContentEntityStorageInterface extends DynamicallyFieldableEntityStorageInterface {
+
+  /**
+   * Constructs a new entity translation object, without permanently saving it.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity object being translated.
+   * @param string $langcode
+   *   The translation language code.
+   * @param array $values
+   *   (optional) An associative array of initial field values keyed by field
+   *   name. If none is provided default values will be applied.
+   *
+   * @return \Drupal\Core\Entity\ContentEntityInterface
+   *   A new entity translation object.
+   */
+  public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []);
+
+}
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityType.php b/core/lib/Drupal/Core/Entity/ContentEntityType.php
index 0c375a9..013ca1d 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityType.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityType.php
@@ -30,4 +30,20 @@ public function getConfigDependencyKey() {
     return 'content';
   }
 
+  /**
+   * {@inheritdoc}
+   *
+   * @see \Drupal\Core\Entity\ContentEntityStorageInterface.
+   *
+   * @throws \InvalidArgumentException
+   *   If the provided class does not implement
+   *   \Drupal\Core\Entity\ContentEntityStorageInterface.
+   */
+  protected function checkStorageClass($class) {
+    $required_interface = ltrim(ContentEntityStorageInterface::class, '\\');
+    if (!is_a($class, $required_interface, TRUE)) {
+      throw new \InvalidArgumentException("$class does not implement $required_interface");
+    }
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index 7072ef5..d678623 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -276,6 +276,9 @@ public function __construct($definition) {
     $this->handlers += array(
       'access' => 'Drupal\Core\Entity\EntityAccessControlHandler',
     );
+    if (isset($this->handlers['storage'])) {
+      $this->checkStorageClass($this->handlers['storage']);
+    }
 
     // Automatically add the EntityChanged constraint if the entity type tracks
     // the changed time.
@@ -459,10 +462,21 @@ public function getStorageClass() {
    * {@inheritdoc}
    */
   public function setStorageClass($class) {
+    $this->checkStorageClass($class);
     $this->handlers['storage'] = $class;
   }
 
   /**
+   * Checks that the provided class is an instance of ConfigEntityStorage.
+   *
+   * @param string $class
+   *   The class to check.
+   */
+  protected function checkStorageClass($class) {
+    // Nothing to check by default.
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getFormClass($operation) {
diff --git a/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php
new file mode 100644
index 0000000..bbff156
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueContentEntityStorage.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\KeyValueStore\KeyValueContentEntityStorage.
+ */
+
+namespace Drupal\Core\Entity\KeyValueStore;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityStorageInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+
+/**
+ * Provides a key value backend for content entities.
+ */
+class KeyValueContentEntityStorage extends KeyValueEntityStorage implements ContentEntityStorageInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function countFieldData($storage_definition, $as_bool = FALSE) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasData() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function purgeFieldData(FieldDefinitionInterface $field_definition, $batch_size) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldDefinitionCreate(FieldDefinitionInterface $field_definition) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldDefinitionUpdate(FieldDefinitionInterface $field_definition, FieldDefinitionInterface $original) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []) {}
+
+}
diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php
index 077c484..9cfa229 100644
--- a/core/lib/Drupal/Core/Entity/entity.api.php
+++ b/core/lib/Drupal/Core/Entity/entity.api.php
@@ -780,10 +780,9 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
 }
 
 /**
- * Act on a newly created entity.
+ * Acts when creating a new entity.
  *
- * This hook runs after a new entity object has just been instantiated. It can
- * be used to set initial values, e.g. to provide defaults.
+ * This hook runs after a new entity object has just been instantiated.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
@@ -792,16 +791,13 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
  * @see hook_ENTITY_TYPE_create()
  */
 function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
-  if ($entity instanceof FieldableEntityInterface && !$entity->foo->value) {
-    $entity->foo->value = 'some_initial_value';
-  }
+  \Drupal::logger('example')->info('Entity created: @label', ['@label' => $entity->label()]);
 }
 
 /**
- * Act on a newly created entity of a specific type.
+ * Acts when creating a new entity of a specific type.
  *
- * This hook runs after a new entity object has just been instantiated. It can
- * be used to set initial values, e.g. to provide defaults.
+ * This hook runs after a new entity object has just been instantiated.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
@@ -810,9 +806,7 @@ function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
  * @see hook_entity_create()
  */
 function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) {
-  if (!$entity->foo->value) {
-    $entity->foo->value = 'some_initial_value';
-  }
+  \Drupal::logger('example')->info('ENTITY_TYPE created: @label', ['@label' => $entity->label()]);
 }
 
 /**
@@ -1012,6 +1006,38 @@ function hook_ENTITY_TYPE_update(Drupal\Core\Entity\EntityInterface $entity) {
 }
 
 /**
+ * Acts when creating a new entity translation.
+ *
+ * This hook runs after a new entity translation object has just been
+ * instantiated.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $translation
+ *   The entity object.
+ *
+ * @ingroup entity_crud
+ * @see hook_ENTITY_TYPE_translation_create()
+ */
+function hook_entity_translation_create(\Drupal\Core\Entity\EntityInterface $translation) {
+  \Drupal::logger('example')->info('Entity translation created: @label', ['@label' => $translation->label()]);
+}
+
+/**
+ * Acts when creating a new entity translation of a specific type.
+ *
+ * This hook runs after a new entity translation object has just been
+ * instantiated.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $translation
+ *   The entity object.
+ *
+ * @ingroup entity_crud
+ * @see hook_entity_translation_create()
+ */
+function hook_ENTITY_TYPE_translation_create(\Drupal\Core\Entity\EntityInterface $translation) {
+  \Drupal::logger('example')->info('ENTITY_TYPE translation created: @label', ['@label' => $translation->label()]);
+}
+
+/**
  * Respond to creation of a new entity translation.
  *
  * This hook runs once the entity translation has been stored. Note that hook
@@ -1887,6 +1913,44 @@ function hook_entity_field_access_alter(array &$grants, array $context) {
 }
 
 /**
+ * Acts when initializing a fieldable entity object.
+ *
+ * This hook runs after a new entity object or a new entity translation object
+ * has just been instantiated. It can be used to set initial values, e.g. to
+ * provide defaults.
+ *
+ * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+ *   The entity object.
+ *
+ * @ingroup entity_crud
+ * @see hook_ENTITY_TYPE_field_values_init()
+ */
+function hook_entity_field_values_init(\Drupal\Core\Entity\FieldableEntityInterface $entity) {
+  if ($entity instanceof \Drupal\Core\Entity\ContentEntityInterface && !$entity->foo->value) {
+    $entity->foo->value = 'some_initial_value';
+  }
+}
+
+/**
+ * Acts when initializing a fieldable entity object.
+ *
+ * This hook runs after a new entity object or a new entity translation object
+ * has just been instantiated. It can be used to set initial values, e.g. to
+ * provide defaults.
+ *
+ * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+ *   The entity object.
+ *
+ * @ingroup entity_crud
+ * @see hook_entity_field_values_init()
+ */
+function hook_ENTITY_TYPE_field_values_init(\Drupal\Core\Entity\FieldableEntityInterface $entity) {
+  if (!$entity->foo->value) {
+    $entity->foo->value = 'some_initial_value';
+  }
+}
+
+/**
  * Exposes "pseudo-field" components on content entities.
  *
  * Field UI's "Manage fields" and "Manage display" pages let users re-order
diff --git a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
index f87e7c8..f64242c 100644
--- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
+++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
@@ -29,6 +29,14 @@ public function language();
   public function isDefaultTranslation();
 
   /**
+   * Checks whether the translation is new.
+   *
+   * @return bool
+   *   TRUE if the translation is new, FALSE otherwise.
+   */
+  public function isNewTranslation();
+
+  /**
    * Returns the languages the data is translated to.
    *
    * @param bool $include_default
diff --git a/core/modules/system/src/Tests/Entity/EntityTranslationTest.php b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
index 07d0bd7..a2e9be4 100644
--- a/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
@@ -328,6 +328,7 @@ protected function doTestEntityTranslationAPI($entity_type) {
     // Verify that we obtain the entity object itself when we attempt to
     // retrieve a translation referring to it.
     $translation = $entity->getTranslation(LanguageInterface::LANGCODE_NOT_SPECIFIED);
+    $this->assertFalse($translation->isNewTranslation(), 'Existing translations are not marked as new.');
     $this->assertIdentical($entity, $translation, 'The translation object corresponding to a non-default language is the entity object itself when the entity is language-neutral.');
     $entity->{$langcode_key}->value = $default_langcode;
     $translation = $entity->getTranslation($default_langcode);
@@ -353,6 +354,7 @@ protected function doTestEntityTranslationAPI($entity_type) {
     $entity->name->value = $name;
     $name_translated = $langcode . '_' . $this->randomMachineName();
     $translation = $entity->addTranslation($langcode);
+    $this->assertTrue($translation->isNewTranslation(), 'Newly added translations are marked as new.');
     $this->assertNotIdentical($entity, $translation, 'The entity and the translation object differ from one another.');
     $this->assertTrue($entity->hasTranslation($langcode), 'The new translation exists.');
     $this->assertEqual($translation->language()->getId(), $langcode, 'The translation language matches the specified one.');
diff --git a/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module
index d8c3666..ed05996 100644
--- a/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module
+++ b/core/modules/system/tests/modules/keyvalue_test/keyvalue_test.module
@@ -11,7 +11,7 @@
 function keyvalue_test_entity_type_alter(array &$entity_types) {
   /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
   if (isset($entity_types['entity_test_label'])) {
-    $entity_types['entity_test_label']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage');
+    $entity_types['entity_test_label']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueContentEntityStorage');
     $entity_keys = $entity_types['entity_test_label']->getKeys();
     $entity_types['entity_test_label']->set('entity_keys', $entity_keys + array('uuid' => 'uuid'));
   }
