diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index cea6b2e..4136afb 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -19,11 +19,13 @@
  *
  * @param \Drupal\Core\Field\FieldDefinitionInterface $field
  *   A field definition object.
+ * @param string $element_name
+ *   The name that the element will have, used applying javascript.
  *
  * @return array
  *   A form element to configure field synchronization.
  */
-function content_translation_field_sync_widget(FieldDefinitionInterface $field) {
+function content_translation_field_sync_widget(FieldDefinitionInterface $field, $element_name = 'third_party_settings[content_translation][translation_sync]') {
   // No way to store field sync information on this field.
   if (!($field instanceof ThirdPartySettingsInterface)) {
     return array();
@@ -33,15 +35,18 @@ function content_translation_field_sync_widget(FieldDefinitionInterface $field)
   $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field->getType());
   $column_groups = $definition['column_groups'];
   if (!empty($column_groups) && count($column_groups) > 1) {
-    $options = array();
-    $default = array();
+    $options = [];
+    $default = [];
+    $require_all_groups_for_translation = [];
 
     foreach ($column_groups as $group => $info) {
       $options[$group] = $info['label'];
       $default[$group] = !empty($info['translatable']) ? $group : FALSE;
+      if (!empty($info['require_all_groups_for_translation'])) {
+        $require_all_groups_for_translation[] = $group;
+      }
     }
 
-    $settings = array('dependent_selectors' => array('instance[third_party_settings][content_translation][translation_sync]' => array('file')));
     $default = $field->getThirdPartySetting('content_translation', 'translation_sync', $default);
 
     $element = array(
@@ -49,15 +54,19 @@ function content_translation_field_sync_widget(FieldDefinitionInterface $field)
       '#title' => t('Translatable elements'),
       '#options' => $options,
       '#default_value' => $default,
-      '#attached' => array(
-        'library' => array(
-          'content_translation/drupal.content_translation.admin',
-        ),
-        'drupalSettings' => [
-          'contentTranslationDependentOptions' => $settings,
-        ],
-      ),
     );
+
+    if ($require_all_groups_for_translation) {
+      // The actual checkboxes are sometimes rendered separately and the parent
+      // element is ignored. Attach to the first option to ensure that this
+      // does not get lost.
+      $element[key($options)]['#attached']['drupalSettings']['contentTranslationDependentOptions'] = [
+        'dependent_selectors' => [
+          $element_name => $require_all_groups_for_translation
+        ],
+      ];
+      $element[key($options)]['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
+    }
   }
 
   return $element;
@@ -82,7 +91,6 @@ function _content_translation_form_language_content_settings_form_alter(array &$
 
   $form['#attached']['library'][] = 'content_translation/drupal.content_translation.admin';
 
-  $dependent_options_settings = array();
   $entity_manager = Drupal::entityManager();
   foreach ($form['#labels'] as $entity_type_id => $label) {
     $entity_type = $entity_manager->getDefinition($entity_type_id);
@@ -110,13 +118,9 @@ function _content_translation_form_language_content_settings_form_alter(array &$
               '#default_value' => $definition->isTranslatable(),
             );
             // Display the column translatability configuration widget.
-            $column_element = content_translation_field_sync_widget($definition);
+            $column_element = content_translation_field_sync_widget($definition, "settings[{$entity_type_id}][{$bundle}][columns][{$field_name}]");
             if ($column_element) {
               $form['settings'][$entity_type_id][$bundle]['columns'][$field_name] = $column_element;
-              // @todo This should not concern only files.
-              if (isset($column_element['#options']['file'])) {
-                $dependent_options_settings["settings[{$entity_type_id}][{$bundle}][columns][{$field_name}]"] = array('file');
-              }
             }
           }
         }
@@ -132,8 +136,6 @@ function _content_translation_form_language_content_settings_form_alter(array &$
     }
   }
 
-  $settings = array('dependent_selectors' => $dependent_options_settings);
-  $form['#attached']['drupalSettings']['contentTranslationDependentOptions'] = $settings;
   $form['#validate'][] = 'content_translation_form_language_content_settings_validate';
   $form['#submit'][] = 'content_translation_form_language_content_settings_submit';
 }
diff --git a/core/modules/content_translation/content_translation.admin.js b/core/modules/content_translation/content_translation.admin.js
index 9d5b882..fcbf699 100644
--- a/core/modules/content_translation/content_translation.admin.js
+++ b/core/modules/content_translation/content_translation.admin.js
@@ -31,7 +31,7 @@
       // We're given a generic name to look for so we find all inputs containing
       // that name and copy over the input values that require all columns to be
       // translatable.
-      if (options.dependent_selectors) {
+      if (options && options.dependent_selectors) {
         for (var field in options.dependent_selectors) {
           if (options.dependent_selectors.hasOwnProperty(field)) {
             $fields = $context.find('input[name^="' + field + '"]');
diff --git a/core/modules/content_translation/content_translation.install b/core/modules/content_translation/content_translation.install
index 79b76cf..c8c47d9 100644
--- a/core/modules/content_translation/content_translation.install
+++ b/core/modules/content_translation/content_translation.install
@@ -45,3 +45,19 @@ function content_translation_update_8001() {
 /**
  * @} End of "addtogroup updates-8.0.0-rc".
  */
+
+/**
+ * @addtogroup updates-8.0.x
+ * @{
+ */
+
+/**
+ * Clear field type plugin caches to fix image field translatability.
+ */
+function content_translation_update_8002() {
+  \Drupal::service('plugin.manager.field.field_type')->clearCachedDefinitions();
+}
+
+/**
+ * @} End of "addtogroup updates-8.0.x".
+ */
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 6e7bb70..f139178 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -14,6 +14,7 @@
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\field\FieldConfigInterface;
 
 /**
  * Implements hook_help().
@@ -199,9 +200,17 @@ function content_translation_entity_base_field_info(EntityTypeInterface $entity_
  *   which columns should be synchronized across different translations and
  *   which are translatable. This is useful for instance to translate the
  *   "alt" and "title" textual elements of an image field, while keeping the
- *   same image on every translation.
+ *   same image on every translation. Each group has the following keys:
+ *   - title: Title of the column group.
+ *   - translatable: (optional) If the column group should be translatable by
+ *     default, defaults to FALSE.
+ *   - columns: (optional) A list of columns of this group. Defaults to the
+ *     name of he group as the single column.
+ *   - require_all_groups_for_translation: (optional) Set to TRUE to enforce
+ *     that making this column group translatable requires all others to be
+ *     translatable too.
  *
- *   @see Drupal\image\Plugin\Field\FieldType\imageItem.
+ * @see Drupal\image\Plugin\Field\FieldType\ImageItem
  */
 function content_translation_field_info_alter(&$info) {
   foreach ($info as $key => $settings) {
diff --git a/core/modules/content_translation/src/FieldTranslationSynchronizer.php b/core/modules/content_translation/src/FieldTranslationSynchronizer.php
index 386f616..33c2cdc 100644
--- a/core/modules/content_translation/src/FieldTranslationSynchronizer.php
+++ b/core/modules/content_translation/src/FieldTranslationSynchronizer.php
@@ -66,6 +66,17 @@ public function synchronizeFields(ContentEntityInterface $entity, $sync_langcode
         // Retrieve all the untranslatable column groups and merge them into
         // single list.
         $groups = array_keys(array_diff($translation_sync, array_filter($translation_sync)));
+
+        // If a group was selected that has the
+        // require_all_groups_for_translation flag set, there are no
+        // untranslatable columns. This is done because the UI adds Javascript
+        // that disables the other checkboxes, so their values are not saved.
+        foreach (array_filter($translation_sync) as $group) {
+          if (!empty($column_groups[$group]['require_all_groups_for_translation'])) {
+            $groups = [];
+            break;
+          }
+        }
         if (!empty($groups)) {
           $columns = array();
           foreach ($groups as $group) {
@@ -163,7 +174,16 @@ public function synchronizeItems(array &$values, array $unchanged_items, $sync_l
             continue;
           }
           // If a synchronized column has changed or has been created from
-          // scratch we need to override the full items array for all languages.
+          // scratch we need to replace the values for this language as a
+          // combination of the values that need to be synced from the source
+          // items and the other columns from the existing values. This only
+          // works if the delta exists in the language.
+          elseif ($created && !empty($original_field_values[$langcode][$delta])) {
+            $item_columns_to_sync = array_intersect_key($source_items[$delta], array_flip($columns));
+            $item_columns_to_keep = array_diff_key($original_field_values[$langcode][$delta], array_flip($columns));
+            $values[$langcode][$delta] = $item_columns_to_sync + $item_columns_to_keep;
+          }
+          // If the delta doesn't exist, copy from the source language.
           elseif ($created) {
             $values[$langcode][$delta] = $source_items[$delta];
           }
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationSyncUnitTest.php b/core/modules/content_translation/src/Tests/ContentTranslationSyncUnitTest.php
index 31a5182..ab1e1cb 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationSyncUnitTest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationSyncUnitTest.php
@@ -201,7 +201,9 @@ function($delta) { return $delta === 0 || $delta === 3; },
       for ($delta = 0; $delta < $this->cardinality; $delta++) {
         if ($delta_callback($delta)) {
           foreach ($this->columns as $column) {
-            $field_values[$sync_langcode][$delta][$column] = $field_values[$sync_langcode][0][$column];
+            if (in_array($column, $this->synchronized)) {
+              $field_values[$sync_langcode][$delta][$column] = $field_values[$sync_langcode][0][$column];
+            }
           }
         }
       }
@@ -209,19 +211,18 @@ function($delta) { return $delta === 0 || $delta === 3; },
       $changed_items = $field_values[$sync_langcode];
       $this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
 
-      $result = TRUE;
       foreach ($this->unchangedFieldValues as $langcode => $unchanged_items) {
         for ($delta = 0; $delta < $this->cardinality; $delta++) {
           foreach ($this->columns as $column) {
             // The first item is always unchanged hence it is retained by the
             // synchronization process. The other ones are retained or synced
-            // depending on the logic implemented by the delta callback.
-            $value = $delta > 0 && $delta_callback($delta) ? $changed_items[0][$column] : $unchanged_items[$delta][$column];
-            $result = $result && ($field_values[$langcode][$delta][$column] == $value);
+            // depending on the logic implemented by the delta callback and
+            // whether it is a sync column or not.
+            $value = $delta > 0 && $delta_callback($delta) && in_array($column, $this->synchronized) ? $changed_items[0][$column] : $unchanged_items[$delta][$column];
+            $this->assertEqual($field_values[$langcode][$delta][$column], $value, "Item $delta column $column for langcode $langcode synced correctly");
           }
         }
       }
-      $this->assertTrue($result, 'Multiple synced items have been correctly synchronized.');
     }
   }
 
@@ -241,15 +242,16 @@ public function testDifferingSyncedColumns() {
     $changed_items = $field_values[$sync_langcode];
     $this->synchronizer->synchronizeItems($field_values, $unchanged_items, $sync_langcode, $this->langcodes, $this->synchronized);
 
-    $result = TRUE;
     foreach ($this->unchangedFieldValues as $langcode => $unchanged_items) {
       for ($delta = 0; $delta < $this->cardinality; $delta++) {
         foreach ($this->columns as $column) {
-          $result = $result && ($field_values[$langcode][$delta][$column] == $changed_items[$delta][$column]);
+          // If the column is synchronized, the value should have been synced,
+          // for unsychronized columns, the value must not change.
+          $expected_value = in_array($column, $this->synchronized) ? $changed_items[$delta][$column] : $this->unchangedFieldValues[$langcode][$delta][$column];
+          $this->assertEqual($field_values[$langcode][$delta][$column], $expected_value, "Differing Item $delta column $column for langcode $langcode synced correctly");
         }
       }
     }
-    $this->assertTrue($result, 'Differing synced columns have been correctly synchronized.');
   }
 
 }
diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
index 656c6e1..a867b9e 100644
--- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
+++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
@@ -31,7 +31,8 @@
  *       "label" = @Translation("File"),
  *       "columns" = {
  *         "target_id", "width", "height"
- *       }
+ *       },
+ *       "require_all_groups_for_translation" = TRUE
  *     },
  *     "alt" = {
  *       "label" = @Translation("Alt"),
diff --git a/core/modules/image/src/Tests/ImageFieldTestBase.php b/core/modules/image/src/Tests/ImageFieldTestBase.php
index 68c9aae..79148a3 100644
--- a/core/modules/image/src/Tests/ImageFieldTestBase.php
+++ b/core/modules/image/src/Tests/ImageFieldTestBase.php
@@ -159,4 +159,11 @@ function uploadNodeImage($image, $field_name, $type, $alt = '') {
     return isset($matches[1]) ? $matches[1] : FALSE;
   }
 
+  /**
+   * Retrieves the fid of the last inserted file.
+   */
+  protected function getLastFileId() {
+    return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
+  }
+
 }
diff --git a/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php b/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
new file mode 100644
index 0000000..e8d2f2a
--- /dev/null
+++ b/core/modules/image/src/Tests/ImageOnTranslatedEntityTest.php
@@ -0,0 +1,232 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Tests\ImageOnTranslatedEntityTest.
+ */
+
+namespace Drupal\image\Tests;
+
+use Drupal\file\Entity\File;
+
+/**
+ * Uploads images to translated nodes.
+ *
+ * @group image
+ */
+class ImageOnTranslatedEntityTest extends ImageFieldTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = array('language', 'content_translation', 'field_ui');
+
+  /**
+   * The name of the image field used in the test.
+   *
+   * @var string
+   */
+  protected $fieldName;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create the "Basic page" node type.
+    $this->drupalCreateContentType(array('type' => 'basicpage', 'name' => 'Basic page'));
+
+    // Create a image field on the "Basic page" node type.
+    $this->fieldName = strtolower($this->randomMachineName());
+    $this->createImageField($this->fieldName, 'basicpage', [], ['title_field' => 1]);
+
+    // Create and login user.
+    $permissions = array(
+      'access administration pages',
+      'administer content translation',
+      'administer content types',
+      'administer languages',
+      'administer node fields',
+      'create content translations',
+      'create basicpage content',
+      'edit any basicpage content',
+      'translate any entity',
+      'delete any basicpage content',
+    );
+    $admin_user = $this->drupalCreateUser($permissions);
+    $this->drupalLogin($admin_user);
+
+    // Add a second and third language.
+    $edit = array();
+    $edit['predefined_langcode'] = 'fr';
+    $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
+
+    $edit = array();
+    $edit['predefined_langcode'] = 'nl';
+    $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
+  }
+
+  /**
+   * Tests synced file fields on translated nodes.
+   */
+  public function testSyncedImages() {
+    // Enable translation for "Basic page" nodes.
+    $edit = array(
+      'entity_types[node]' => 1,
+      'settings[node][basicpage][translatable]' => 1,
+      "settings[node][basicpage][fields][$this->fieldName]" => 1,
+      "settings[node][basicpage][columns][$this->fieldName][file]" => 1,
+      // Explicitly disable alt and title since the javascript disables the
+      // checkboxes on the form.
+      "settings[node][basicpage][columns][$this->fieldName][alt]" => FALSE,
+      "settings[node][basicpage][columns][$this->fieldName][title]" => FALSE,
+    );
+    $this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
+
+    // Verify that the image field on the "Basic basic" node type is
+    // translatable.
+    $definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'basicpage');
+    $this->assertTrue($definitions[$this->fieldName]->isTranslatable(), 'Node image field is translatable.');
+
+    // Create a default language node.
+    $default_language_node = $this->drupalCreateNode(array('type' => 'basicpage', 'title' => 'Lost in translation'));
+
+    // Edit the node to upload a file.
+    $edit = array();
+    $name = 'files[' . $this->fieldName . '_0]';
+    $edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[0]->uri);
+    $this->drupalPostForm('node/' . $default_language_node->id() . '/edit', $edit, t('Save'));
+    $edit = [$this->fieldName . '[0][alt]' => 'Lost in translation image', $this->fieldName . '[0][title]' => 'Lost in translation image title'];
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $first_fid = $this->getLastFileId();
+
+    // Translate the node into French: remove the existing file.
+    $this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/fr', array(), t('Remove'));
+
+    // Upload a different file.
+    $edit = array();
+    $edit['title[0][value]'] = 'Scarlett Johansson';
+    $name = 'files[' . $this->fieldName . '_0]';
+    $edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[1]->uri);
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $edit = [$this->fieldName . '[0][alt]' => 'Scarlett Johansson image', $this->fieldName . '[0][title]' => 'Scarlett Johansson image title'];
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    // This inspects the HTML after the post of the translation, the image
+    // should be displayed on the original node.
+    $this->assertRaw('alt="Lost in translation image"');
+    $this->assertRaw('title="Lost in translation image title"');
+    $second_fid = $this->getLastFileId();
+    // View the translated node.
+    $this->drupalGet('fr/node/' . $default_language_node->id());
+    $this->assertRaw('alt="Scarlett Johansson image"');
+
+    \Drupal::entityTypeManager()->getStorage('file')->resetCache();
+
+    /* @var $file \Drupal\file\FileInterface */
+
+    // Ensure the file status of the first file permanent.
+    $file = File::load($first_fid);
+    $this->assertTrue($file->isPermanent());
+
+    // Ensure the file status of the second file is permanent.
+    $file = File::load($second_fid);
+    $this->assertTrue($file->isPermanent());
+
+    // Translate the node into dutch: remove the existing file.
+    $this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/nl', array(), t('Remove'));
+
+    // Upload a different file.
+    $edit = array();
+    $edit['title[0][value]'] = 'Akiko Takeshita';
+    $name = 'files[' . $this->fieldName . '_0]';
+    $edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[2]->uri);
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $edit = [$this->fieldName . '[0][alt]' => 'Akiko Takeshita image', $this->fieldName . '[0][title]' => 'Akiko Takeshita image title'];
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $third_fid = $this->getLastFileId();
+
+    \Drupal::entityTypeManager()->getStorage('file')->resetCache();
+
+    // Ensure the first file is untouched.
+    $file = File::load($first_fid);
+    $this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
+    // This inspects the HTML after the post of the translation, the image
+    // should be displayed on the original node.
+    $this->assertRaw('alt="Lost in translation image"');
+    $this->assertRaw('title="Lost in translation image title"');
+    // View the translated node.
+    $this->drupalGet('nl/node/' . $default_language_node->id());
+    $this->assertRaw('alt="Akiko Takeshita image"');
+    $this->assertRaw('title="Akiko Takeshita image title"');
+
+    // Ensure the file status of the second file is permanent.
+    $file = File::load($second_fid);
+    $this->assertTrue($file->isPermanent());
+
+    // Ensure the file status of the third file is permanent.
+    $file = File::load($third_fid);
+    $this->assertTrue($file->isPermanent());
+
+    // Edit the second translation: remove the existing file.
+    $this->drupalPostForm('fr/node/' . $default_language_node->id() . '/edit', array(), t('Remove'));
+
+    // Upload a different file.
+    $edit = array();
+    $edit['title[0][value]'] = 'Giovanni Ribisi';
+    $name = 'files[' . $this->fieldName . '_0]';
+    $edit[$name] = drupal_realpath($this->drupalGetTestFiles('image')[3]->uri);
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $name = $this->fieldName . '[0][alt]';
+
+    $edit = [$name => 'Giovanni Ribisi image'];
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $replaced_second_fid = $this->getLastFileId();
+
+    \Drupal::entityTypeManager()->getStorage('file')->resetCache();
+
+    // Ensure the first and third files are untouched.
+    $file = File::load($first_fid);
+    $this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
+
+    $file = File::load($third_fid);
+    $this->assertTrue($file->isPermanent());
+
+    // Ensure the file status of the replaced second file is permanent.
+    $file = File::load($replaced_second_fid);
+    $this->assertTrue($file->isPermanent());
+
+    // Ensure the file status of the old second file is now temporary.
+    $file = File::load($second_fid);
+    $this->assertTrue($file->isTemporary());
+
+    // Delete the third translation.
+    $this->drupalPostForm('nl/node/' . $default_language_node->id() . '/delete', array(), t('Delete Dutch translation'));
+
+    \Drupal::entityTypeManager()->getStorage('file')->resetCache();
+
+    // Ensure the first and replaced second files are untouched.
+    $file = File::load($first_fid);
+    $this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
+
+    $file = File::load($replaced_second_fid);
+    $this->assertTrue($file->isPermanent());
+
+    // Ensure the file status of the third file is now temporary.
+    $file = File::load($third_fid);
+    $this->assertTrue($file->isTemporary());
+
+    // Delete the all translations.
+    $this->drupalPostForm('node/' . $default_language_node->id() . '/delete', array(), t('Delete all translations'));
+
+    \Drupal::entityTypeManager()->getStorage('file')->resetCache();
+
+    // Ensure the file status of the all files are now temporary.
+    $file = File::load($first_fid);
+    $this->assertTrue($file->isTemporary(), 'First file still exists and is temporary.');
+
+    $file = File::load($replaced_second_fid);
+    $this->assertTrue($file->isTemporary());
+  }
+
+}
