diff --git a/entity_translation.admin.inc b/entity_translation.admin.inc
index ca24130..2275c67 100644
--- a/entity_translation.admin.inc
+++ b/entity_translation.admin.inc
@@ -358,6 +358,7 @@ function _entity_translation_label($entity_type, $entity, $langcode = NULL) {
  */
 function entity_translation_delete_confirm($form, $form_state, $entity_type, $entity, $langcode) {
   $handler = entity_translation_get_handler($entity_type, $entity);
+  $handler->setFormLanguage($langcode);
   $languages = language_list();
 
   $form = array(
diff --git a/entity_translation.info b/entity_translation.info
index 74ef491..e59a3ce 100644
--- a/entity_translation.info
+++ b/entity_translation.info
@@ -5,6 +5,7 @@ core = 7.x
 configure = admin/config/regional/entity_translation
 dependencies[] = locale (>7.14)
 
+files[] = includes/translation.handler_factory.inc
 files[] = includes/translation.handler.inc
 files[] = includes/translation.handler.comment.inc
 files[] = includes/translation.handler.node.inc
diff --git a/entity_translation.install b/entity_translation.install
index f93aa30..8b39013 100644
--- a/entity_translation.install
+++ b/entity_translation.install
@@ -254,3 +254,10 @@ function entity_translation_update_7003() {
 function entity_translation_update_7004() {
   entity_info_cache_clear();
 }
+
+/**
+ * Rebuild the class registry to pick up the translation handler factory class.
+ */
+function entity_translation_update_7005() {
+  registry_rebuild();
+}
diff --git a/entity_translation.module b/entity_translation.module
index cf10e94..a2f1344 100644
--- a/entity_translation.module
+++ b/entity_translation.module
@@ -1108,8 +1108,10 @@ function entity_translation_field_attach_delete($entity_type, $entity) {
  * Implementation of hook_field_attach_form().
  */
 function entity_translation_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
-  // Skip recursing into the source form.
-  if (empty($form['#entity_translation_source_form']) && ($handler = entity_translation_entity_form_get_handler($form, $form_state))) {
+  // Avoid recursing into the source form.
+  list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
+  if (empty($form['#entity_translation_source_form']) && entity_translation_enabled($entity_type, $bundle)) {
+    $handler = entity_translation_get_handler($entity_type, $entity);
     $langcode = !empty($langcode) ? $langcode : $handler->getLanguage();
     $form_langcode = $handler->getFormLanguage();
     $translations = $handler->getTranslations();
@@ -1126,8 +1128,6 @@ function entity_translation_field_attach_form($entity_type, $entity, &$form, &$f
     // with the correct form language and replace the field elements with the
     // correct ones.
     if ($update_langcode || ($source && !isset($translations->data[$form_langcode]) && isset($translations->data[$source]) && empty($form_state['rebuild']))) {
-      list(, , $bundle) = entity_extract_ids($entity_type, $entity);
-
       foreach (field_info_instances($entity_type, $bundle) as $instance) {
         $field_name = $instance['field_name'];
         $field = field_info_field($field_name);
@@ -1137,25 +1137,30 @@ function entity_translation_field_attach_form($entity_type, $entity, &$form, &$f
         // user can find the form items already populated with the source values
         // while the field form element holds the correct language information.
         if ($field['translatable']) {
-          $form[$field_name]['#field_name'] = $field_name;
-          $form[$field_name]['#source'] = $update_langcode ? $form_langcode : $source;
-          $form[$field_name]['#previous'] = NULL;
+          $element = &$form[$field_name];
+          $element['#entity_type'] = $entity_type;
+          $element['#entity'] = $entity;
+          $element['#entity_id'] = $id;
+          $element['#field_name'] = $field_name;
+          $element['#source'] = $update_langcode ? $form_langcode : $source;
+          $element['#previous'] = NULL;
+          $element['#form_parents'] = $form['#parents'];
 
           // If we are updating the form language we need to make sure that the
           // wrong language is unset and the right one is stored in the field
           // element (see entity_translation_prepare_element()).
           if ($update_langcode) {
-            $form[$field_name]['#previous'] = $form[$field_name]['#language'];
-            $form[$field_name]['#language'] = $form_langcode;
+            $element['#previous'] = $element['#language'];
+            $element['#language'] = $form_langcode;
           }
 
           // Swap default values during form processing to avoid recursion. We
           // try to act before any other callback so that the correct values are
           // already in place for them.
-          if (!isset($form[$field_name]['#process'])) {
-            $form[$field_name]['#process'] = array();
+          if (!isset($element['#process'])) {
+            $element['#process'] = array();
           }
-          array_unshift($form[$field_name]['#process'], 'entity_translation_prepare_element');
+          array_unshift($element['#process'], 'entity_translation_prepare_element');
         }
       }
     }
@@ -1189,18 +1194,31 @@ function entity_translation_field_attach_form($entity_type, $entity, &$form, &$f
  * Form element process callback.
  */
 function entity_translation_prepare_element($element, &$form_state) {
-  $source_form = &drupal_static(__FUNCTION__, array());
+  static $drupal_static_fast;
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['source_forms'] = &drupal_static(__FUNCTION__, array());
+  }
+
+  $source_forms = &$drupal_static_fast['source_forms'];
   $form = $form_state['complete form'];
   $build_id = $form['#build_id'];
   $source = $element['#source'];
-
-  if (!isset($source_form[$build_id][$source])) {
-    $source_form[$build_id][$source] = array('#entity_translation_source_form' => TRUE);
+  $entity_type = $element['#entity_type'];
+  $id = $element['#entity_id'];
+
+  // Key the source form cache per entity type and entity id to allow for
+  // multiple entities on the same entity form.
+  if (!isset($source_forms[$build_id][$source][$entity_type][$id])) {
+    $source_form = array(
+      '#entity_translation_source_form' => TRUE,
+      '#parents' => $element['#form_parents'],
+    );
     $source_form_state = $form_state;
-    $info = entity_translation_edit_form_info($form, $form_state);
-    field_attach_form($info['entity type'], $info['entity'], $source_form[$build_id][$source], $source_form_state, $source);
+    field_attach_form($entity_type, $element['#entity'], $source_form, $source_form_state, $source);
+    $source_forms[$build_id][$source][$entity_type][$id] = &$source_form;
   }
 
+  $source_form = &$source_forms[$build_id][$source][$entity_type][$id];
   $langcode = $element['#language'];
   $field_name = $element['#field_name'];
 
@@ -1208,8 +1226,8 @@ function entity_translation_prepare_element($element, &$form_state) {
   // language information from source to target language, this way the user can
   // find the form items already populated with the source values while the
   // field form element holds the correct language information.
-  if (isset($source_form[$build_id][$source][$field_name][$source])) {
-    $element[$langcode] = $source_form[$build_id][$source][$field_name][$source];
+  if (isset($source_form[$field_name][$source])) {
+    $element[$langcode] = $source_form[$field_name][$source];
     entity_translation_form_element_language_replace($element, $source, $langcode);
     unset($element[$element['#previous']]);
   }
@@ -1346,30 +1364,32 @@ function _entity_translation_element_title_append(&$element, $suffix) {
  * Implements hook_form_alter().
  */
 function entity_translation_form_alter(&$form, &$form_state) {
-  if ($handler = entity_translation_entity_form_get_handler($form, $form_state)) {
-    if (!$handler->isNewEntity()) {
-      $handler->entityForm($form, $form_state);
-      $translations = $handler->getTranslations();
-      $form_langcode = $handler->getFormLanguage();
-      if (!isset($translations->data[$form_langcode]) || count($translations->data) > 1) {
-        // Hide shared form elements if the user is not allowed to edit them.
-        $handler->entityFormSharedElements($form);
+  if ($info = entity_translation_edit_form_info($form, $form_state)) {
+    $handler = entity_translation_get_handler($info['entity type'], $info['entity']);
+    if (entity_translation_enabled($info['entity type'], $info['entity'])) {
+      if (!$handler->isNewEntity()) {
+        $handler->entityForm($form, $form_state);
+        $translations = $handler->getTranslations();
+        $form_langcode = $handler->getFormLanguage();
+        if (!isset($translations->data[$form_langcode]) || count($translations->data) > 1) {
+          // Hide shared form elements if the user is not allowed to edit them.
+          $handler->entityFormSharedElements($form);
+        }
       }
+      else {
+        $handler->entityFormLanguageWidget($form, $form_state);
+      }
+      // We need to process the posted form as early as possible to update the
+      // form language value.
+      array_unshift($form['#validate'], 'entity_translation_entity_form_validate');
     }
+    // We might have an entity form for an entity or a bundle not enabled for
+    // translation. In this case we might need to deal with entity and field
+    // languages anyway, since fields may be shared among different bundles and
+    // entity types.
     else {
       $handler->entityFormLanguageWidget($form, $form_state);
     }
-    // We need to process the posted form as early as possible to update the
-    // form language value.
-    array_unshift($form['#validate'], 'entity_translation_entity_form_validate');
-  }
-  // We might have an entity form for an entity or a bundle not enabled for
-  // translation. In this case we might need to deal with entity and field
-  // languages anyway, since fields may be shared among different bundles and
-  // entity types.
-  elseif ($info = entity_translation_edit_form_info($form, $form_state)) {
-    $handler = entity_translation_get_handler($info['entity type'], $info['entity']);
-    $handler->entityFormLanguageWidget($form, $form_state);
   }
 }
 
@@ -1413,16 +1433,22 @@ function entity_translation_entity_form_validate($form, &$form_state) {
 }
 
 /**
- * Submit handler for the entity language widget.
+ * Validation handler for the entity language widget.
  */
-function entity_translation_language_widget_submit($form, &$form_state) {
+function entity_translation_entity_form_language_update($form, &$form_state) {
   $handler = entity_translation_entity_form_get_handler($form, $form_state);
-  // On non-translatable entities, we need to handle just the entity and field
-  // language.
-  if (empty($handler) && ($info = entity_translation_edit_form_info($form, $form_state))) {
-    $handler = entity_translation_get_handler($info['entity type'], $info['entity']);
+  // Ensure the handler form and source languages match the actual ones. This is
+  // mainly needed when responding to an AJAX request where the languages cannot
+  // be set from the usual page callback.
+  if (!empty($form_state['entity_translation']['form_langcode'])) {
+    $handler->setFormLanguage($form_state['entity_translation']['form_langcode']);
+  }
+  // When responding to an AJAX request we should ignore any change in the
+  // language widget as it may alter the field language expected by the AJAX
+  // callback.
+  if (empty($form_state['triggering_element']['#ajax'])) {
+    $handler->entityFormLanguageWidgetSubmit($form, $form_state);
   }
-  $handler->entityFormLanguageWidgetSubmit($form, $form_state);
 }
 
 /**
@@ -1444,7 +1470,7 @@ function entity_translation_entity_form_submit($form, &$form_state) {
  * Mark translations as outdated if the submitted value is true.
  */
 function entity_translation_field_attach_submit($entity_type, $entity, $form, &$form_state) {
-  if ($handler = entity_translation_entity_form_get_handler($form, $form_state)) {
+  if (($handler = entity_translation_entity_form_get_handler($form, $form_state)) && entity_translation_enabled($entity_type, $entity)) {
     // Update the wrapped entity with the submitted values.
     $handler->setEntity($entity);
     $handler->entityFormSubmit($form, $form_state);
@@ -1668,32 +1694,7 @@ function entity_translation_settings($entity_type, $bundle) {
  *   A valid language code.
  */
 function entity_translation_language($entity_type, $entity) {
-  // If a form has been post, we need to check its state to verify if any form
-  // translation handler is stored there. This is mainly needed when responding
-  // to an AJAX request where the form language cannot be set from the page
-  // callback.
-  $handler = entity_translation_current_form_get_handler();
-
-  // Make sure we always have a translation handler instance available.
-  if (empty($handler)) {
-    $handler = entity_translation_get_handler($entity_type, $entity);
-  }
-  // If a translation handler associated to the current form is found, we need
-  // to update the wrapped entity. This way submitted values will be picked up.
-  // Other entities may be created or saved while submitting the current one,
-  // hence we need to check we are dealing with it.
-  elseif ($handler->isWrappedEntity($entity_type, $entity)) {
-    $langcode = $handler->getLanguage();
-    $handler->setEntity($entity);
-    $submitted_langcode = $handler->getLanguage();
-    // If the entity language has changed we are editing the original values. In
-    // this case we need to update the current form language with the submitted
-    // one.
-    if ($submitted_langcode != $langcode) {
-      $handler->setFormLanguage($submitted_langcode);
-    }
-  }
-
+  $handler = entity_translation_get_handler($entity_type, $entity);
   $langcode = $handler->getFormLanguage();
   return !empty($langcode) ? $langcode : $handler->getLanguage();
 }
@@ -1706,61 +1707,20 @@ function entity_translation_language($entity_type, $entity) {
  * @param $entity
  *   (optional) The entity to be translated. A bundle name may be passed to
  *   instantiate an empty entity.
- * @param $update
- *   (optional) Instances are statically cached: if this is TRUE the wrapped
- *   entity will be replaced by the passed one.
  *
  * @return EntityTranslationHandlerInterface
  *   A class implementing EntityTranslationHandlerInterface.
  */
-function entity_translation_get_handler($entity_type = NULL, $entity = NULL, $update = FALSE) {
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast['handlers'])) {
-    $drupal_static_fast['handlers'] = &drupal_static(__FUNCTION__, array());
+function entity_translation_get_handler($entity_type = NULL, $entity = NULL) {
+  if (class_exists('EntityTranslationHandlerFactory')) {
+    $factory = EntityTranslationHandlerFactory::getInstance();
+    return empty($entity) ? $factory->getLastHandler($entity_type) : $factory->getHandler($entity_type, $entity);
   }
-  $handlers = &$drupal_static_fast['handlers'];
-
-  // Workaround the lack of a context object.
-  if (empty($entity)) {
-    if (isset($handlers[$entity_type]['#current'])) {
-      return $handlers[$entity_type]['#current'];
-    }
-    elseif (empty($entity_type) && isset($handlers['#current']['#current'])) {
-      return $handlers['#current']['#current'];
-    }
-    else {
-      return NULL;
-    }
-  }
-  elseif (is_string($entity)) {
-    $entity = entity_create_stub_entity($entity_type, array(NULL, NULL, $entity));
-  }
-
-  list($entity_id) = entity_extract_ids($entity_type, $entity);
-
-  if (!isset($handlers[$entity_type][$entity_id])) {
+  // @todo BC layer. Remove before the first stable release.
+  elseif (!empty($entity_type) && is_object($entity)) {
     $entity_info = entity_get_info($entity_type);
-    $class = $entity_info['translation']['entity_translation']['class'];
-    // @todo remove fourth parameter once 3rd-party translation handlers have
-    // been fixed and no longer require the deprecated entity_id parameter.
-    $handler = new $class($entity_type, $entity_info, $entity, NULL);
-
-    // If the entity id is empty we cannot cache the translation handler
-    // instance.
-    if (empty($entity_id)) {
-      return $handler;
-    }
-    else {
-      $handlers[$entity_type][$entity_id] = $handler;
-    }
-  }
-  elseif ($update) {
-    $handlers[$entity_type][$entity_id]->setEntity($entity);
+    return new EntityTranslationDefaultHandler($entity_type, $entity_info, $entity);
   }
-
-  $handlers[$entity_type]['#current'] = $handlers[$entity_type][$entity_id];
-  $handlers['#current']['#current'] = $handlers[$entity_type][$entity_id];
-  return $handlers[$entity_type][$entity_id];
 }
 
 /**
@@ -1774,24 +1734,11 @@ function entity_translation_get_handler($entity_type = NULL, $entity = NULL, $up
  * @return EntityTranslationHandlerInterface
  *   A class implementing EntityTranslationHandlerInterface.
  */
-function entity_translation_entity_form_get_handler($form, &$form_state) {
+function entity_translation_entity_form_get_handler($form, $form_state) {
   $handler = FALSE;
-  $entity_type = isset($form['#entity_type']) && is_string($form['#entity_type']) ? $form['#entity_type'] : FALSE;
-
-  if ($entity_type) {
-    if (empty($form_state['storage']['entity_translation']['handler'][$entity_type])) {
-      if ($info = entity_translation_edit_form_info($form, $form_state)) {
-        if (entity_translation_enabled($info['entity type'], $info['entity'])) {
-          $handler = entity_translation_get_handler($info['entity type'], $info['entity']);
-          $form_state['storage']['entity_translation']['handler'][$info['entity type']] = $handler;
-        }
-      }
-    }
-    else {
-      $handler = $form_state['storage']['entity_translation']['handler'][$entity_type];
-    }
+  if ($info = entity_translation_edit_form_info($form, $form_state)) {
+    $handler = entity_translation_get_handler($info['entity type'], $info['entity']);
   }
-
   return $handler;
 }
 
@@ -1800,14 +1747,18 @@ function entity_translation_entity_form_get_handler($form, &$form_state) {
  *
  * @return EntityTranslationHandlerInterface
  *   A translation handler instance if available, FALSE oterwise.
+ *
+ * @deprecated This is no longer used and will be removed in the first stable
+ *   release.
  */
 function entity_translation_current_form_get_handler() {
   $handler = FALSE;
 
   if (!empty($_POST['form_build_id'])) {
     $form_state = form_state_defaults();
-    $form = form_get_cache($_POST['form_build_id'], $form_state);
-    $handler = entity_translation_entity_form_get_handler($form, $form_state);
+    if ($form = form_get_cache($_POST['form_build_id'], $form_state)) {
+      $handler = entity_translation_entity_form_get_handler($form, $form_state);
+    }
   }
 
   return $handler;
diff --git a/includes/translation.handler.inc b/includes/translation.handler.inc
index 9da2a71..6f414c6 100644
--- a/includes/translation.handler.inc
+++ b/includes/translation.handler.inc
@@ -15,6 +15,16 @@
 interface EntityTranslationHandlerInterface {
 
   /**
+   * Injects the translation handler factory.
+   */
+  public function setFactory(EntityTranslationHandlerFactory $factory);
+
+  /**
+   * Registers a child translation handler for the given entity.
+   */
+  public function addChild($entity_type, $entity);
+
+  /**
    * Loads the translation data into the wrapped entity.
    */
   public function loadTranslations();
@@ -302,6 +312,20 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
   protected $entityId;
   protected $bundle;
 
+  /**
+   * The translation handler factory.
+   *
+   * @var EntityTranslationHandlerFactory
+   */
+  protected $factory;
+
+  /**
+   * The translation handler hierarchy storage.
+   *
+   * @var array
+   */
+  protected $children = array();
+
   private $entityForm;
   private $translating;
   private $outdated;
@@ -395,6 +419,36 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
   }
 
   /**
+   * @see EntityTranslationHandlerInterface::setFactory()
+   */
+  public function setFactory(EntityTranslationHandlerFactory $factory) {
+    $this->factory = $factory;
+  }
+
+  /**
+   * @see EntityTranslationHandlerInterface::addChild()
+   */
+  public function addChild($entity_type, $entity) {
+    if (!empty($this->factory)) {
+      $handler = $this->factory->getHandler($entity_type, $entity);
+      $handler->setFormLanguage($this->getFormLanguage());
+      $handler->setSourceLanguage($this->getSourceLanguage());
+      // Avoid registering more than one child handler for each entity.
+      $hid = $this->factory->getHandlerId($entity_type, $entity);
+      $this->children[$hid] = $handler;
+    }
+  }
+
+  /**
+   * Proxies the specified method invocation to a child translation handler.
+   */
+  protected function notifyChildren($method, $args) {
+    foreach ($this->children as $handler) {
+      call_user_func_array(array($handler, $method), $args);
+    }
+  }
+
+  /**
    * @see EntityTranslationHandlerInterface::loadTranslations()
    */
   public function loadTranslations() {
@@ -742,6 +796,11 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
 
     // Update bundle and entity id properties.
     list($this->entityId, , $this->bundle) = entity_extract_ids($this->entityType, $this->entity);
+
+    // Initialize the handler id if needed.
+    if (!empty($this->factory)) {
+      $this->factory->getHandlerId($this->entityType, $entity);
+    }
   }
 
   /**
@@ -918,6 +977,7 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
    */
   public function setFormLanguage($langcode) {
     $this->formLanguage = $langcode;
+    $this->notifyChildren(__FUNCTION__, func_get_args());
   }
 
   /**
@@ -932,6 +992,7 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
    */
   public function setSourceLanguage($langcode) {
     $this->sourceLanguage = $langcode;
+    $this->notifyChildren(__FUNCTION__, func_get_args());
   }
 
   /**
@@ -970,12 +1031,16 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
     $languages = language_list();
     $access = user_access('translate any entity') || user_access("translate $this->entityType entities");
 
+    // Store contextual information in the form state.
+    $form_state['entity_translation']['form_langcode'] = $form_langcode;
+    $form_state['entity_translation']['source_langcode'] = $this->getSourceLanguage();
     // The only way to determine whether we are editing the original values is
     // comparing form language and entity language. Since a language change
     // might render impossible to make this check after form submission, we
     // store the related information here.
     $form_state['entity_translation']['is_translation'] = $is_translation;
 
+
     // Adjust page title to specify the current language being edited, if we
     // have at least one translation.
     if ($form_langcode != LANGUAGE_NONE && (!$no_translations || $new_translation)) {
@@ -1138,8 +1203,9 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
    * Either remove access or add a translatability clue depending on the current
    * user's "edit translation shared fields" permissions.
    */
-  public function entityFormSharedElements(&$element) {
-    static $ignored_types, $shared_labels, $access;
+  public function entityFormSharedElements(&$element, $access = NULL) {
+    static $ignored_types, $shared_labels;
+
     if (!isset($ignored_types)) {
       $ignored_types = array_flip(array('actions', 'value', 'hidden', 'vertical_tabs', 'token'));
     }
@@ -1152,7 +1218,7 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
 
     foreach (element_children($element) as $key) {
       if (!isset($element[$key]['#type'])) {
-        $this->entityFormSharedElements($element[$key]);
+        $this->entityFormSharedElements($element[$key], $access);
       }
       else {
         // Ignore non-widget form elements.
@@ -1179,7 +1245,7 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
   public function entityFormLanguageWidget(&$form, &$form_state) {
     if (entity_translation_enabled($this->entityType, $this->bundle)) {
       $is_new = $this->isNewEntity();
-      $is_translation = !$is_new && !empty($form_state['entity_translation']['is_translation']);
+      $is_translation = !empty($form_state['entity_translation']['is_translation']);
       $translations = $this->getTranslations();
       $settings = entity_translation_settings($this->entityType, $this->bundle);
       $languages = entity_translation_languages($this->entityType, $this->entity);
@@ -1187,8 +1253,8 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
 
       foreach ($languages as $langcode => $language) {
         // Disable languages for existing translations, so it is not possible to
-        // switch this entity to some language which is already in the translation
-        // set.
+        // switch this entity to some language which is already in the
+        // translation set.
         if (!isset($translations->data[$langcode]) || empty($translations->data[$langcode]['source'])) {
           $options[$langcode] = t($language->name);
         }
@@ -1213,17 +1279,14 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
       }
     }
 
-    if (!empty($form['actions']['submit']['#submit'])) {
-      $submit = &$form['actions']['submit']['#submit'];
-    }
-    else {
-      if (!isset($form['#submit'])) {
-        $form['#submit'] = array();
-      }
-      $submit = &$form['#submit'];
-    }
-
-    array_unshift($submit, 'entity_translation_language_widget_submit');
+    // Prepend an empty form element to the form array so that we can update the
+    // form language before any other form handler has been called.
+    $form = array(
+      'entity_translation_entity_form_language_update' => array(
+        '#element_validate' => array('entity_translation_entity_form_language_update'),
+        '#entity_type' => $this->entityType,
+       ),
+    ) + $form;
   }
 
   /**
@@ -1256,28 +1319,32 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
   protected function updateFormLanguage($form_state) {
     // Update the form language as it might have changed. We exploit the
     // validation phase to be sure to act as early as possible.
-    if (isset($form_state['values']['language']) && !$this->isTranslationForm()) {
-      $this->setFormLanguage($form_state['values'][$this->getLanguageKey()]);
+    $language_key = $this->getLanguageKey();
+    if (isset($form_state['values'][$language_key]) && !$this->isTranslationForm()) {
+      $langcode = $form_state['values'][$language_key];
+      $this->setFormLanguage($langcode);
     }
   }
 
   /**
    * @see EntityTranslationHandlerInterface::entityFormLanguageWidgetSubmit()
    */
-  function entityFormLanguageWidgetSubmit($form, &$form_state) {
+  public function entityFormLanguageWidgetSubmit($form, &$form_state) {
     $this->updateFormLanguage($form_state);
     $form_langcode = $this->getFormLanguage();
 
     foreach (field_info_instances($this->entityType, $this->bundle) as $instance) {
       $field_name = $instance['field_name'];
-      $field = field_info_field($field_name);
-      $previous_langcode = $form[$field_name]['#language'];
+      if (isset($form[$field_name]['#language'])) {
+        $field = field_info_field($field_name);
+        $previous_langcode = $form[$field_name]['#language'];
 
-      // Handle a possible language change: new language values are inserted,
-      // previous ones are deleted.
-      if ($field['translatable'] && $previous_langcode != $form_langcode) {
-        $form_state['values'][$field_name][$form_langcode] = $form_state['values'][$field_name][$previous_langcode];
-        $form_state['values'][$field_name][$previous_langcode] = array();
+        // Handle a possible language change: new language values are inserted,
+        // previous ones are deleted.
+        if ($field['translatable'] && $previous_langcode != $form_langcode && isset($form_state['values'][$field_name][$previous_langcode])) {
+          $form_state['values'][$field_name][$form_langcode] = $form_state['values'][$field_name][$previous_langcode];
+          $form_state['values'][$field_name][$previous_langcode] = array();
+        }
       }
     }
   }
diff --git a/includes/translation.handler_factory.inc b/includes/translation.handler_factory.inc
new file mode 100644
index 0000000..f7a252d
--- /dev/null
+++ b/includes/translation.handler_factory.inc
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Translation handler factory for the Entity Translation module.
+ */
+
+/**
+ * Class implementing the entity translation handler factory.
+ */
+class EntityTranslationHandlerFactory {
+
+  /**
+   * A singleton instance of the factory.
+   *
+   * @var EntityTranslationHandlerFactory
+   */
+  protected static $instance;
+
+  /**
+   * Counter used to generate handler ids for new entities.
+   *
+   * @var int
+   */
+  protected static $newId = 1;
+
+  /**
+   * Handlers cache.
+   *
+   * @var array
+   */
+  protected $handlers = array();
+
+  /**
+   * The last translation handler retrieved.
+   *
+   * @var EntityTranslationHandlerInterface
+   */
+  protected $last;
+
+  /**
+   * An array of translation handler retrieved by type.
+   *
+   * @var array
+   */
+  protected $lastByType = array();
+
+  /**
+   * Returns the singleton instance of the translation handler factory.
+   *
+   * @return EntityTranslationHandlerFactory
+   */
+  public static function getInstance() {
+    if (!isset(self::$instance)) {
+      self::$instance = new EntityTranslationHandlerFactory();
+    }
+    return self::$instance;
+  }
+
+  /**
+   * Prevents the factory from being publicly instantiated.
+   */
+  protected function __construct() {}
+
+  /**
+   * Translation handler factory.
+   *
+   * @param $entity_type
+   *   The type of $entity; e.g. 'node' or 'user'.
+   * @param $entity
+   *   The entity to be translated. A bundle name may be passed to instantiate
+   *   an empty entity.
+   *
+   * @return EntityTranslationHandlerInterface
+   *   A class implementing EntityTranslationHandlerInterface.
+   */
+  public function getHandler($entity_type, $entity) {
+    if (is_string($entity)) {
+      $entity = entity_create_stub_entity($entity_type, array(NULL, NULL, $entity));
+    }
+
+    $id = $this->getHandlerId($entity_type, $entity);
+    if (!isset($this->handlers[$entity_type][$id])) {
+      $entity_info = entity_get_info($entity_type);
+      $class = $entity_info['translation']['entity_translation']['class'];
+      // @todo Remove the fourth parameter once 3rd-party translation handlers
+      //   have been fixed and no longer require the deprecated entity_id
+      //   parameter.
+      $handler = new $class($entity_type, $entity_info, $entity, NULL);
+      $handler->setFactory($this);
+      $this->handlers[$entity_type][$id] = $handler;
+    }
+
+    $this->last = $this->handlers[$entity_type][$id];
+    $this->lastByType[$entity_type] = $this->last;
+    $this->last->setEntity($entity);
+
+    return $this->last;
+  }
+
+  /**
+   * Retrieves the translation handler identifier for the given entity.
+   *
+   * @param $entity_type
+   *   The type of the entity the translation handler will wrap.
+   * @param $entity
+   *   The entity the translation handler will wrap.
+   */
+  public function getHandlerId($entity_type, $entity) {
+    if (!isset($entity->entity_translation_handler_id)) {
+      list($id, , ) = entity_extract_ids($entity_type, $entity);
+      $entity->entity_translation_handler_id = $entity_type . '-' . (!empty($id) ? 'eid-' . $id : 'new-' . self::$newId++);
+    }
+    return $entity->entity_translation_handler_id;
+  }
+
+  /**
+   * Returns the last translation handler retrieved.
+   *
+   * @param $entity_type
+   *   (optional) The entity type of the translation handler. Defaults to the
+   *   last one.
+   *
+   * @return EntityTranslationHandlerInterface
+   *   A class implementing EntityTranslationHandlerInterface.
+   */
+  public function getLastHandler($entity_type = NULL) {
+    return isset($this->lastByType[$entity_type]) ? $this->lastByType[$entity_type] : $this->last;
+  }
+
+}
diff --git a/tests/entity_translation.test b/tests/entity_translation.test
index 6a1bdc4..f440b4b 100644
--- a/tests/entity_translation.test
+++ b/tests/entity_translation.test
@@ -477,7 +477,7 @@ class EntityTranslationHookTestCase extends EntityTranslationTestCase {
     $handler->setTranslation($translation);
     $handler->saveTranslations();
     $node = node_load($node->nid, NULL, TRUE);
-    $handler = entity_translation_get_handler('node', $node, TRUE);
+    $handler = entity_translation_get_handler('node', $node);
     $translations = $handler->getTranslations();
     $this->assertTrue(!empty($translations->data['it']), t('An Italian translation has been created'));
     $info = $this->getHookInfo();
@@ -497,7 +497,7 @@ class EntityTranslationHookTestCase extends EntityTranslationTestCase {
     $handler->setTranslation($translation);
     $handler->saveTranslations();
     $node = node_load($node->nid, NULL, TRUE);
-    $handler = entity_translation_get_handler('node', $node, TRUE);
+    $handler = entity_translation_get_handler('node', $node);
     $translations = $handler->getTranslations();
     $this->assertTrue(!empty($translations->data['es']), t('A Spanish translation has been created'));
     $info = $this->getHookInfo();
