diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index 73db361..cf61d42 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -662,12 +662,9 @@ field.field_settings.integer:
     max:
       type: integer
       label: 'Maximum'
-    prefix:
+    format_plural_string:
       type: label
-      label: 'Prefix'
-    suffix:
-      type: label
-      label: 'Suffix'
+      label: 'Plural formatted prefix/suffix'
 
 field.value.integer:
   type: mapping
@@ -700,12 +697,9 @@ field.field_settings.decimal:
     max:
       type: float
       label: 'Maximum'
-    prefix:
-      type: label
-      label: 'Prefix'
-    suffix:
+    format_plural_string:
       type: label
-      label: 'Suffix'
+      label: 'Plural formatted prefix/suffix'
 
 field.value.decimal:
    type: mapping
@@ -731,12 +725,9 @@ field.field_settings.float:
     max:
       type: float
       label: 'Maximum'
-    prefix:
-      type: label
-      label: 'Prefix'
-    suffix:
+    format_plural_string:
       type: label
-      label: 'Suffix'
+      label: 'Plural formatted prefix/suffix'
 
 field.value.float:
   type: mapping
diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index bf0e12d..5fc710e 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -205,6 +205,9 @@ field.widget.settings.number:
     placeholder:
       type: label
       label: 'Placeholder'
+    format_plural:
+      type: boolean
+      label: 'Whether or not to display prefix/suffix'
 
 field.widget.settings.checkbox:
   type: mapping
@@ -282,9 +285,12 @@ field.formatter.settings.number_decimal:
     scale:
       type: integer
       label: 'Scale'
-    prefix_suffix:
-      type: boolean
-      label: 'Display prefix and suffix.'
+    format_plural:
+      type: string
+      label: 'Which prefix/suffix to display, if any'
+    format_plural_string:
+      type: label
+      label: 'Override for prefix/suffix'
 
 field.formatter.settings.number_integer:
   type: mapping
@@ -293,9 +299,12 @@ field.formatter.settings.number_integer:
     thousand_separator:
       type: string
       label: 'Thousand marker'
-    prefix_suffix:
-      type: boolean
-      label: 'Display prefix and suffix.'
+    format_plural:
+      type: string
+      label: 'Which prefix/suffix to display, if any'
+    format_plural_string:
+      type: label
+      label: 'Override for prefix/suffix'
 
 field.formatter.settings.number_unformatted:
   type: mapping
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
index b2f64df..37999e5 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
@@ -136,6 +136,11 @@ public function getRenderer($field_name) {
 
     // Instantiate the widget object from the stored display properties.
     if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
+      // Pass the language of the display configuration down to the widget,
+      // which otherwise doesn't have access to it because it is not a
+      // stand-alone config entity. It may be relevant for some operations,
+      // such as using plural formatting as a field prefix.
+      $configuration['settings']['_settings_langcode'] = $this->langcode;
       $widget = $this->pluginManager->getInstance(array(
         'field_definition' => $definition,
         'form_mode' => $this->originalMode,
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
index b5fab53..029443d 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
@@ -193,6 +193,11 @@ public function getRenderer($field_name) {
 
     // Instantiate the formatter object from the stored display properties.
     if (($configuration = $this->getComponent($field_name)) && isset($configuration['type']) && ($definition = $this->getFieldDefinition($field_name))) {
+      // Pass the language of the display configuration down to the formatter,
+      // which otherwise doesn't have access to it because it is not a
+      // stand-alone config entity. It may be relevant for some operations,
+      // such as plural formatting.
+      $configuration['settings']['_settings_langcode'] = $this->langcode;
       $formatter = $this->pluginManager->getInstance(array(
         'field_definition' => $definition,
         'view_mode' => $this->originalMode,
diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
index 4de891a..25910bf 100644
--- a/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityDisplayBase.php
@@ -287,6 +287,11 @@ public function toArray() {
       if (!$definition->isDisplayConfigurable($this->displayContext)) {
         unset($properties['content'][$field_name]);
         unset($properties['hidden'][$field_name]);
+        // If _settings_langcode is in the settings, remove it. It should never
+        // be saved in the configuration.
+        if (isset($properties['content'][$field_name]['settings']['_settings_langcode'])) {
+          unset($properties['content'][$field_name]['settings']['_settings_langcode']);
+        }
       }
     }
 
@@ -334,6 +339,12 @@ public function setComponent($name, array $options = array()) {
     // Ensure we always have an empty settings and array.
     $options += ['settings' => [], 'third_party_settings' => []];
 
+    // If _settings_langcode is in the settings, remove it. It should never
+    // be saved in the configuration.
+    if (isset($options['settings']['_settings_langcode'])) {
+      unset($options['settings']['_settings_langcode']);
+    }
+
     $this->content[$name] = $options;
     unset($this->hidden[$name]);
     unset($this->plugins[$name]);
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 8bfb3a3..aa9f0e1 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Field\FieldItemInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\TypedData\TranslatableInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -448,10 +449,21 @@ protected function getSingleFieldDisplay($entity, $field_name, $display_options)
       $bundle = $entity->bundle();
       $key = $entity_type_id . ':' . $bundle . ':' . $field_name . ':' . hash('crc32b', serialize($display_options));
       if (!isset($this->singleFieldDisplays[$key])) {
+        // Assign the incoming '_settings_langcode' as the langcode of the
+        // EntityViewDisplay, or default to the current interface language.
+        if (isset($display_options['settings']['_settings_langcode'])) {
+          $langcode = $display_options['settings']['_settings_langcode'];
+          unset($display_options['settings']['_settings_langcode']);
+        }
+        else {
+          $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE)->getId();
+        }
+
         $this->singleFieldDisplays[$key] = EntityViewDisplay::create(array(
           'targetEntityType' => $entity_type_id,
           'bundle' => $bundle,
           'status' => TRUE,
+          'langcode' => $langcode,
         ))->setComponent($field_name, $display_options);
       }
       $display = $this->singleFieldDisplays[$key];
diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
index b86b592..efb8cb8 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php
@@ -113,7 +113,11 @@ public function resetCache(array $entities = NULL);
    *       'default_formatter' for the field type. The default formatter will
    *       also be used if the requested formatter is not available.
    *     - settings: (array) Settings specific to the formatter. Defaults to the
-   *       formatter's default settings.
+   *       formatter's default settings. A '_settings_langcode' entry should be
+   *       present to indicate the language of the settings that are being
+   *       provided (typically, the language of the configuration entity that
+   *       contains the formatter, such as an entity view mode). If absent, the
+   *       current interface language will be assumed.
    *     - weight: (float) The weight to assign to the renderable element.
    *       Defaults to 0.
    *
diff --git a/core/lib/Drupal/Core/Field/FormatterPluginManager.php b/core/lib/Drupal/Core/Field/FormatterPluginManager.php
index ca9bd06..f14082b 100644
--- a/core/lib/Drupal/Core/Field/FormatterPluginManager.php
+++ b/core/lib/Drupal/Core/Field/FormatterPluginManager.php
@@ -68,7 +68,7 @@ public function createInstance($plugin_id, array $configuration = array()) {
   }
 
   /**
-   * Overrides PluginManagerBase::getInstance().
+   * Creates a formatter instance with the provided options.
    *
    * @param array $options
    *   An array with the following key/value pairs:
@@ -87,6 +87,11 @@ public function createInstance($plugin_id, array $configuration = array()) {
    *       also be used if the requested formatter is not available.
    *     - settings: (array) Settings specific to the formatter. Each setting
    *       defaults to the default value specified in the formatter definition.
+   *       A '_settings_langcode' entry should be present to indicate the
+   *       language of the settings that are being provided (typically, the
+   *       language of the configuration entity that contains the formatter,
+   *       such as an entity view mode). If absent, the current interface
+   *       language will be assumed.
    *     - third_party_settings: (array) Settings provided by other extensions
    *       through hook_field_formatter_third_party_settings_form().
    *
@@ -111,7 +116,7 @@ public function getInstance(array $options) {
     // - the formatter is not applicable to the field definition.
     $definition = $this->getDefinition($configuration['type'], FALSE);
     if (!isset($definition['class']) || !in_array($field_type, $definition['field_types']) || !$definition['class']::isApplicable($field_definition)) {
-      // Grab the default widget for the field type.
+      // Grab the default formatter for the field type.
       $field_type_definition = $this->fieldTypeManager->getDefinition($field_type);
       if (empty($field_type_definition['default_formatter'])) {
         return NULL;
@@ -123,6 +128,7 @@ public function getInstance(array $options) {
       'field_definition' => $field_definition,
       'view_mode' => $options['view_mode'],
     );
+
     return $this->createInstance($plugin_id, $configuration);
   }
 
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php
index 0c38929..a8fe2ef 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/DecimalFormatter.php
@@ -27,10 +27,8 @@ class DecimalFormatter extends NumericFormatterBase {
    */
   public static function defaultSettings() {
     return array(
-      'thousand_separator' => '',
       'decimal_separator' => '.',
       'scale' => 2,
-      'prefix_suffix' => TRUE,
     ) + parent::defaultSettings();
   }
 
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/IntegerFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/IntegerFormatter.php
index 17ddf9c..4ea5d52 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/IntegerFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/IntegerFormatter.php
@@ -22,16 +22,6 @@ class IntegerFormatter extends NumericFormatterBase {
   /**
    * {@inheritdoc}
    */
-  public static function defaultSettings() {
-    return array(
-      'thousand_separator' => '',
-      'prefix_suffix' => TRUE,
-    ) + parent::defaultSettings();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   protected function numberFormat($number) {
     return number_format($number, 0, '', $this->getSetting('thousand_separator'));
   }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php
index 0e35629..be70b10 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/NumericFormatterBase.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Field\FormatterBase;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
 
 /**
  * Parent plugin for decimal and integer formatters.
@@ -15,6 +16,32 @@
   use AllowedTagsXssTrait;
 
   /**
+   * Indicates plural formatting using the setting from the field.
+   */
+  const FORMAT_PLURAL_USE_FIELD_SETTING = 'field';
+
+  /**
+   * Indicates plural formatting using the setting from the formatter.
+   */
+  const FORMAT_PLURAL_USE_FORMATTER_SETTING = 'formatter';
+
+  /**
+   * Indicates no plural formatting.
+   */
+  const FORMAT_PLURAL_NONE = 'none';
+
+   /**
+    * {@inheritdoc}
+    */
+  public static function defaultSettings() {
+    return array(
+      'thousand_separator' => '',
+      'format_plural' => NumericFormatterBase::FORMAT_PLURAL_NONE,
+      'format_plural_string' => '',
+    ) + parent::defaultSettings();
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function settingsForm(array $form, FormStateInterface $form_state) {
@@ -34,16 +61,83 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
       '#weight' => 0,
     );
 
-    $elements['prefix_suffix'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Display prefix and suffix'),
-      '#default_value' => $this->getSetting('prefix_suffix'),
-      '#weight' => 10,
+    $elements['format_plural'] = array(
+      '#type' => 'radios',
+      '#title' => $this->t('Display prefix/suffix'),
+      '#default_value' => $this->getSetting('format_plural'),
+      '#options' => array(
+        NumericFormatterBase::FORMAT_PLURAL_NONE => $this->t('No prefix/suffix'),
+        NumericFormatterBase::FORMAT_PLURAL_USE_FIELD_SETTING => $this->t('Use setting from the field settings'),
+        NumericFormatterBase::FORMAT_PLURAL_USE_FORMATTER_SETTING => $this->t('Override the setting from the field settings'),
+      ),
     );
 
+    $elements['format_plural_values'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Prefix/suffix override'),
+      '#after_build' => [[get_class($this), 'formatPluralValuesAfterBuild']],
+      '#states' => [
+        'visible' => [
+          ':input[name*="format_plural"]' => ['value' => NumericFormatterBase::FORMAT_PLURAL_USE_FORMATTER_SETTING],
+        ],
+      ],
+    ];
+
+    $langcode = $this->getSetting('_settings_langcode');
+    $plurals = $this->getNumberOfPlurals($langcode);
+    $labels = $this->getPluralLabels($langcode);
+    $plural_string = $this->getSetting('format_plural_string');
+    if (!$plural_string) {
+      $settings = $this->getFieldSettings();
+      if (isset($settings['format_plural_string'])) {
+        $plural_string = $settings['format_plural_string'];
+      }
+    }
+    $plural_array = explode(PluralTranslatableMarkup::DELIMITER, $plural_string);
+    for ($i = 0; $i < $plurals; $i++) {
+      $elements['format_plural_values'][$i] = array(
+        '#type' => 'textfield',
+        '#title' => $labels[$i]['label'],
+        '#description' => $labels[$i]['description'],
+        '#default_value' => isset($plural_array[$i]) ? $plural_array[$i] : '',
+      );
+    }
+
     return $elements;
   }
 
+ /**
+   * After-build callback: Sets the value for the format_plural_string property.
+   *
+   * This method is assigned as an #after_build function in the settings form.
+   *
+   * The user interface for setting up singular/plural forms is an array of text
+   * fields, for usability, but for translation purposes, the values have to be
+   * stored as a single string in the field settings. This function implodes the
+   * values.
+   *
+   * @param array $element
+   *   Textfield element where the plural format should be set.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return array
+   *   Textfield element with plural string set.
+   */
+  public static function formatPluralValuesAfterBuild($element, FormStateInterface $form_state) {
+    if ($form_state->isProcessingInput()) {
+      $keys = $element['#parents'];
+      $array_value = $form_state->getValue($keys);
+      $form_state->unsetValue($keys);
+      $string_value = implode(PluralTranslatableMarkup::DELIMITER, $array_value);
+      array_pop($keys);
+      $keys[] = 'format_plural_string';
+      $form_state->setValue($keys, $string_value);
+
+    }
+    return $element;
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -51,8 +145,11 @@ public function settingsSummary() {
     $summary = array();
 
     $summary[] = $this->numberFormat(1234.1234567890);
-    if ($this->getSetting('prefix_suffix')) {
-      $summary[] = t('Display with prefix and suffix.');
+    if ($this->getSetting('format_plural') == NumericFormatterBase::FORMAT_PLURAL_USE_FIELD_SETTING) {
+      $summary[] = $this->t('Display prefix/suffix from the field settings.');
+    }
+    elseif ($this->getSetting('format_plural') == NumericFormatterBase::FORMAT_PLURAL_USE_FORMATTER_SETTING) {
+      $summary[] = $this->t('Display overridden prefix/suffix.');
     }
 
     return $summary;
@@ -63,19 +160,29 @@ public function settingsSummary() {
    */
   public function viewElements(FieldItemListInterface $items, $langcode) {
     $elements = array();
-    $settings = $this->getFieldSettings();
+    $field_settings = $this->getFieldSettings();
 
     foreach ($items as $delta => $item) {
       $output = $this->numberFormat($item->value);
 
       // Account for prefix and suffix.
-      if ($this->getSetting('prefix_suffix')) {
-        $prefixes = isset($settings['prefix']) ? array_map(array('Drupal\Core\Field\FieldFilteredMarkup', 'create'), explode('|', $settings['prefix'])) : array('');
-        $suffixes = isset($settings['suffix']) ? array_map(array('Drupal\Core\Field\FieldFilteredMarkup', 'create'), explode('|', $settings['suffix'])) : array('');
-        $prefix = (count($prefixes) > 1) ? $this->formatPlural($item->value, $prefixes[0], $prefixes[1]) : $prefixes[0];
-        $suffix = (count($suffixes) > 1) ? $this->formatPlural($item->value, $suffixes[0], $suffixes[1]) : $suffixes[0];
-        $output = $prefix . $output . $suffix;
+      $format_string = '';
+      if ($this->getSetting('format_plural') == NumericFormatterBase::FORMAT_PLURAL_USE_FIELD_SETTING) {
+        $format_string = $field_settings['format_plural_string'];
+      }
+      elseif ($this->getSetting('format_plural') == NumericFormatterBase::FORMAT_PLURAL_USE_FORMATTER_SETTING) {
+        $format_string = $this->getSetting('format_plural_string');
       }
+
+      if ($format_string) {
+        // Pass in the current output as an override for @count in plural
+        // formatting. The numeric value is used to decide which plural form to
+        // use, but we want to substitute the current number-formatted
+        // string with the thousands separator and other settings applied in
+        // the output.
+        $output = PluralTranslatableMarkup::createFromTranslatedString($item->value, $format_string, ['@count' => $output], ['langcode' => $this->getSetting('_settings_langcode')]);
+      }
+
       // Output the raw value in a content attribute if the text of the HTML
       // element differs from the raw value (for example when a prefix is used).
       if (isset($item->_attributes) && $item->value != $output) {
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
index 0e95417..4348e83 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
@@ -35,18 +35,6 @@ public static function defaultStorageSettings() {
   /**
    * {@inheritdoc}
    */
-  public static function defaultFieldSettings() {
-    return array(
-      'min' => '',
-      'max' => '',
-      'prefix' => '',
-      'suffix' => '',
-    ) + parent::defaultFieldSettings();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
     $properties['value'] = DataDefinition::create('integer')
       ->setLabel(t('Integer value'))
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php
index 678ab1f..3ed36e9 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Field\FieldItemBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
 
 /**
  * Base class for numeric configurable field types.
@@ -17,8 +18,7 @@ public static function defaultFieldSettings() {
     return array(
       'min' => '',
       'max' => '',
-      'prefix' => '',
-      'suffix' => '',
+      'format_plural_string' => '',
     ) + parent::defaultFieldSettings();
   }
 
@@ -31,31 +31,66 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
 
     $element['min'] = array(
       '#type' => 'number',
-      '#title' => t('Minimum'),
+      '#title' => $this->t('Minimum'),
       '#default_value' => $settings['min'],
-      '#description' => t('The minimum value that should be allowed in this field. Leave blank for no minimum.'),
+      '#description' => $this->t('The minimum value that should be allowed in this field. Leave blank for no minimum.'),
     );
     $element['max'] = array(
       '#type' => 'number',
-      '#title' => t('Maximum'),
+      '#title' => $this->t('Maximum'),
       '#default_value' => $settings['max'],
-      '#description' => t('The maximum value that should be allowed in this field. Leave blank for no maximum.'),
+      '#description' => $this->t('The maximum value that should be allowed in this field. Leave blank for no maximum.'),
     );
-    $element['prefix'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Prefix'),
-      '#default_value' => $settings['prefix'],
-      '#size' => 60,
-      '#description' => t("Define a string that should be prefixed to the value, like '$ ' or '&euro; '. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."),
-    );
-    $element['suffix'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Suffix'),
-      '#default_value' => $settings['suffix'],
-      '#size' => 60,
-      '#description' => t("Define a string that should be suffixed to the value, like ' m', ' kb/s'. Leave blank for none. Separate singular and plural values with a pipe ('pound|pounds')."),
+
+    $element['format_plural_values'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Plural formatted prefix/suffix'),
+      '#after_build' => [[get_class($this), 'formatPluralValuesAfterBuild']],
     );
+    $langcode = $this->getFieldDefinition()->get('langcode');
+    $plurals = $this->getNumberOfPlurals($langcode);
+    $labels = $this->getPluralLabels($langcode);
+    $plural_array = explode(PluralTranslatableMarkup::DELIMITER, $settings['format_plural_string']);
+    for ($i = 0; $i < $plurals; $i++) {
+      $element['format_plural_values'][$i] = array(
+        '#type' => 'textfield',
+        '#title' => $labels[$i]['label'],
+        '#description' => $labels[$i]['description'],
+        '#default_value' => isset($plural_array[$i]) ? $plural_array[$i] : '',
+      );
+    }
+
+    return $element;
+  }
 
+ /**
+   * After-build callback: Sets the value for the format_plural_string property.
+   *
+   * This method is assigned as an #after_build function in the settings form.
+   *
+   * The user interface for setting up singular/plural forms is an array of text
+   * fields, for usability, but for translation purposes, the values have to be
+   * stored as a single string in the field settings. This function implodes the
+   * values.
+   *
+   * @param array $element
+   *   Textfield element where the plural format should be set.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return array
+   *   Textfield element with plural string set.
+   */
+  public static function formatPluralValuesAfterBuild($element, FormStateInterface $form_state) {
+    if ($form_state->isProcessingInput()) {
+      $keys = $element['#parents'];
+      $array_value = $form_state->getValue($keys);
+      $form_state->unsetValue($keys);
+      $string_value = implode(PluralTranslatableMarkup::DELIMITER, $array_value);
+      array_pop($keys);
+      $keys[] = 'format_plural_string';
+      $form_state->setValue($keys, $string_value);
+    }
     return $element;
   }
 
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
index 8200569..363fc36 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/NumberWidget.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\WidgetBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
 use Symfony\Component\Validator\ConstraintViolationInterface;
 
 /**
@@ -29,6 +30,7 @@ class NumberWidget extends WidgetBase {
   public static function defaultSettings() {
     return array(
       'placeholder' => '',
+      'format_plural' => TRUE,
     ) + parent::defaultSettings();
   }
 
@@ -42,6 +44,14 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
       '#default_value' => $this->getSetting('placeholder'),
       '#description' => t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
     );
+
+    $element['format_plural'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Display prefix/suffix'),
+      '#description' => $this->t('If checked, prefix/suffix from the field will be displayed.'),
+      '#default_value' => $this->getSetting('format_plural'),
+    );
+
     return $element;
   }
 
@@ -53,10 +63,14 @@ public function settingsSummary() {
 
     $placeholder = $this->getSetting('placeholder');
     if (!empty($placeholder)) {
-      $summary[] = t('Placeholder: @placeholder', array('@placeholder' => $placeholder));
+      $summary[] = $this->t('Placeholder: @placeholder', array('@placeholder' => $placeholder));
     }
     else {
-      $summary[] = t('No placeholder');
+      $summary[] = $this->t('No placeholder');
+    }
+
+    if ($this->getSetting('format_plural')) {
+      $summary[] = $this->t('Prefix/suffix from the field are displayed');
     }
 
     return $summary;
@@ -95,13 +109,19 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     }
 
     // Add prefix and suffix.
-    if ($field_settings['prefix']) {
-      $prefixes = explode('|', $field_settings['prefix']);
-      $element['#field_prefix'] = FieldFilteredMarkup::create(array_pop($prefixes));
-    }
-    if ($field_settings['suffix']) {
-      $suffixes = explode('|', $field_settings['suffix']);
-      $element['#field_suffix'] = FieldFilteredMarkup::create(array_pop($suffixes));
+    if ($field_settings['format_plural_string'] && $this->getSetting('format_plural')) {
+      // The field setting is a string containing all plural variants, suitable
+      // for use in plural formatting, translated into the UI language. Pop
+      // off the last plural variant, and use the parts before/after @count in
+      // that string as the prefix/suffix for this field.
+      $values = explode(PluralTranslatableMarkup::DELIMITER, $field_settings['format_plural_string']);
+      $labels = explode('@count', array_pop($values));
+      if (isset($labels[0])) {
+        $element['#field_prefix'] = FieldFilteredMarkup::create(trim($labels[0]));
+      }
+      if (isset($labels[1])) {
+        $element['#field_suffix'] = FieldFilteredMarkup::create(trim($labels[1]));
+      }
     }
 
     return array('value' => $element);
diff --git a/core/lib/Drupal/Core/Field/WidgetPluginManager.php b/core/lib/Drupal/Core/Field/WidgetPluginManager.php
index 6be1b9e..7da56f0 100644
--- a/core/lib/Drupal/Core/Field/WidgetPluginManager.php
+++ b/core/lib/Drupal/Core/Field/WidgetPluginManager.php
@@ -50,7 +50,7 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
   }
 
   /**
-   * Overrides PluginManagerBase::getInstance().
+   * Creates a widget instance with the provided options.
    *
    * @param array $options
    *   An array with the following key/value pairs:
@@ -66,6 +66,11 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
    *       used if the requested widget is not available.
    *     - settings: (array) Settings specific to the widget. Each setting
    *       defaults to the default value specified in the widget definition.
+   *       A '_settings_langcode' entry should be present to indicate the
+   *       language of the settings that are being provided (typically, the
+   *       language of the configuration entity that contains the widget,
+   *       such as an entity form mode). If absent, the current interface
+   *       language will be assumed.
    *     - third_party_settings: (array) Settings provided by other extensions
    *       through hook_field_formatter_third_party_settings_form().
    *
diff --git a/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php b/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php
index 89e602f..926a92d 100644
--- a/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php
+++ b/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php
@@ -50,8 +50,10 @@ class PluralTranslatableMarkup extends TranslatableMarkup {
    *   (optional) An array with placeholder replacements, keyed by placeholder.
    *   See \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for
    *   additional information about placeholders. Note that you do not need to
-   *   include @count in this array; this replacement is done automatically
-   *   for the plural cases.
+   *   include @count in this array; this value is supplied automatically.
+   *   However, you can include a replacement for @count to override the
+   *   default of $count (for example, you might want to have a formatted
+   *   number instead of the raw number $count in the output).
    * @param array $options
    *   (optional) An associative array of additional options. See t() for
    *   allowed keys.
@@ -83,7 +85,9 @@ public function __construct($count, $singular, $plural, array $args = [], array
    *   Based on the first character of the key, the value is escaped and/or
    *   themed. See \Drupal\Component\Utility\SafeMarkup::format(). Note that you
    *   do not need to include @count in this array; this replacement is done
-   *   automatically for the plural cases.
+   *   automatically for the plural cases. But you can include @count to
+   *   override the replacement value (for instance, to use a formatted number
+   *   instead of the raw number).
    * @param array $options
    *   An associative array of additional options. See t() for allowed keys.
    *
@@ -111,7 +115,12 @@ public function render() {
     }
 
     $arguments = $this->getArguments();
-    $arguments['@count'] = $this->count;
+    // Allow overrides of what gets substituted in for @count; for example, to
+    // include a formatted number in the output, while using the raw number to
+    // decide which plural form to use.
+    if (!isset($arguments['@count'])) {
+      $arguments['@count'] = $this->count;
+    }
     $translated_array = explode(static::DELIMITER, $this->translatedString);
 
     if ($this->count == 1) {
@@ -130,9 +139,9 @@ public function render() {
       }
       else {
         // If the index cannot be computed or there's no translation, use the
-        // second plural form as a fallback (which allows for most flexibility
+        // last plural form as a fallback (which allows for most flexibility
         // with the replaceable @count value).
-        $return = $translated_array[1];
+        $return = $translated_array[count($translated_array) - 1];
       }
     }
 
diff --git a/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php b/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
index 63ff019..fbcd884 100644
--- a/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
+++ b/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
@@ -94,6 +94,67 @@ protected function getNumberOfPlurals($langcode = NULL) {
   }
 
   /**
+   * Returns the labels and descriptions for plural variants in a language.
+   *
+   * @param string $langcode
+   *   The language code.
+   *
+   * @return array[]
+   *  An array of labels and descriptions to use in forms for the plural
+   *  variants of $langcode, translated into the current interface language.
+   *  Each item in the returned array is an array with elements 'label' and
+   *  'description' for the corresponding plural variant.
+   */
+  protected function getPluralLabels($langcode) {
+    $plurals = $this->getNumberOfPlurals($langcode);
+
+    // @todo The return values should be made language-specific, so that they
+    // reflect the actual plural rules of languages. Also this method should
+    // be used in several forms that are currently calculating their own
+    // labels for plural variant forms. Issue:
+    // https://www.drupal.org/node/2499639.
+
+    if ($plurals == 1) {
+      return [
+        [
+          'label' => $this->t('Form for all numbers'),
+          'description' => $this->t('Text to use for all numbers; @count will be replaced with the value.'),
+        ],
+      ];
+    }
+
+    if ($plurals == 2) {
+      // @todo see above note. This text is good for typical 2-form languages,
+      // but it may need special cases for languages where the two forms do not
+      // correspond to 1 and != 1 values.
+      return [
+        [
+          'label' => $this->t('Singular form'),
+          'description' => $this->t('Text to use for the singular form; @count will be replaced with the value.'),
+        ],
+        [
+          'label' => $this->t('Plural form'),
+          'description' => $this->t('Text to use for the plural form; @count will be replaced with the value.'),
+        ],
+      ];
+    }
+
+    // For the != 2 case, generate labels.
+    // @todo see above note. This definitely needs specific cases for languages
+    // that describe what the forms are used for, rather than saying
+    // Singular form, First plural form, 2. plural form, etc.
+    $labels = [];
+    for ($i = 0; $i < $plurals; $i++) {
+      $labels[] = [
+        'label' => ($i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form')),
+        'description' => $this->t('Text to use for this variant; @count will be replaced with the value.'),
+      ];
+    }
+
+    return $labels;
+  }
+
+  /**
    * Gets the string translation service.
    *
    * @return \Drupal\Core\StringTranslation\TranslationInterface
diff --git a/core/modules/field/src/Plugin/migrate/process/d6/FieldFormatterSettingsDefaults.php b/core/modules/field/src/Plugin/migrate/process/d6/FieldFormatterSettingsDefaults.php
index 034ab0b..a94ec7e 100644
--- a/core/modules/field/src/Plugin/migrate/process/d6/FieldFormatterSettingsDefaults.php
+++ b/core/modules/field/src/Plugin/migrate/process/d6/FieldFormatterSettingsDefaults.php
@@ -59,69 +59,69 @@ protected function numberSettings($type, $format) {
           'scale' => 0,
           'decimal_separator' => '.',
           'thousand_separator' => ',',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'us_1' => [
           'scale' => 1,
           'decimal_separator' => '.',
           'thousand_separator' => ',',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'us_2' => [
           'scale' => 2,
           'decimal_separator' => '.',
           'thousand_separator' => ',',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'be_0' => [
           'scale' => 0,
           'decimal_separator' => ',',
           'thousand_separator' => '.',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'be_1' => [
           'scale' => 1,
           'decimal_separator' => ',',
           'thousand_separator' => '.',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'be_2' => [
           'scale' => 2,
           'decimal_separator' => ',',
           'thousand_separator' => '.',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'fr_0' => [
           'scale' => 0,
           'decimal_separator' => ',',
           'thousand_separator' => ' ',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'fr_1' => [
           'scale' => 1,
           'decimal_separator' => ',',
           'thousand_separator' => ' ',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'fr_2' => [
           'scale' => 2,
           'decimal_separator' => ',',
           'thousand_separator' => ' ',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
       ],
       'number_integer' => [
         'us_0' => [
           'thousand_separator' => ',',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'be_0' => [
           'thousand_separator' => '.',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
         'fr_0' => [
           'thousand_separator' => ' ',
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ],
       ],
     ];
diff --git a/core/modules/field/src/Plugin/migrate/process/d6/FieldInstanceSettings.php b/core/modules/field/src/Plugin/migrate/process/d6/FieldInstanceSettings.php
index 080a4d7..e89df31 100644
--- a/core/modules/field/src/Plugin/migrate/process/d6/FieldInstanceSettings.php
+++ b/core/modules/field/src/Plugin/migrate/process/d6/FieldInstanceSettings.php
@@ -2,10 +2,12 @@
 
 namespace Drupal\field\Plugin\migrate\process\d6;
 
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
 use Drupal\migrate\MigrateExecutableInterface;
 use Drupal\migrate\ProcessPluginBase;
 use Drupal\migrate\Row;
 
+
 /**
  * @MigrateProcessPlugin(
  *   id = "d6_field_field_settings"
@@ -25,8 +27,27 @@ public function transform($value, MigrateExecutableInterface $migrate_executable
       case 'number':
         $settings['min'] = $field_settings['min'];
         $settings['max'] = $field_settings['max'];
-        $settings['prefix'] = $field_settings['prefix'];
-        $settings['suffix'] = $field_settings['suffix'];
+        $prefix = $field_settings['prefix'];
+        $suffix = $field_settings['suffix'];
+        if ($prefix || $suffix) {
+          // In case they were using the previous system to separate
+          // plural values by |, make this into a proper format_plural
+          // object.
+          $prefixes = explode('|', $prefix);
+          $suffixes = explode('|', $suffix);
+          $length = max(count($prefixes), count($suffixes));
+          $strings = [];
+          for ($i = 0; $i < $length; $i++) {
+            $prefix = isset($prefixes[$i]) ? $prefixes[$i] : $prefixes[count($prefixes) - 1];
+            $suffix = isset($suffixes[$i]) ? $suffixes[$i] : $suffixes[count($suffixes) - 1];
+            $strings[] = $prefix . '@count' . $suffix;
+          }
+          $settings['format_plural_string'] = implode(PluralTranslatableMarkup::DELIMITER, $strings);
+        }
+        else {
+          $settings['format_plural_string'] = '';
+        }
+
         break;
 
       case 'link':
diff --git a/core/modules/field/src/Tests/Number/NumberFieldTest.php b/core/modules/field/src/Tests/Number/NumberFieldTest.php
index 342f6dd..e39e656 100644
--- a/core/modules/field/src/Tests/Number/NumberFieldTest.php
+++ b/core/modules/field/src/Tests/Number/NumberFieldTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\field\Tests\Number;
 
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\node\Entity\Node;
 use Drupal\simpletest\WebTestBase;
@@ -143,7 +144,9 @@ function testNumberIntegerField() {
       'entity_type' => 'entity_test',
       'bundle' => 'entity_test',
       'settings' => array(
-        'min' => $minimum, 'max' => $maximum, 'prefix' => 'ThePrefix',
+        'min' => $minimum,
+        'max' => $maximum,
+        'format_plural_string' => 'ThePrefix@count' . PluralTranslatableMarkup::DELIMITER . 'PluralPrefix@countPluralSuffix',
       )
     ])->save();
 
@@ -151,7 +154,8 @@ function testNumberIntegerField() {
       ->setComponent($field_name, array(
         'type' => 'number',
         'settings' => array(
-          'placeholder' => '4'
+          'placeholder' => '4',
+          'format_plural' => TRUE,
         ),
       ))
       ->save();
@@ -159,7 +163,7 @@ function testNumberIntegerField() {
       ->setComponent($field_name, array(
         'type' => 'number_integer',
         'settings' => array(
-          'prefix_suffix' => FALSE,
+          'format_plural' => 'none',
         ),
       ))
       ->save();
@@ -183,6 +187,24 @@ function testNumberIntegerField() {
     $this->drupalGet('entity_test/add');
     $this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
     $this->assertRaw('placeholder="4"');
+    $this->assertText('PluralPrefix');
+    $this->assertText('PluralSuffix');
+
+    // Turn off the prefix and suffix and test.
+    entity_get_form_display('entity_test', 'entity_test', 'default')
+      ->setComponent($field_name, array(
+        'type' => 'number',
+        'settings' => array(
+          'placeholder' => '7',
+          'format_plural' => FALSE,
+        ),
+      ))
+      ->save();
+    $this->drupalGet('entity_test/add');
+    $this->assertFieldByName("{$field_name}[0][value]", '', 'Widget is displayed');
+    $this->assertRaw('placeholder="7"');
+    $this->assertNoText('PluralPrefix');
+    $this->assertNoText('PluralSuffix');
 
     // Submit a valid integer
     $value = rand($minimum, $maximum);
@@ -246,15 +268,17 @@ function testNumberIntegerField() {
       $this->assertNoFieldByXpath('//div[@content="' . $valid_entry . '"]', NULL, 'The "content" attribute is not present since the Prefix is not being displayed');
     }
 
-    // Test for the content attribute when a Prefix is displayed. Presumably this also tests for the attribute when a Suffix is displayed.
+    // Test for the content attribute using the field setting for plural
+    // prefix/suffix formatting.
     entity_get_display('entity_test', 'entity_test', 'default')
       ->setComponent($field_name, array(
         'type' => 'number_integer',
         'settings' => array(
-          'prefix_suffix' => TRUE,
+          'format_plural' => 'field',
         ),
       ))
       ->save();
+
     $integer_value = '123';
     $this->drupalGet('entity_test/add');
     $edit = array(
@@ -265,7 +289,34 @@ function testNumberIntegerField() {
     $id = $match[1];
     $this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
     $this->drupalGet('entity_test/' . $id);
-    $this->assertFieldByXPath('//div[@content="' . $integer_value . '"]', 'ThePrefix' . $integer_value, 'The "content" attribute has been set to the value of the field, and the prefix is being displayed.');
+    $this->assertFieldByXPath('//div[@content="' . $integer_value . '"]', 'PluralPrefix' . $integer_value . 'PluralSuffix', 'The "content" attribute has been set to the value of the field, and the prefix and suffix are being displayed.');
+
+    // Test with a singular value.
+    $integer_value = '1';
+    $this->drupalGet('entity_test/add');
+    $edit = array(
+      "{$field_name}[0][value]" => $integer_value,
+    );
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    preg_match('|entity_test/manage/(\d+)|', $this->url, $match);
+    $id = $match[1];
+    $this->assertText(t('entity_test @id has been created.', array('@id' => $id)), 'Entity was created');
+    $this->drupalGet('entity_test/' . $id);
+    $this->assertFieldByXPath('//div[@content="' . $integer_value . '"]', 'ThePrefix' . $integer_value, 'The "content" attribute has been set to the value of the field, and the singular prefix is being displayed.');
+
+    // Change to using an overridden plural formatting string.
+    entity_get_display('entity_test', 'entity_test', 'default')
+      ->setComponent($field_name, array(
+        'type' => 'number_integer',
+        'settings' => array(
+          'format_plural' => 'formatter',
+          'format_plural_string' => 'NewPrefix@count',
+        ),
+      ))
+      ->save();
+    $this->drupalGet('entity_test/' . $id);
+    $this->assertFieldByXPath('//div[@content="' . $integer_value . '"]', 'NewPrefix' . $integer_value, 'The "content" attribute has been set to the value of the field, and the overridden prefix is being displayed.');
+
   }
 
   /**
@@ -392,8 +443,7 @@ function testNumberFormatter() {
       'entity_type' => 'node',
       'bundle' => $type,
       'settings' => array(
-        'prefix' => $prefix,
-        'suffix' => $suffix
+        'format_plural_string' => $prefix . '@count' . $suffix,
       ),
     ])->save();
 
@@ -402,8 +452,7 @@ function testNumberFormatter() {
       'entity_type' => 'node',
       'bundle' => $type,
       'settings' => array(
-        'prefix' => $prefix,
-        'suffix' => $suffix
+        'format_plural_string' => $prefix . '@count' . $suffix,
       ),
     ])->save();
 
@@ -450,7 +499,7 @@ function testNumberFormatter() {
 
     $this->drupalPostAjaxForm(NULL, array(), "${float_field}_settings_edit");
     $edit = array(
-      "fields[${float_field}][settings_edit_form][settings][prefix_suffix]" => TRUE,
+      "fields[${float_field}][settings_edit_form][settings][format_plural]" => 'field',
       "fields[${float_field}][settings_edit_form][settings][scale]" => $scale,
       "fields[${float_field}][settings_edit_form][settings][decimal_separator]" => $decimal_separator,
       "fields[${float_field}][settings_edit_form][settings][thousand_separator]" => $thousand_separator,
@@ -476,7 +525,7 @@ function testNumberFormatter() {
 
     $this->drupalPostAjaxForm(NULL, array(), "${integer_field}_settings_edit");
     $edit = array(
-      "fields[${integer_field}][settings_edit_form][settings][prefix_suffix]" => FALSE,
+      "fields[${integer_field}][settings_edit_form][settings][format_plural]" => 'none',
       "fields[${integer_field}][settings_edit_form][settings][thousand_separator]" => $thousand_separator,
     );
     $this->drupalPostAjaxForm(NULL, $edit, "${integer_field}_plugin_settings_update");
diff --git a/core/modules/field/src/Tests/Update/NumberFieldUpdateTest.php b/core/modules/field/src/Tests/Update/NumberFieldUpdateTest.php
new file mode 100644
index 0000000..842e006
--- /dev/null
+++ b/core/modules/field/src/Tests/Update/NumberFieldUpdateTest.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\Tests\Update\NumberFieldUpdateTest.
+ */
+
+namespace Drupal\field\Tests\Update;
+
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\system\Tests\Update\UpdatePathTestBase;
+
+/**
+ * Tests the numeric field updates in system_update_8014().
+ *
+ * @see https://www.drupal.org/node/2545730
+ *
+ * @group field
+ */
+class NumberFieldUpdateTest extends UpdatePathTestBase {
+
+  // Make sure all the modules that the config depends on are enabled.
+  protected static $modules = array('node', 'views', 'text', 'user', 'path');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+      __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.numeric-field-data-2545730.php',
+    ];
+  }
+
+  /**
+   * Tests that numeric fields, widgets, and formatters are updated correctly.
+   */
+  public function testUpdateHookN() {
+    // Run the updates.
+    $this->runUpdates();
+
+    // Load the updated configuration and verify it was updated correctly.
+
+    // Field configuration - prefix/suffix should be updated to format_plural
+    // in settings.
+    $config = \Drupal::config('field.field.node.number_test.field_test_int')->get();
+    $settings = $config['settings'];
+    $this->assertFalse(isset($settings['prefix']), 'Prefix setting has been removed from field settings');
+    $this->assertFalse(isset($settings['suffix']), 'Suffix setting has been removed from field settings');
+    $this->assertEqual($settings['format_plural_string'], 'Sprefix@countSsuffix' . PluralTranslatableMarkup::DELIMITER . 'Pprefix@countPsuffix');
+
+    // Form mode configuration - there should be a format_plural setting,
+    // and since this was not optional prior to this update, it should be
+    // set to TRUE.
+    $config = \Drupal::config('core.entity_form_display.node.number_test.default')->get();
+    $this->assertTrue($config['content']['field_test_int']['settings']['format_plural']);
+
+    // View mode configuration - should be migrated from prefix_suffix TRUE to
+    // format_plural 'field'.
+    $config = \Drupal::config('core.entity_view_display.node.number_test.default')->get();
+    $this->assertFalse(isset($config['content']['field_test_int']['settings']['prefix_suffix']), 'prefix_suffix setting is removed from entity view display');
+    $this->assertEqual($config['content']['field_test_int']['settings']['format_plural'], 'field');
+
+    // In the View, the first numeric field should be displaying and the second
+    // not displaying the prefix_suffix. These should now be using the
+    // format_plural setting as in the view mode, set to 'field' and 'none'
+    // respectively.
+    $config = \Drupal::config('views.view.number_test')->get();
+    $fields = $config['display']['default']['display_options']['fields'];
+    $this->assertFalse(isset($fields['field_test_int']['settings']['prefix_suffix']), 'prefix_suffix setting has been removed from views field');
+    $this->assertFalse(isset($fields['field_test_int_1']['settings']['prefix_suffix']), 'prefix_suffix setting has been removed from views field');
+    $this->assertEqual($fields['field_test_int']['settings']['format_plural'], 'field');
+    $this->assertEqual($fields['field_test_int_1']['settings']['format_plural'], 'none');
+  }
+
+}
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
index 4c9cbf1..dbe2bb8 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldFormatterSettingsTest.php
@@ -67,7 +67,8 @@ public function testEntityDisplaySettings() {
     $expected['type'] = 'number_integer';
     $expected['settings'] = array(
       'thousand_separator' => ',',
-      'prefix_suffix' => TRUE,
+      'format_plural' => 'field',
+      'format_plural_string' => '',
     );
     $component = $display->getComponent('field_test_two');
     $this->assertIdentical($expected, $component);
@@ -77,7 +78,8 @@ public function testEntityDisplaySettings() {
        'scale' => 2,
        'decimal_separator' => '.',
        'thousand_separator' => ',',
-       'prefix_suffix' => TRUE,
+       'format_plural' => 'field',
+       'format_plural_string' => '',
     );
     $component = $display->getComponent('field_test_three');
     $this->assertIdentical($expected, $component);
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
index b3fac3f..2a6254f 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldInstanceTest.php
@@ -35,8 +35,7 @@ public function testFieldInstanceMigration() {
     $expected = array(
       'min' => 10,
       'max' => 100,
-      'prefix' => 'pref',
-      'suffix' => 'suf',
+      'format_plural_string' => 'pref@countsuf',
       'unsigned' => FALSE,
       'size' => 'normal',
     );
@@ -47,8 +46,7 @@ public function testFieldInstanceMigration() {
     $expected = array(
       'min' => 100.0,
       'max' => 200.0,
-      'prefix' => 'id-',
-      'suffix' => '',
+      'format_plural_string' => 'id-@count',
     );
     $this->assertIdentical($expected, $field->getSettings());
 
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
index a16040f..1c886be 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFieldWidgetSettingsTest.php
@@ -39,7 +39,7 @@ public function testWidgetSettings() {
     $component = $form_display->getComponent('field_test_two');
     $expected['type'] = 'number';
     $expected['weight'] = 1;
-    $expected['settings'] = array('placeholder' => '');
+    $expected['settings'] = array('placeholder' => '', 'format_plural' => TRUE);
     $this->assertIdentical($expected, $component);
 
     // Float field.
diff --git a/core/modules/field_ui/src/Tests/NumericFieldsUiTest.php b/core/modules/field_ui/src/Tests/NumericFieldsUiTest.php
new file mode 100644
index 0000000..e647b0e
--- /dev/null
+++ b/core/modules/field_ui/src/Tests/NumericFieldsUiTest.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field_ui\Tests\NumericFieldsUiTest.
+ */
+
+namespace Drupal\field_ui\Tests;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the UI for field and formatter for numeric fields.
+ *
+ * @group field_ui
+ */
+class NumericFieldsUiTest extends WebTestBase {
+
+  use FieldUiTestTrait;
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = array('node', 'field_ui', 'block');
+
+  /**
+   * The name of the field to be created for testing.
+   *
+   * @var string
+   */
+  protected $fieldName;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalPlaceBlock('system_breadcrumb_block');
+    $this->drupalPlaceBlock('local_actions_block');
+    $this->drupalPlaceBlock('local_tasks_block');
+    $this->drupalPlaceBlock('page_title_block');
+
+    // Create a test user.
+    $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer users', 'administer account settings', 'administer user display', 'bypass node access'));
+    $this->drupalLogin($admin_user);
+
+    $this->fieldName = 'my_int_test';
+
+    // Create Article node type.
+    $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
+
+  }
+
+  /**
+   * Tests the UI for numeric fields and formatters.
+   */
+  public function testNumericFieldUI() {
+    // Add an integer field using the UI with plural prefix/suffix forms.
+    $this->fieldUIAddNewField('admin/structure/types/manage/article',
+      $this->fieldName, NULL, 'integer', [],
+      ['settings[format_plural_values][0]' => 'SingularPrefix@countSingularSuffix', 'settings[format_plural_values][1]' => 'PluralPrefix@countPluralSuffix']);
+
+    // Set the display to use the plural formatting from field settings.
+    $this->drupalGet('admin/structure/types/manage/article/display');
+    $settings_button = 'field_' . $this->fieldName . '_settings_edit';
+    $this->drupalPostAjaxForm(NULL, array(), $settings_button);
+    $formatter_settings_prefix = 'fields[field_' . $this->fieldName . '][settings_edit_form][settings]';
+    $edit = [$formatter_settings_prefix . '[format_plural]' => 'field'];
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+
+    // Create a node with field value 1.
+    $this->drupalGet('node/add/article');
+    // While doing that, verify that on the editing form, the plural form
+    // prefix/suffix are shown. We don't need to test all the variations here,
+    // since \Drupal\field\Tests\Number\NumberFieldTest verifies that if you
+    // turn the setting for prefix display on/off on the widget, the
+    // prefix/suffix are/are not shown.
+    $this->assertText('PluralPrefix');
+    $this->assertText('PluralSuffix');
+    $this->drupalPostForm(NULL, ['title[0][value]' => 'foobar', 'field_' . $this->fieldName . '[0][value]' => 1], t('Save'));
+
+    // We should be on the node page. Verify that the singular
+    // prefix/suffix are shown.
+    $this->assertText('SingularPrefix1SingularSuffix');
+
+    // Change the setting on the formatter to override the prefix/suffix.
+    $this->drupalGet('admin/structure/types/manage/article/display');
+    $this->drupalPostAjaxForm(NULL, array(), $settings_button);
+    $edit = array(
+      $formatter_settings_prefix . '[format_plural]' => 'formatter',
+      $formatter_settings_prefix . '[format_plural_values][0]' => 'OverrideSingularP@countOverrideSingularS',
+      $formatter_settings_prefix . '[format_plural_values][1]' => 'OverridePluralP@countOverridePluralS',
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, 'field_' . $this->fieldName . '_plugin_settings_update');
+    $this->drupalPostForm(NULL, array(), t('Save'));
+
+    // Create another node, this time with a plural value, and verify the
+    // formatting.
+    $this->drupalGet('node/add/article');
+    $this->drupalPostForm(NULL, ['title[0][value]' => 'barbaz', 'field_' . $this->fieldName . '[0][value]'=> 2], t('Save'));
+    $this->assertText('OverridePluralP2OverridePluralS');
+
+  }
+
+}
diff --git a/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php b/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
index 3aa8e68..211f04e 100644
--- a/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
+++ b/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
@@ -215,6 +215,7 @@ public function testFieldComponent() {
     // Check that the getFormatter() method returns the correct formatter plugin.
     $formatter = $display->getRenderer($field_name);
     $this->assertEqual($formatter->getPluginId(), $default_formatter);
+    $formatter_settings['_settings_langcode'] = 'en';
     $this->assertEqual($formatter->getSettings(), $formatter_settings);
 
     // Check that the formatter is statically persisted, by assigning an
diff --git a/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php b/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
index b66d29c..343006c 100644
--- a/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
+++ b/core/modules/field_ui/tests/src/Kernel/EntityFormDisplayTest.php
@@ -86,6 +86,7 @@ public function testFieldComponent() {
     // Check that the getWidget() method returns the correct widget plugin.
     $widget = $form_display->getRenderer($field_name);
     $this->assertEqual($widget->getPluginId(), $default_widget);
+    $widget_settings['_settings_langcode'] = 'en';
     $this->assertEqual($widget->getSettings(), $widget_settings);
 
     // Check that the widget is statically persisted, by assigning an
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml
index f521093..7fd375f 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_nid_argument.yml
@@ -106,7 +106,7 @@ display:
           type: number_integer
           settings:
             thousand_separator: ''
-            prefix_suffix: true
+            format_plural: 'field'
           group_column: value
           group_columns: {  }
           group_rows: true
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index c716891..8748595 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -14,6 +14,7 @@
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PrivateStream;
 use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
 use Drupal\system\SystemRequirements;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -1622,6 +1623,106 @@ function system_update_8013() {
 }
 
 /**
+ * Update numeric field prefix/suffix configuration.
+ */
+function system_update_8014($sandbox) {
+  // Update prefix/suffix on numeric fields (types: float, decimal, integer)
+  // to new format_plural_string configuration. Also update associated
+  // formatters and widgets.
+
+  $config_factory = \Drupal::configFactory();
+  $views_fields_to_update = [];
+  $types = ['float', 'decimal', 'integer'];
+
+  foreach ($config_factory->listAll('field.field.') as $field_config_name) {
+    $field_config = $config_factory->getEditable($field_config_name);
+    if (in_array($field_config->get('field_type'), $types)) {
+      $settings = $field_config->get('settings');
+      $prefix = isset($settings['prefix']) ? $settings['prefix'] : '';
+      $suffix = isset($settings['suffix']) ? $settings['suffix'] : '';
+      if (($prefix || $suffix) && (!isset($settings['format_plural_string']) || !$settings['format_plural_string'])) {
+        // In case they were using the previous system to separate
+        // plural values by |, make this into a proper format_plural
+        // object.
+        $prefixes = explode('|', $prefix);
+        $suffixes = explode('|', $suffix);
+        $length = max(count($prefixes), count($suffixes));
+        $strings = [];
+        for ($i = 0; $i < $length; $i++) {
+          $prefix = isset($prefixes[$i]) ? $prefixes[$i] : $prefixes[count($prefixes) - 1];
+          $suffix = isset($suffixes[$i]) ? $suffixes[$i] : $suffixes[count($suffixes) - 1];
+          $strings[] = $prefix . '@count' . $suffix;
+        }
+        $settings['format_plural_string'] = implode(PluralTranslatableMarkup::DELIMITER, $strings);
+      }
+      else {
+        $settings['format_plural_string'] = '';
+      }
+
+      unset($settings['prefix']);
+      unset($settings['suffix']);
+
+      $field_config->set('settings', $settings);
+      $field_config->save(TRUE);
+
+      $field_name = $field_config->get('field_name');
+
+      $views_fields_to_update[] = $field_name;
+
+      foreach ($config_factory->listAll('core.entity_view_display.') as $view_display_name) {
+        $view_display = $config_factory->getEditable($view_display_name);
+        if ($settings = $view_display->get('content.' . $field_name . '.settings')) {
+          $settings['format_plural'] = (isset($settings['prefix_suffix']) && $settings['prefix_suffix']) ? 'field' : 'none';
+          unset($settings['prefix_suffix']);
+          $settings['format_plural_string'] = '';
+
+          $view_display->set('content.' . $field_name . '.settings', $settings);
+          $view_display->save(TRUE);
+        }
+      }
+
+      foreach ($config_factory->listAll('core.entity_form_display.') as $form_display_name) {
+        $form_display = $config_factory->getEditable($form_display_name);
+        if ($settings = $form_display->get('content.' . $field_name . '.settings')) {
+          $settings['format_plural'] = TRUE;
+          $form_display->set('content.' . $field_name . '.settings', $settings);
+          $form_display->save(TRUE);
+        }
+      }
+    }
+  }
+
+  // Now do the same formatter update as above for formatters that are
+  // part of views. There isn't a way to query for which views might have
+  // them, so we have to go through all the displays on all the views.
+  if (count($views_fields_to_update)) {
+    foreach ($config_factory->listAll('views.view.') as $view_config_name) {
+      $view = $config_factory->getEditable($view_config_name);
+      $displays = $view->get('display');
+      $changed = FALSE;
+      foreach ($displays as $display_name => $display) {
+        if (!empty($display['display_options']['fields'])) {
+          foreach ($display['display_options']['fields'] as $field_name => $field) {
+            if (isset($field['field']) && in_array($field['field'], $views_fields_to_update) && !empty($field['settings'])) {
+              $settings = $field['settings'];
+              $settings['format_plural'] = (isset($settings['prefix_suffix']) && $settings['prefix_suffix']) ? 'field' : 'none';
+              unset($settings['prefix_suffix']);
+              $settings['format_plural_string'] = '';
+              $displays[$display_name]['display_options']['fields'][$field_name]['settings'] = $settings;
+              $changed = TRUE;
+            }
+          }
+        }
+      }
+      if ($changed) {
+        $view->set('display', $displays);
+        $view->save(TRUE);
+      }
+    }
+  }
+}
+
+/**
  * @} End of "addtogroup updates-8.0.0-beta".
  */
 
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.core.entity_form_display.node.number_test.default-2545730.yml b/core/modules/system/tests/fixtures/update/drupal-8.core.entity_form_display.node.number_test.default-2545730.yml
new file mode 100644
index 0000000..d21bbee
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.core.entity_form_display.node.number_test.default-2545730.yml
@@ -0,0 +1,58 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.node.number_test.field_test_int
+    - node.type.number_test
+  module:
+    - path
+    - text
+id: node.number_test.default
+targetEntityType: node
+bundle: number_test
+mode: default
+content:
+  created:
+    type: datetime_timestamp
+    weight: 10
+    settings: {  }
+    third_party_settings: {  }
+  field_test_int:
+    weight: 32
+    settings:
+      placeholder: ''
+    third_party_settings: {  }
+    type: number
+  path:
+    type: path
+    weight: 30
+    settings: {  }
+    third_party_settings: {  }
+  promote:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 15
+    third_party_settings: {  }
+  sticky:
+    type: boolean_checkbox
+    settings:
+      display_label: true
+    weight: 16
+    third_party_settings: {  }
+  title:
+    type: string_textfield
+    weight: -5
+    settings:
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+  uid:
+    type: entity_reference_autocomplete
+    weight: 5
+    settings:
+      match_operator: CONTAINS
+      size: 60
+      placeholder: ''
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.core.entity_view_display.node.number_test.default-2545730.yml b/core/modules/system/tests/fixtures/update/drupal-8.core.entity_view_display.node.number_test.default-2545730.yml
new file mode 100644
index 0000000..27c99e8
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.core.entity_view_display.node.number_test.default-2545730.yml
@@ -0,0 +1,27 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.field.node.number_test.field_test_int
+    - node.type.number_test
+  module:
+    - text
+    - user
+id: node.number_test.default
+targetEntityType: node
+bundle: number_test
+mode: default
+content:
+  field_test_int:
+    weight: 102
+    label: above
+    settings:
+      thousand_separator: ''
+      prefix_suffix: true
+    third_party_settings: {  }
+    type: number_integer
+  links:
+    weight: 100
+    settings: {  }
+    third_party_settings: {  }
+hidden: {  }
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.field.field.node.number_test.field_test_int-2545730.yml b/core/modules/system/tests/fixtures/update/drupal-8.field.field.node.number_test.field_test_int-2545730.yml
new file mode 100644
index 0000000..9ba1609
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.field.field.node.number_test.field_test_int-2545730.yml
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.node.field_test_int
+    - node.type.number_test
+id: node.number_test.field_test_int
+field_name: field_test_int
+entity_type: node
+bundle: number_test
+label: 'Test int'
+description: ''
+required: false
+translatable: false
+default_value: {  }
+default_value_callback: ''
+settings:
+  min: null
+  max: null
+  prefix: Sprefix|Pprefix
+  suffix: Ssuffix|Psuffix
+field_type: integer
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.field.storage.node.field_test_int-2545730.yml b/core/modules/system/tests/fixtures/update/drupal-8.field.storage.node.field_test_int-2545730.yml
new file mode 100644
index 0000000..341b8a4
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.field.storage.node.field_test_int-2545730.yml
@@ -0,0 +1,19 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - node
+id: node.field_test_int
+field_name: field_test_int
+entity_type: node
+type: integer
+settings:
+  unsigned: false
+  size: normal
+module: core
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.node.type.number_test-2545730.yml b/core/modules/system/tests/fixtures/update/drupal-8.node.type.number_test-2545730.yml
new file mode 100644
index 0000000..6eadf70
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.node.type.number_test-2545730.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: true
+name: 'Number test'
+type: number_test
+description: 'Content type for testing update of numeric field prefix/suffix to format_plural setting.'
+help: ''
+new_revision: false
+preview_mode: 1
+display_submitted: false
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.numeric-field-data-2545730.php b/core/modules/system/tests/fixtures/update/drupal-8.numeric-field-data-2545730.php
new file mode 100644
index 0000000..f8e250b
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.numeric-field-data-2545730.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Contains database additions to drupal-8.bare.standard.php.gz for testing the
+ * upgrade path of https://www.drupal.org/node/2545730.
+ */
+
+use Drupal\Core\Database\Database;
+use Drupal\field\Entity\FieldStorageConfig;
+
+$connection = Database::getConnection();
+
+// Define a complete set of configurations, with dependencies.
+$configs = [
+  'core.entity_form_display.node.number_test.default',
+  'core.entity_view_display.node.number_test.default',
+  'field.field.node.number_test.field_test_int',
+  'field.storage.node.field_test_int',
+  'node.type.number_test',
+  'views.view.number_test',
+];
+
+$data = [];
+foreach ($configs as $config) {
+  $file = 'drupal-8.' . $config . '-2545730.yml';
+  $data[$config] = \Drupal\Component\Serialization\Yaml::decode(file_get_contents(__DIR__ . '/' . $file));
+  $connection->insert('config')
+    ->fields(array(
+      'collection',
+      'name',
+      'data',
+    ))
+    ->values(array(
+      'collection' => '',
+      'name' => $config,
+      'data' => serialize($data[$config]),
+      ))
+    ->execute();
+}
+
+// Create the field database tables. Don't bother with descriptions or
+// indexes, since we're not actually putting data into them.
+$schema = $connection->schema();
+$common_fields = [
+    'bundle' => [
+      'type' => 'varchar',
+      'length' => 128,
+    ],
+    'deleted' => [
+      'type' => 'int',
+      'length' => 4,
+    ],
+    'entity_id' => [
+      'type' => 'int',
+      'length' => 10,
+    ],
+    'revision_id' => [
+      'type' => 'int',
+      'length' => 10,
+    ],
+    'langcode' => [
+      'type' => 'varchar',
+      'length' => 32,
+    ],
+    'delta' => [
+      'type' => 'int',
+      'length' => 10,
+    ],
+];
+
+$schema->createTable('node_revision__field_test_int', [ 'fields' => $common_fields, ]);
+$schema->createTable('node__field_test_int', [ 'fields' => $common_fields, ]);
+
+// Tell Drupal this field has been installed, or it will think it has been
+// deleted. Normally you'd want to use \Drupal::keyValue() to do this, but
+// you cannot use this service at this point in the update test. So set the
+// value in the database directly.
+$definitions = unserialize($connection->select('key_value', 'kv')
+  ->fields('kv', array('value'))
+  ->condition('kv.collection', 'entity.definitions.installed')
+  ->condition('kv.name', 'node.field_storage_definitions')
+  ->execute()
+  ->fetchField());
+// Here we would normally use an Entity::create() method, but again we do
+// not have the full container, so instead we just use the config entity
+// class constructor directly.
+$definitions['field_test_int'] = new FieldStorageConfig($data['field.storage.node.field_test_int']);
+$connection->update('key_value')
+  ->condition('collection', 'entity.definitions.installed')
+  ->condition('name', 'node.field_storage_definitions')
+  ->fields(array('value' => serialize($definitions)))
+  ->execute();
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml b/core/modules/system/tests/fixtures/update/drupal-8.views.view.number_test-2545730.yml
similarity index 56%
copy from core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml
copy to core/modules/system/tests/fixtures/update/drupal-8.views.view.number_test-2545730.yml
index 80295b6..1ec3a6a 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml
+++ b/core/modules/system/tests/fixtures/update/drupal-8.views.view.number_test-2545730.yml
@@ -1,17 +1,19 @@
 langcode: en
 status: true
 dependencies:
+  config:
+    - field.storage.node.field_test_int
+    - node.type.number_test
   module:
     - node
-    - taxonomy
     - user
-id: test_taxonomy_term_relationship
-label: test_taxonomy_term_relationship
+id: number_test
+label: 'Number test'
 module: views
 description: ''
 tag: ''
-base_table: taxonomy_term_field_data
-base_field: tid
+base_table: node_field_data
+base_field: nid
 core: 8.x
 display:
   default:
@@ -81,18 +83,11 @@ display:
           hide_empty: false
           default_field_elements: true
       fields:
-        tid:
-          id: tid
-          table: taxonomy_term_field_data
-          field: tid
-          entity_type: taxonomy_term
-          entity_field: tid
-          plugin_id: field
-        tid_1:
-          id: tid_1
-          table: taxonomy_term_field_data
-          field: tid
-          relationship: parent
+        field_test_int:
+          id: field_test_int
+          table: node__field_test_int
+          field: field_test_int
+          relationship: none
           group_type: group
           admin_label: ''
           label: ''
@@ -151,52 +146,121 @@ display:
           multi_type: separator
           separator: ', '
           field_api_classes: false
-          entity_type: taxonomy_term
-          entity_field: tid
           plugin_id: field
-        nid:
-          id: nid
+        field_test_int_1:
+          id: field_test_int_1
+          table: node__field_test_int
+          field: field_test_int
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: number_integer
+          settings:
+            thousand_separator: ''
+            prefix_suffix: false
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          plugin_id: field
+      filters:
+        status:
+          value: true
           table: node_field_data
-          field: nid
+          field: status
+          plugin_id: boolean
           entity_type: node
-          entity_field: nid
-          plugin_id: field
-          relationship: nid
-      filters: {  }
+          entity_field: status
+          id: status
+          expose:
+            operator: ''
+          group: 1
+        type:
+          id: type
+          table: node_field_data
+          field: type
+          value:
+            number_test: number_test
+          entity_type: node
+          entity_field: type
+          plugin_id: bundle
       sorts:
-        tid:
-          id: tid
-          table: taxonomy_term_field_data
-          field: tid
-          entity_type: taxonomy_term
-          entity_field: tid
-          plugin_id: standard
+        created:
+          id: created
+          table: node_field_data
+          field: created
+          order: DESC
+          entity_type: node
+          entity_field: created
+          plugin_id: date
+          relationship: none
+          group_type: group
+          admin_label: ''
+          exposed: false
+          expose:
+            label: ''
+          granularity: second
       header: {  }
       footer: {  }
       empty: {  }
-      relationships:
-        nid:
-          id: nid
-          table: taxonomy_index
-          field: nid
-          relationship: none
-          group_type: group
-          admin_label: node
-          required: false
-          plugin_id: standard
-        parent:
-          id: parent
-          table: taxonomy_term_hierarchy
-          field: parent
-          relationship: none
-          group_type: group
-          admin_label: Parent
-          required: false
-          plugin_id: standard
+      relationships: {  }
       arguments: {  }
       display_extenders: {  }
     cache_metadata:
+      max-age: -1
       contexts:
         - 'languages:language_content'
         - 'languages:language_interface'
-      max-age: 0
+        - url.query_args
+        - 'user.node_grants:view'
+        - user.permissions
+      tags:
+        - 'config:field.storage.node.field_test_int'
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml
index 80295b6..c255b1c 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_term_relationship.yml
@@ -140,7 +140,7 @@ display:
           type: number_integer
           settings:
             thousand_separator: ''
-            prefix_suffix: true
+            format_plural: 'field'
           group_column: value
           group_columns: {  }
           group_rows: true
diff --git a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php
index cf76b88..4e683a6 100644
--- a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php
+++ b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php
@@ -192,6 +192,7 @@ protected function buildFields(array $values) {
 
     if ($values && ($field_ids = $this->getRenderableFieldIds())) {
       $entity_type_id = $this->getEntityTypeId();
+      $settings_langcode = $this->view->storage->language()->getId();
 
       // Collect the entities for the relationship, fetch the right translation,
       // and group by bundle. For each result row, the corresponding entity can
@@ -228,6 +229,7 @@ protected function buildFields(array $values) {
             'targetEntityType' => $entity_type_id,
             'bundle' => $bundle,
             'status' => TRUE,
+            'langcode' => $settings_langcode,
           ]);
           foreach ($display_fields['field_ids'] as $field) {
             $display->setComponent($field->definition['field_name'], [
diff --git a/core/modules/views/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php
index 6d318c4..81ea964 100644
--- a/core/modules/views/src/Plugin/views/field/Field.php
+++ b/core/modules/views/src/Plugin/views/field/Field.php
@@ -470,6 +470,13 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
       $form['field_api_classes']['#description'] .= ' ' . $this->t('Checking this option will cause the group Display Type and Separator values to be ignored.');
     }
 
+    // Get the currently selected formatter.
+    $format = $this->options['type'];
+
+    $settings = $this->options['settings'] + $this->formatterPluginManager->getDefaultSettings($format);
+    $settings_langcode = $this->view->storage->language()->getId();
+    $settings['_settings_langcode'] = $settings_langcode;
+
     // Get the settings form.
     $settings_form = array('#value' => array());
     if ($formatter = $this->getFormatterInstance()) {
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml
index 2a79e74..27442e7 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_row_render_cache.yml
@@ -180,7 +180,7 @@ display:
           type: number_integer
           settings:
             thousand_separator: ''
-            prefix_suffix: true
+            format_plural: 'field'
           group_column: value
           group_columns: {  }
           group_rows: true
