diff --git a/tests/title.test b/tests/title.test
index e88eb52..99f2f0e 100644
--- a/tests/title.test
+++ b/tests/title.test
@@ -72,6 +72,7 @@ class TitleFieldReplacementTestCase extends DrupalWebTestCase {
     // Clear field cache so synchronization can be performed on field attach
     // load.
     cache_clear_all('*', 'cache_field');
+    drupal_static_reset();
 
     // Check that the replacing field value is correctly synchronized on load
     // and view.
@@ -203,6 +204,7 @@ class TitleAdminSettingsTestCase extends DrupalWebTestCase {
  * Tests for legacy field replacement.
  */
 class TitleTranslationTestCase extends DrupalWebTestCase {
+
   public static function getInfo() {
     return array(
       'name' => 'Replaced fields translation',
@@ -211,7 +213,7 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
     );
   }
 
-  function setUp() {
+  protected function setUp() {
     parent::setUp('locale', 'entity_translation', 'title');
 
     // Create a power user.
@@ -252,19 +254,105 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
       $t_args = array('%legacy_field' => $legacy_field);
       $this->assertTrue(field_info_instance($entity_type, $info['field']['field_name'], $name), t('The %legacy_field field has been correctly replaced.', $t_args));
     }
+
+    // Ensure static caches do not interfere with API calls.
+    drupal_static_reset();
   }
 
   /**
-   * Test taxonomy translation workflow.
+   * Tests taxonomy programmatic translation workflow.
    */
-  function testTranslationWorkflow() {
+  public function testProgrammaticTranslationWorkflow() {
+    // Create a taxonomy term and assign it an original language different from
+    // the default language.
+    $langcode = 'it';
+    $original_values = array(
+      'name' => $langcode . '_' . $this->randomName(),
+      'description' => $langcode . '_' . $this->randomName(),
+    );
+    $term = (object) ($original_values + array(
+      'format' => 'filtered_html',
+      'vocabulary_machine_name' => $this->vocabulary->machine_name,
+      'vid' => $this->vocabulary->vid,
+    ));
+    entity_translation_get_handler('taxonomy_term', $term)->setOriginalLanguage($langcode);
+    taxonomy_term_save($term);
+    $term = $this->termLoad($term->tid);
+    $this->assertTrue($this->checkFieldValues($term, $original_values, $langcode), 'Replacing field values correctly created from the legacy field values.');
+
+    // Create a translation using the default language.
+    $translation_langcode = language_default()->language;
+    $translation = array(
+      'language' => $translation_langcode,
+      'source' => $langcode,
+      'uid' => $this->loggedInUser->uid,
+      'status' => 1,
+      'translate' => 0,
+      'created' => REQUEST_TIME,
+      'changed' => REQUEST_TIME,
+    );
+    $translated_values = array(
+      'name' => $translation_langcode . '_' . $this->randomName(),
+      'description' => $translation_langcode . '_' . $this->randomName(),
+    );
+    entity_translation_get_handler('taxonomy_term', $term)->setTranslation($translation, $this->fieldValues($translated_values, $translation_langcode));
+    taxonomy_term_save($term);
+    $term = $this->termLoad($term->tid, $translation_langcode);
+    $this->assertTrue($this->checkFieldValues($term, $translated_values, $translation_langcode), 'Replacing field translations correctly created.');
+    $this->assertTrue($this->checkFieldValues($term, $original_values, $langcode, FALSE), 'Replacing field original values correctly preserved.');
+
+    // Delete the translation.
+    entity_translation_get_handler('taxonomy_term', $term)->removeTranslation($translation_langcode);
+    taxonomy_term_save($term);
+    $term = $this->termLoad($term->tid, $langcode);
+    $this->assertTrue($this->checkFieldValues($term, $original_values, $langcode), 'Replacing field translations correctly deleted.');
+
+    // Make the term language neutral.
+    entity_translation_get_handler('taxonomy_term', $term)->setOriginalLanguage(LANGUAGE_NONE);
+    foreach ($original_values as $name => $value) {
+      $field_name = $name . '_field';
+      $term->{$field_name}[LANGUAGE_NONE] = $term->{$field_name}[$langcode];
+      $term->{$field_name}[$langcode] = array();
+    }
+    taxonomy_term_save($term);
+    $term = $this->termLoad($term->tid);
+    $this->assertTrue($this->checkFieldValues($term, $original_values, LANGUAGE_NONE), 'Term original language correctly changed to the former translation language.');
+
+    // Change the term language to the former translation language.
+    entity_translation_get_handler('taxonomy_term', $term)->setOriginalLanguage($translation_langcode);
+    foreach ($original_values as $name => $value) {
+      $field_name = $name . '_field';
+      $term->{$field_name}[$translation_langcode] = $term->{$field_name}[LANGUAGE_NONE];
+      $term->{$field_name}[LANGUAGE_NONE] = array();
+    }
+    taxonomy_term_save($term);
+    $term = $this->termLoad($term->tid, $translation_langcode);
+    $this->assertTrue($this->checkFieldValues($term, $original_values, $translation_langcode), 'Term original language correctly changed to language neutral.');
+
+    // Make a replacing field untranslatable and change its value.
+    $field_name = 'name_field';
+    $field = field_info_field($field_name);
+    $field['translatable'] = FALSE;
+    field_update_field($field);
+    $original_values['name'] = LANGUAGE_NONE . '_' . $this->randomName();
+    $term->name = $original_values['name'];
+    taxonomy_term_save($term);
+    $term = $this->termLoad($term->tid);
+    $this->assertEqual($term->{$field_name}[LANGUAGE_NONE][0]['value'], $original_values['name'], 'Untranslatable replacing field on translatable entity correctly handled.');
+  }
+
+  /**
+   * Tests taxonomy form translation workflow.
+   */
+  public function testFormTranslationWorkflow() {
     // Create a taxonomy term and check that legacy fields are properly
     // populated.
     $original_values = array(
       'name' => $this->randomName(),
       'description' => $this->randomName(),
     );
-    $edit = $this->editValues($original_values, 'en');
+    $langcode = 'en';
+    $edit = $this->editValues($original_values, $langcode);
     $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', $edit, t('Save'));
     $term = current(entity_load('taxonomy_term', FALSE, array('name' => $original_values['name']), TRUE));
     $this->assertEqual($term->description, $original_values['description'], t('Taxonomy term created.'));
@@ -275,11 +363,12 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
       'name' => $this->randomName(),
       'description' => $this->randomName(),
     );
+    $translation_langcode = 'it';
     $edit = $this->editValues($translated_values, 'it');
-    $this->drupalPost('it/taxonomy/term/' . $term->tid . '/edit/add/en/it', $edit, t('Save'));
-    $term = current(entity_load('taxonomy_term', array($term->tid), array(), TRUE));
-    $this->assertTrue($this->checkFieldValues($term, $translated_values, 'it'), t('Taxonomy term translation created.'));
-    $this->assertTrue($this->checkFieldValues($term, $original_values, 'en'), t('Taxonomy term original values preserved.'));
+    $this->drupalPost($translation_langcode . '/taxonomy/term/' . $term->tid . '/edit/add/' . $langcode . '/' . $translation_langcode, $edit, t('Save'));
+    $term = $this->termLoad($term->tid);
+    $this->assertTrue($this->checkFieldValues($term, $translated_values, $translation_langcode, FALSE), t('Taxonomy term translation created.'));
+    $this->assertTrue($this->checkFieldValues($term, $original_values, $langcode), t('Taxonomy term original values preserved.'));
 
     // Check that legacy fields have the correct values.
     $this->assertEqual($term->name, $original_values['name'], t('Taxonomy term name correctly stored.'));
@@ -291,31 +380,59 @@ class TitleTranslationTestCase extends DrupalWebTestCase {
       'name' => $this->randomName(),
       'description' => $this->randomName(),
     );
-    $edit = $this->editValues($translated_values, 'it');
-    $this->drupalPost('it/taxonomy/term/' . $term->tid . '/edit/it', $edit, t('Save'));
-    $term = current(entity_load('taxonomy_term', array($term->tid), array(), TRUE));
-    $this->assertTrue($this->checkFieldValues($term, $translated_values, 'it'), t('Taxonomy term translation updated.'));
-    $this->assertTrue($this->checkFieldValues($term, $original_values, 'en'), t('Taxonomy term original values preserved.'));
+    $edit = $this->editValues($translated_values, $translation_langcode);
+    $this->drupalPost($translation_langcode . '/taxonomy/term/' . $term->tid . '/edit/' . $translation_langcode, $edit, t('Save'));
+    $term = $this->termLoad($term->tid);
+    $this->assertTrue($this->checkFieldValues($term, $translated_values, $translation_langcode, FALSE), t('Taxonomy term translation updated.'));
+    $this->assertTrue($this->checkFieldValues($term, $original_values, $langcode), t('Taxonomy term original values preserved.'));
 
     // Check that legacy fields have the correct values.
     $this->assertEqual($term->name, $original_values['name'], t('Taxonomy term name correctly stored.'));
     $this->assertEqual($term->description, $original_values['description'], t('Taxonomy term description correctly stored.'));
   }
 
+  /**
+   * Loads a term using the given language as active language.
+   */
+  protected function termLoad($tid, $langcode = NULL) {
+    drupal_static_reset();
+    title_active_language($langcode);
+    return current(entity_load('taxonomy_term', array($tid), array(), TRUE));
+  }
+
+  /**
+   * Returns the drupalPost() $edit array corresponding to the given values.
+   */
   protected function editValues($values, $langcode) {
     $edit = array();
-    foreach ($values as $key => $value) {
-      $edit["{$key}_field[{$langcode}][0][value]"] = $value;
+    foreach ($values as $name => $value) {
+      $edit["{$name}_field[{$langcode}][0][value]"] = $value;
     }
     return $edit;
   }
 
-  protected function checkFieldValues($term, $values, $langcode) {
-    foreach ($values as $key => $value) {
-      if ($term->{$key . '_field'}[$langcode][0]['value'] != $value) {
+  /**
+   * Returns the field values array corresponding to the given values.
+   */
+  protected function fieldValues($values, $langcode) {
+    $field_values = array();
+    foreach ($values as $name => $value) {
+      $field_values[$name . '_field'][$langcode][0]['value'] = $value;
+    }
+    return $field_values;
+  }
+
+  /**
+   * Checks that the field values and optionally the legacy ones match the given values.
+   */
+  protected function checkFieldValues($term, $values, $langcode, $legacy_match = TRUE) {
+    foreach ($values as $name => $value) {
+      $field_value = $term->{$name . '_field'}[$langcode][0]['value'];
+      if ($field_value != $value || ($legacy_match !== ($field_value == $term->{$name}))) {
         return FALSE;
       }
     }
     return TRUE;
   }
+
 }
diff --git a/title.module b/title.module
index 06b6b3a..8c3f8e4 100644
--- a/title.module
+++ b/title.module
@@ -33,6 +33,7 @@ function title_module_implements_alter(&$implementations, $hook) {
       // The following hook implementations should be executed as last ones.
       case 'entity_info_alter':
       case 'entity_presave':
+      case 'field_attach_presave':
         $implementations['title'] = $group;
         break;
 
@@ -140,7 +141,38 @@ function title_entity_label($entity, $type, $langcode = NULL) {
  * Implements hook_entity_presave().
  */
 function title_entity_presave($entity, $type) {
-  title_entity_sync($type, $entity, NULL, TRUE);
+  // Since in some cases only hook_field_attach_presave() is called but in
+  // normal cases hook_entity_presave() acts before hook_field_presave(), we
+  // need to act on both to ensure that legacy fields are propely handled.
+  title_field_attach_presave($type, $entity);
+}
+
+/**
+ * Implements hook_field_attach_presave().
+ */
+function title_field_attach_presave($entity_type, $entity) {
+  // If the current content language is the original language we need to update
+  // the replacing field value with legacy one, as modules are supposed to write
+  // in the legacy field. Instead if we have loaded a translation, we need to
+  // override the legacy field value with the original one, taken from the
+  // replacing field.
+  $langcode = title_entity_language($entity_type, $entity);
+  list($id, , ) = entity_extract_ids($entity_type, $entity);
+  $set = empty($id) || $langcode == LANGUAGE_NONE || $langcode == title_active_language();
+  // We need to ensure synchronization is always performed.
+  $sync = &drupal_static('title_entity_sync', array());
+  unset($sync[$entity_type][$id]);
+  title_entity_sync($entity_type, $entity, $langcode, $set);
+}
+
+/**
+ * Implements hook_field_attach_update().
+ */
+function title_field_attach_update($entity_type, $entity) {
+  // Immediately after saving the entity we need to ensure that the legacy field
+  // holds a value corresponding to the current active language, as it were
+  // just loaded.
+  title_entity_sync($entity_type, $entity);
 }
 
 /**
@@ -374,9 +406,12 @@ function title_field_replacement_init($entity_type, $bundle, $legacy_field, $ids
 function title_entity_sync($entity_type, &$entity, $langcode = NULL, $set = FALSE) {
   $sync = &drupal_static(__FUNCTION__, array());
   list($id, , $bundle) = entity_extract_ids($entity_type, $entity);
-  $langcode = field_valid_language($langcode, FALSE);
 
-  // We do not need to perform this more than once.
+  if (!isset($langcode)) {
+    $langcode = $set ? title_entity_language($entity_type, $entity) : title_active_language();
+  }
+
+  // We do not need to perform synchronization more than once.
   if (!empty($id) && !empty($sync[$entity_type][$id][$langcode][$set])) {
     return;
   }
@@ -405,8 +440,8 @@ function title_entity_sync($entity_type, &$entity, $langcode = NULL, $set = FALS
  *   The name of the legacy field to be replaced.
  * @param $field_name
  *   The regular field to use as source value.
- * @param $display
- *   Specifies if synchronization is being performed on display or on save.
+ * @param $info
+ *   Field replacement information for the given entity.
  * @param $langcode
  *   The field language to use for the source value.
  */
@@ -431,19 +466,46 @@ function title_field_sync_get($entity_type, $entity, $legacy_field, $info, $lang
  *   The name of the legacy field to be replaced.
  * @param $field_name
  *   The regular field to use as source value.
- * @param $display
- *   Specifies if synchronization is being performed on display or on save.
+ * @param $info
+ *   Field replacement information for the given entity.
  * @param $langcode
- *   The field language to use for the source value.
+ *   The field language to use for the target value.
  */
-function title_field_sync_set($entity_type, $entity, $legacy_field, $info) {
+function title_field_sync_set($entity_type, $entity, $legacy_field, $info, $langcode) {
   if (property_exists($entity, $legacy_field)) {
-    $langcode = title_entity_language($entity_type, $entity);
+    // Find out the actual language to use (field might be untranslatable).
+    $field = field_info_field($info['field']['field_name']);
+    $langcode = field_is_translatable($entity_type, $field) ? $langcode : LANGUAGE_NONE;
     $info['callbacks']['sync_set']($entity_type, $entity, $legacy_field, $info, $langcode);
   }
 }
 
 /**
+ * Returns and optionally stores the active language.
+ *
+ * @param string $langcode
+ *   (optional) The active language to be set. If none is provided the active
+ *   language is just returned.
+ *
+ * @return string
+ *   The active language code. Defaults to the current content language.
+ */
+function title_active_language($langcode = NULL) {
+  static $drupal_static_fast;
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['active_language'] = &drupal_static(__FUNCTION__);
+  }
+  $active_langcode = &$drupal_static_fast['active_language'];
+  if (isset($langcode)) {
+    $active_langcode = $langcode;
+  }
+  if (empty($active_langcode)) {
+    $active_langcode = $GLOBALS['language_content']->language;
+  }
+  return $active_langcode;
+}
+
+/**
  * Provide the original entity language.
  *
  * If a language property is defined for the current entity we synchronize the
@@ -642,10 +704,11 @@ function title_tokens_alter(array &$replacements, array $context) {
       list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
       $options = $context['options'];
 
-      $langcode = NULL;
-      if (isset($options['language'])) {
-        $langcode = $options['language']->language;
-      }
+      // Since Title tokens are mostly used in storage contexts we default to
+      // the current working language, that is the entity language. Modules
+      // using Title tokens in display contexts need to specify the current
+      // display language.
+      $langcode = isset($options['language']) ? $options['language']->language : entity_language($entity_type, $entity);
 
       if ($fr_info) {
         foreach ($fr_info as $legacy_field => $info) {
