From a9f3f72e03183c7ba9917309361a919db7cd5913 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Fri, 15 Sep 2023 08:19:10 +0100
Subject: [PATCH] Issue #2157945 by mondrake, andypost, kim.pepper, Bhanu951,
 alexpott, martin107, plach, ankithashetty, dawehner, karishmaamin, voleger,
 jhedstrom, jofitz, kostyashupenko, shashikant_chauhan, longwave,
 zeeshan_khan, claudiu.cristea, mstrelan, Berdir: Deprecate format_size() and
 use Drupal\Core\StringTranslation\ByteSizeMarkup instead

---
 core/includes/common.inc                      | 51 +++---------
 .../EventSubscriber/FormAjaxSubscriber.php    | 11 ++-
 .../Core/StringTranslation/ByteSizeMarkup.php | 59 ++++++++++++++
 core/modules/editor/editor.admin.inc          |  3 +-
 core/modules/file/file.module                 | 21 +++--
 .../Controller/FileWidgetAjaxController.php   |  7 +-
 .../Plugin/Field/FieldFormatter/FileSize.php  |  3 +-
 .../Field/FieldFormatter/TableFormatter.php   |  3 +-
 .../src/Plugin/Field/FieldType/FileItem.php   |  7 +-
 .../FileSizeLimitConstraintValidator.php      |  9 ++-
 .../src/Functional/FileFieldValidateTest.php  | 14 +++-
 .../src/Functional/FileTokenReplaceTest.php   |  5 +-
 .../src/Kernel/FileUploadHandlerTest.php      |  5 +-
 .../modules/migrate/src/MigrateExecutable.php | 20 +++--
 .../tests/src/Unit/TestMigrateExecutable.php  |  7 --
 core/modules/system/system.install            |  5 +-
 .../views/src/Plugin/views/field/FileSize.php |  3 +-
 .../demo_umami/themes/umami/umami.theme       |  3 +-
 .../Core/Common/LegacyCommonTest.php          | 25 ++++++
 .../KernelTests/Core/Common/SizeTest.php      | 59 --------------
 .../FormAjaxSubscriberTest.php                | 15 +---
 .../StringTranslation/ByteSizeMarkupTest.php  | 79 +++++++++++++++++++
 22 files changed, 255 insertions(+), 159 deletions(-)
 create mode 100644 core/lib/Drupal/Core/StringTranslation/ByteSizeMarkup.php
 create mode 100644 core/tests/Drupal/KernelTests/Core/Common/LegacyCommonTest.php
 delete mode 100644 core/tests/Drupal/KernelTests/Core/Common/SizeTest.php
 create mode 100644 core/tests/Drupal/Tests/Core/StringTranslation/ByteSizeMarkupTest.php

diff --git a/core/includes/common.inc b/core/includes/common.inc
index 9d1f0781306e..cabd3ceeb8f4 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -8,11 +8,10 @@
  * a cached page are instead located in bootstrap.inc.
  */
 
-use Drupal\Component\Utility\Bytes;
 use Drupal\Component\Utility\SortArray;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\DrupalKernel;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 
 /**
  * @defgroup php_wrappers PHP wrapper functions
@@ -132,48 +131,16 @@
  *
  * @return \Drupal\Core\StringTranslation\TranslatableMarkup
  *   A translated string representation of the size.
+ *
+ * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+ *   \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode)
+ *   instead.
+ *
+ * @see https://www.drupal.org/node/2999981
  */
 function format_size($size, $langcode = NULL) {
-  $absolute_size = abs($size);
-  if ($absolute_size < Bytes::KILOBYTE) {
-    return \Drupal::translation()->formatPlural($size, '1 byte', '@count bytes', [], ['langcode' => $langcode]);
-  }
-  // Create a multiplier to preserve the sign of $size.
-  $sign = $absolute_size / $size;
-  foreach (['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] as $unit) {
-    $absolute_size /= Bytes::KILOBYTE;
-    $rounded_size = round($absolute_size, 2);
-    if ($rounded_size < Bytes::KILOBYTE) {
-      break;
-    }
-  }
-  $args = ['@size' => $rounded_size * $sign];
-  $options = ['langcode' => $langcode];
-  switch ($unit) {
-    case 'KB':
-      return new TranslatableMarkup('@size KB', $args, $options);
-
-    case 'MB':
-      return new TranslatableMarkup('@size MB', $args, $options);
-
-    case 'GB':
-      return new TranslatableMarkup('@size GB', $args, $options);
-
-    case 'TB':
-      return new TranslatableMarkup('@size TB', $args, $options);
-
-    case 'PB':
-      return new TranslatableMarkup('@size PB', $args, $options);
-
-    case 'EB':
-      return new TranslatableMarkup('@size EB', $args, $options);
-
-    case 'ZB':
-      return new TranslatableMarkup('@size ZB', $args, $options);
-
-    case 'YB':
-      return new TranslatableMarkup('@size YB', $args, $options);
-  }
+  @trigger_error('format_size() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode) instead. See https://www.drupal.org/node/2999981', E_USER_DEPRECATED);
+  return ByteSizeMarkup::create($size ?? 0, $langcode);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php b/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php
index 75b12bf30f3f..92fa49541754 100644
--- a/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php
+++ b/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Form\FormAjaxResponseBuilderInterface;
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -86,7 +87,9 @@ public function onException(ExceptionEvent $event) {
     // Render a nice error message in case we have a file upload which exceeds
     // the configured upload limit.
     if ($exception instanceof BrokenPostRequestException && $request->query->has(FormBuilderInterface::AJAX_FORM_REQUEST)) {
-      $this->messenger->addError($this->t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', ['@size' => $this->formatSize($exception->getSize())]));
+      $this->messenger->addError($this->t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', [
+        '@size' => ByteSizeMarkup::create((int) $exception->getSize()),
+      ]));
       $response = new AjaxResponse(NULL, 200);
       $status_messages = ['#type' => 'status_messages'];
       $response->addCommand(new PrependCommand(NULL, $status_messages));
@@ -148,8 +151,14 @@ protected function getFormAjaxException(\Throwable $e) {
    *
    * @return string
    *   The formatted size.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   \Drupal\Core\StringTranslation\ByteSizeMarkup::create() instead.
+   *
+   * @see https://www.drupal.org/node/2999981
    */
   protected function formatSize($size) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\StringTranslation\ByteSizeMarkup::create() instead. See https://www.drupal.org/node/2999981', E_USER_DEPRECATED);
     return format_size($size);
   }
 
diff --git a/core/lib/Drupal/Core/StringTranslation/ByteSizeMarkup.php b/core/lib/Drupal/Core/StringTranslation/ByteSizeMarkup.php
new file mode 100644
index 000000000000..766904e4daf1
--- /dev/null
+++ b/core/lib/Drupal/Core/StringTranslation/ByteSizeMarkup.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\StringTranslation;
+
+use Drupal\Component\Utility\Bytes;
+
+/**
+ * A class to generate translatable markup for the given byte count.
+ */
+final class ByteSizeMarkup {
+
+  /**
+   * This class should not be instantiated.
+   */
+  private function __construct() {}
+
+  /**
+   * Gets the TranslatableMarkup object for the provided size.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+   *   The translatable markup.
+   *
+   * @throws \LogicException
+   *   Thrown when an invalid unit size is used.
+   */
+  public static function create(float|int $size, string $langcode = NULL, TranslationInterface $stringTranslation = NULL): TranslatableMarkup {
+    $options = ['langcode' => $langcode];
+    $absolute_size = abs($size);
+    if ($absolute_size < Bytes::KILOBYTE) {
+      return new PluralTranslatableMarkup($size, '1 byte', '@count bytes', [], $options, $stringTranslation);
+    }
+    // Create a multiplier to preserve the sign of $size.
+    $sign = $absolute_size / $size;
+    foreach (['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] as $unit) {
+      $absolute_size /= Bytes::KILOBYTE;
+      $rounded_size = round($absolute_size, 2);
+      if ($rounded_size < Bytes::KILOBYTE) {
+        break;
+      }
+    }
+
+    $args = ['@size' => $rounded_size * $sign];
+    // At this point $markup must be set.
+    return match ($unit) {
+      'KB' => new TranslatableMarkup('@size KB', $args, $options, $stringTranslation),
+      'MB' => new TranslatableMarkup('@size MB', $args, $options, $stringTranslation),
+      'GB' => new TranslatableMarkup('@size GB', $args, $options, $stringTranslation),
+      'TB' => new TranslatableMarkup('@size TB', $args, $options, $stringTranslation),
+      'PB' => new TranslatableMarkup('@size PB', $args, $options, $stringTranslation),
+      'EB' => new TranslatableMarkup('@size EB', $args, $options, $stringTranslation),
+      'ZB' => new TranslatableMarkup('@size ZB', $args, $options, $stringTranslation),
+      'YB' => new TranslatableMarkup('@size YB', $args, $options, $stringTranslation),
+      default => throw new \LogicException("Unexpected unit value"),
+    };
+  }
+
+}
diff --git a/core/modules/editor/editor.admin.inc b/core/modules/editor/editor.admin.inc
index 6a474689d439..b8245e7df1bf 100644
--- a/core/modules/editor/editor.admin.inc
+++ b/core/modules/editor/editor.admin.inc
@@ -7,6 +7,7 @@
 
 use Drupal\Component\Utility\Environment;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\editor\Entity\Editor;
 
 /**
@@ -78,7 +79,7 @@ function editor_image_upload_settings_form(Editor $editor) {
     '#states' => $show_if_image_uploads_enabled,
   ];
 
-  $default_max_size = format_size(Environment::getUploadMaxSize());
+  $default_max_size = ByteSizeMarkup::create(Environment::getUploadMaxSize());
   $form['max_size'] = [
     '#type' => 'textfield',
     '#default_value' => $image_upload['max_size'],
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 840850806999..b07fe158eec2 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -22,6 +22,7 @@
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
 use Drupal\file\Entity\File;
@@ -199,12 +200,18 @@ function file_validate_size(FileInterface $file, $file_limit = 0, $user_limit =
   $errors = [];
 
   if ($file_limit && $file->getSize() > $file_limit) {
-    $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', ['%filesize' => format_size($file->getSize()), '%maxsize' => format_size($file_limit)]);
+    $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', [
+      '%filesize' => ByteSizeMarkup::create($file->getSize()),
+      '%maxsize' => ByteSizeMarkup::create($file_limit),
+    ]);
   }
 
   // Save a query by only calling spaceUsed() when a limit is provided.
   if ($user_limit && (\Drupal::entityTypeManager()->getStorage('file')->spaceUsed($user->id()) + $file->getSize()) > $user_limit) {
-    $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', ['%filesize' => format_size($file->getSize()), '%quota' => format_size($user_limit)]);
+    $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', [
+      '%filesize' => ByteSizeMarkup::create($file->getSize()),
+      '%quota' => ByteSizeMarkup::create($user_limit),
+    ]);
   }
 
   return $errors;
@@ -673,7 +680,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
     catch (IniSizeFileException | FormSizeFileException $e) {
       \Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', [
         '%file' => $uploaded_file->getFilename(),
-        '%maxsize' => format_size(Environment::getUploadMaxSize()),
+        '%maxsize' => ByteSizeMarkup::create(Environment::getUploadMaxSize()),
       ]));
       $files[$i] = FALSE;
     }
@@ -790,7 +797,7 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta
           break;
 
         case 'size':
-          $replacements[$original] = format_size($file->getSize());
+          $replacements[$original] = ByteSizeMarkup::create($file->getSize());
           break;
 
         case 'url':
@@ -1080,7 +1087,7 @@ function template_preprocess_file_link(&$variables) {
   // Set file classes to the options array.
   $variables['attributes'] = new Attribute($variables['attributes']);
   $variables['attributes']->addClass($classes);
-  $variables['file_size'] = $file->getSize() !== NULL ? format_size($file->getSize()) : '';
+  $variables['file_size'] = $file->getSize() !== NULL ? ByteSizeMarkup::create($file->getSize()) : '';
 
   $variables['link'] = Link::fromTextAndUrl($link_text, $url->mergeOptions($options))->toRenderable();
 }
@@ -1230,10 +1237,10 @@ function template_preprocess_file_upload_help(&$variables) {
   }
   if (isset($upload_validators['file_validate_size'])) {
     @trigger_error('\'file_validate_size\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileSizeLimit\' constraint instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
-    $descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['file_validate_size'][0])]);
+    $descriptions[] = t('@size limit.', ['@size' => ByteSizeMarkup::create($upload_validators['file_validate_size'][0])]);
   }
   if (isset($upload_validators['FileSizeLimit'])) {
-    $descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['FileSizeLimit']['fileLimit'])]);
+    $descriptions[] = t('@size limit.', ['@size' => ByteSizeMarkup::create($upload_validators['FileSizeLimit']['fileLimit'])]);
   }
 
   if (isset($upload_validators['file_validate_extensions'])) {
diff --git a/core/modules/file/src/Controller/FileWidgetAjaxController.php b/core/modules/file/src/Controller/FileWidgetAjaxController.php
index 992fcb174c23..098337887a94 100644
--- a/core/modules/file/src/Controller/FileWidgetAjaxController.php
+++ b/core/modules/file/src/Controller/FileWidgetAjaxController.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\file\Controller;
 
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Symfony\Component\HttpFoundation\JsonResponse;
 
@@ -30,9 +31,9 @@ public function progress($key) {
     if ($implementation == 'uploadprogress') {
       $status = uploadprogress_get_info($key);
       if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) {
-        $progress['message'] = $this->t('Uploading... (@current of @total)', [
-          '@current' => format_size($status['bytes_uploaded']),
-          '@total' => format_size($status['bytes_total']),
+        $progress['message'] = t('Uploading... (@current of @total)', [
+          '@current' => ByteSizeMarkup::create($status['bytes_uploaded']),
+          '@total' => ByteSizeMarkup::create($status['bytes_total']),
         ]);
         $progress['percentage'] = round(100 * $status['bytes_uploaded'] / $status['bytes_total']);
       }
diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php b/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php
index 9bb080770a02..b0a9d3c3414d 100644
--- a/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php
+++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 
 /**
  * Formatter that shows the file size in a human readable way.
@@ -33,7 +34,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
     $elements = [];
 
     foreach ($items as $delta => $item) {
-      $elements[$delta] = ['#markup' => format_size($item->value)];
+      $elements[$delta] = ['#markup' => ByteSizeMarkup::create((int) $item->value)];
     }
 
     return $elements;
diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php
index e94156a6d365..b41a968b2eb4 100644
--- a/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php
+++ b/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php
@@ -3,6 +3,7 @@
 namespace Drupal\file\Plugin\Field\FieldFormatter;
 
 use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 
 /**
  * Plugin implementation of the 'file_table' formatter.
@@ -39,7 +40,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
               ],
             ],
           ],
-          ['data' => $file->getSize() !== NULL ? format_size($file->getSize()) : $this->t('Unknown')],
+          ['data' => $file->getSize() !== NULL ? ByteSizeMarkup::create($file->getSize()) : $this->t('Unknown')],
         ];
       }
 
diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
index 252bdf8bae42..b5947c9e5b70 100644
--- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
+++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\file\Plugin\Field\FieldType;
 
-use Drupal\Component\Utility\Bytes;
 use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Component\Utility\Bytes;
 use Drupal\Component\Utility\Environment;
 use Drupal\Component\Utility\Random;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -12,6 +12,7 @@
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\TypedData\DataDefinition;
 
@@ -191,7 +192,9 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
       '#type' => 'textfield',
       '#title' => $this->t('Maximum upload size'),
       '#default_value' => $settings['max_filesize'],
-      '#description' => $this->t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes could be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', ['%limit' => format_size(Environment::getUploadMaxSize())]),
+      '#description' => $this->t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes could be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', [
+        '%limit' => ByteSizeMarkup::create(Environment::getUploadMaxSize()),
+      ]),
       '#size' => 10,
       '#element_validate' => [[static::class, 'validateMaxFilesize']],
       '#weight' => 5,
diff --git a/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php b/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php
index 8bd0a1ad3719..d3886c1cff42 100644
--- a/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php
+++ b/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php
@@ -5,6 +5,7 @@
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@@ -50,8 +51,8 @@ public function validate(mixed $value, Constraint $constraint): void {
 
     if ($fileLimit && $file->getSize() > $fileLimit) {
       $this->context->addViolation($constraint->maxFileSizeMessage, [
-        '%filesize' => format_size($file->getSize()),
-        '%maxsize' => format_size($fileLimit),
+        '%filesize' => ByteSizeMarkup::create($file->getSize()),
+        '%maxsize' => ByteSizeMarkup::create($fileLimit),
       ]);
     }
 
@@ -64,8 +65,8 @@ public function validate(mixed $value, Constraint $constraint): void {
       $spaceUsed = $fileStorage->spaceUsed($this->currentUser->id()) + $file->getSize();
       if ($spaceUsed > $userLimit) {
         $this->context->addViolation($constraint->diskQuotaMessage, [
-          '%filesize' => format_size($file->getSize()),
-          '%quota' => format_size($userLimit),
+          '%filesize' => ByteSizeMarkup::create($file->getSize()),
+          '%quota' => ByteSizeMarkup::create($userLimit),
         ]);
       }
     }
diff --git a/core/modules/file/tests/src/Functional/FileFieldValidateTest.php b/core/modules/file/tests/src/Functional/FileFieldValidateTest.php
index 4cc0aaf88539..18967d329529 100644
--- a/core/modules/file/tests/src/Functional/FileFieldValidateTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldValidateTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\file\Entity\File;
 
@@ -103,12 +104,15 @@ public function testFileMaxSize() {
       $node = $node_storage->load($nid);
       $node_file = File::load($node->{$field_name}->target_id);
       $this->assertFileExists($node_file->getFileUri());
-      $this->assertFileEntryExists($node_file, new FormattableMarkup('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', ['%filesize' => format_size($small_file->getSize()), '%maxsize' => $max_filesize]));
+      $this->assertFileEntryExists($node_file, new FormattableMarkup('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', [
+        '%filesize' => ByteSizeMarkup::create($small_file->getSize()),
+        '%maxsize' => $max_filesize,
+      ]));
 
       // Check that uploading the large file fails (1M limit).
       $this->uploadNodeFile($large_file, $field_name, $type_name);
-      $filesize = format_size($large_file->getSize());
-      $maxsize = format_size($file_limit);
+      $filesize = ByteSizeMarkup::create($large_file->getSize());
+      $maxsize = ByteSizeMarkup::create($file_limit);
       $this->assertSession()->pageTextContains("The file is {$filesize} exceeding the maximum file size of {$maxsize}.");
     }
 
@@ -121,7 +125,9 @@ public function testFileMaxSize() {
     $node = $node_storage->load($nid);
     $node_file = File::load($node->{$field_name}->target_id);
     $this->assertFileExists($node_file->getFileUri());
-    $this->assertFileEntryExists($node_file, new FormattableMarkup('File entry exists after uploading a file (%filesize) with no max limit.', ['%filesize' => format_size($large_file->getSize())]));
+    $this->assertFileEntryExists($node_file, new FormattableMarkup('File entry exists after uploading a file (%filesize) with no max limit.', [
+      '%filesize' => ByteSizeMarkup::create($large_file->getSize()),
+    ]));
   }
 
   /**
diff --git a/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php b/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
index 67cb13cb2b67..0319a1448862 100644
--- a/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
+++ b/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\file\Entity\File;
 
 /**
@@ -54,7 +55,7 @@ public function testFileTokenReplacement() {
     $tests['[file:name]'] = Html::escape($file->getFilename());
     $tests['[file:path]'] = Html::escape($file->getFileUri());
     $tests['[file:mime]'] = Html::escape($file->getMimeType());
-    $tests['[file:size]'] = format_size($file->getSize());
+    $tests['[file:size]'] = ByteSizeMarkup::create($file->getSize());
     $tests['[file:url]'] = Html::escape($file->createFileUrl(FALSE));
     $tests['[file:created]'] = $date_formatter->format($file->getCreatedTime(), 'medium', '', NULL, $language_interface->getId());
     $tests['[file:created:short]'] = $date_formatter->format($file->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
@@ -95,7 +96,7 @@ public function testFileTokenReplacement() {
     $tests['[file:name]'] = $file->getFilename();
     $tests['[file:path]'] = $file->getFileUri();
     $tests['[file:mime]'] = $file->getMimeType();
-    $tests['[file:size]'] = format_size($file->getSize());
+    $tests['[file:size]'] = ByteSizeMarkup::create($file->getSize());
 
     foreach ($tests as $input => $expected) {
       $output = $token_service->replace($input, ['file' => $file], ['langcode' => $language_interface->getId(), 'sanitize' => FALSE]);
diff --git a/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php b/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
index c5f3362c8ecb..72c7fdb8f222 100644
--- a/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
+++ b/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\file\Kernel;
 
 use Drupal\Component\Utility\Environment;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\file\Upload\UploadedFileInterface;
 use Drupal\KernelTests\KernelTestBase;
 use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
@@ -42,9 +43,9 @@ public function testFileSaveUploadSingleErrorFormSize() {
     $file_info = $this->createMock(UploadedFileInterface::class);
     $file_info->expects($this->once())->method('getError')->willReturn(UPLOAD_ERR_FORM_SIZE);
     $file_info->expects($this->once())->method('getClientOriginalName')->willReturn($file_name);
-    $file_info->expects($this->once())->method('getErrorMessage')->willReturn(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, format_size(Environment::getUploadMaxSize())));
+    $file_info->expects($this->once())->method('getErrorMessage')->willReturn(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, ByteSizeMarkup::create(Environment::getUploadMaxSize())));
     $this->expectException(FormSizeFileException::class);
-    $this->expectExceptionMessage(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, format_size(Environment::getUploadMaxSize())));
+    $this->expectExceptionMessage(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, ByteSizeMarkup::create(Environment::getUploadMaxSize())));
     $this->fileUploadHandler->handleFileUpload($file_info);
   }
 
diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php
index 59aada1381ec..e95a1d5cb85d 100644
--- a/core/modules/migrate/src/MigrateExecutable.php
+++ b/core/modules/migrate/src/MigrateExecutable.php
@@ -3,6 +3,7 @@
 namespace Drupal\migrate;
 
 use Drupal\Component\Utility\Bytes;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\Utility\Error;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\migrate\Event\MigrateEvents;
@@ -537,8 +538,8 @@ protected function memoryExceeded() {
           'Memory usage is @usage (@pct% of limit @limit), reclaiming memory.',
           [
             '@pct' => round($pct_memory * 100),
-            '@usage' => $this->formatSize($usage),
-            '@limit' => $this->formatSize($this->memoryLimit),
+            '@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
+            '@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
           ]
         ),
         'warning'
@@ -553,8 +554,8 @@ protected function memoryExceeded() {
             'Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch',
             [
               '@pct' => round($pct_memory * 100),
-              '@usage' => $this->formatSize($usage),
-              '@limit' => $this->formatSize($this->memoryLimit),
+              '@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
+              '@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
             ]
           ),
           'warning'
@@ -567,8 +568,8 @@ protected function memoryExceeded() {
             'Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing',
             [
               '@pct' => round($pct_memory * 100),
-              '@usage' => $this->formatSize($usage),
-              '@limit' => $this->formatSize($this->memoryLimit),
+              '@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
+              '@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
             ]
           ),
           'warning');
@@ -620,8 +621,15 @@ protected function attemptMemoryReclaim() {
    *
    * @return string
    *   A translated string representation of the size.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode)
+   *   instead.
+   *
+   * @see https://www.drupal.org/node/2999981
    */
   protected function formatSize($size) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode) instead. See https://www.drupal.org/node/2999981', E_USER_DEPRECATED);
     return format_size($size);
   }
 
diff --git a/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php b/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
index ecd56ca9c844..0fe7d79a6957 100644
--- a/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
+++ b/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
@@ -123,11 +123,4 @@ public function setMemoryThreshold($threshold) {
     $this->memoryThreshold = $threshold;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function formatSize($size) {
-    return $size;
-  }
-
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 5684accadf2f..d89ca280b3fa 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -22,6 +22,7 @@
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PrivateStream;
 use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Url;
@@ -422,7 +423,7 @@ function system_requirements($phase) {
     $requirements['php_apcu_available']['title'] = t('PHP APCu available caching');
     if (extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) {
       $memory_info = apcu_sma_info(TRUE);
-      $apcu_actual_size = format_size($memory_info['seg_size']);
+      $apcu_actual_size = ByteSizeMarkup::create($memory_info['seg_size']);
       $apcu_recommended_size = '32 MB';
       $requirements['php_apcu_enabled']['value'] = t('Enabled (@size)', ['@size' => $apcu_actual_size]);
       if (Bytes::toNumber($apcu_actual_size) < Bytes::toNumber($apcu_recommended_size)) {
@@ -450,7 +451,7 @@ function system_requirements($phase) {
           $requirements['php_apcu_available']['severity'] = REQUIREMENT_OK;
         }
         $requirements['php_apcu_available']['value'] = t('Memory available: @available.', [
-          '@available' => format_size($memory_info['avail_mem']),
+          '@available' => ByteSizeMarkup::create($memory_info['avail_mem']),
         ]);
       }
     }
diff --git a/core/modules/views/src/Plugin/views/field/FileSize.php b/core/modules/views/src/Plugin/views/field/FileSize.php
index 5af1b94a3c40..79fdcadb4ddd 100644
--- a/core/modules/views/src/Plugin/views/field/FileSize.php
+++ b/core/modules/views/src/Plugin/views/field/FileSize.php
@@ -3,6 +3,7 @@
 namespace Drupal\views\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\views\ResultRow;
 
 /**
@@ -52,7 +53,7 @@ public function render(ResultRow $values) {
 
         case 'formatted':
         default:
-          return format_size($value);
+          return ByteSizeMarkup::create((int) $value);
       }
     }
     else {
diff --git a/core/profiles/demo_umami/themes/umami/umami.theme b/core/profiles/demo_umami/themes/umami/umami.theme
index 78b19feaa9f9..3775471a5923 100644
--- a/core/profiles/demo_umami/themes/umami/umami.theme
+++ b/core/profiles/demo_umami/themes/umami/umami.theme
@@ -7,6 +7,7 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\search\SearchPageInterface;
 use Drupal\views\Form\ViewsForm;
 use Drupal\Core\Render\Element;
@@ -150,7 +151,7 @@ function umami_form_alter(array &$form, FormStateInterface $form_state, $form_id
 function umami_preprocess_image_widget(&$variables) {
   if (!empty($variables['element']['fids']['#value'])) {
     $file = reset($variables['element']['#files']);
-    $variables['data']["file_{$file->id()}"]['filename']['#suffix'] = ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
+    $variables['data']["file_{$file->id()}"]['filename']['#suffix'] = ' <span class="file-size">(' . ByteSizeMarkup::create($file->getSize()) . ')</span> ';
   }
 }
 
diff --git a/core/tests/Drupal/KernelTests/Core/Common/LegacyCommonTest.php b/core/tests/Drupal/KernelTests/Core/Common/LegacyCommonTest.php
new file mode 100644
index 000000000000..b4ca44047242
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Common/LegacyCommonTest.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Common;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests deprecated legacy functions in common.inc.
+ *
+ * @group Common
+ * @group legacy
+ */
+class LegacyCommonTest extends KernelTestBase {
+
+  /**
+   * Tests deprecation of the format_size() function.
+   */
+  public function testFormatSizeDeprecation(): void {
+    $this->expectDeprecation('format_size() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode) instead. See https://www.drupal.org/node/2999981');
+    $size = format_size(4053371676);
+    $this->assertEquals('3.77 GB', $size);
+    $this->assertEquals('@size GB', $size->getUntranslatedString());
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Common/SizeTest.php b/core/tests/Drupal/KernelTests/Core/Common/SizeTest.php
deleted file mode 100644
index 77e70784ee34..000000000000
--- a/core/tests/Drupal/KernelTests/Core/Common/SizeTest.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-namespace Drupal\KernelTests\Core\Common;
-
-use Drupal\Component\Utility\Bytes;
-use Drupal\KernelTests\KernelTestBase;
-
-/**
- * Tests format_size().
- *
- * @group Common
- */
-class SizeTest extends KernelTestBase {
-
-  /**
-   * Checks that format_size() returns the expected string.
-   *
-   * @dataProvider providerTestCommonFormatSize
-   */
-  public function testCommonFormatSize($expected, $input) {
-    $size = format_size($input, NULL);
-    $this->assertEquals($expected, $size);
-  }
-
-  /**
-   * Provides a list of byte size to test.
-   */
-  public function providerTestCommonFormatSize() {
-    $kb = Bytes::KILOBYTE;
-    return [
-      ['0 bytes', 0],
-      ['1 byte', 1],
-      ['-1 bytes', -1],
-      ['2 bytes', 2],
-      ['-2 bytes', -2],
-      ['1023 bytes', $kb - 1],
-      ['1 KB', $kb],
-      ['1 MB', pow($kb, 2)],
-      ['1 GB', pow($kb, 3)],
-      ['1 TB', pow($kb, 4)],
-      ['1 PB', pow($kb, 5)],
-      ['1 EB', pow($kb, 6)],
-      ['1 ZB', pow($kb, 7)],
-      ['1 YB', pow($kb, 8)],
-      ['1024 YB', pow($kb, 9)],
-      // Rounded to 1 MB - not 1000 or 1024 kilobytes
-      ['1 MB', ($kb * $kb) - 1],
-      ['-1 MB', -(($kb * $kb) - 1)],
-      // Decimal Megabytes
-      ['3.46 MB', 3623651],
-      ['3.77 GB', 4053371676],
-      // Decimal Petabytes
-      ['59.72 PB', 67234178751368124],
-      // Decimal Yottabytes
-      ['194.67 YB', 235346823821125814962843827],
-    ];
-  }
-
-}
diff --git a/core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php b/core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php
index d1495335f354..fe3b81afbc73 100644
--- a/core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php
@@ -167,19 +167,8 @@ public function testOnExceptionBrokenPostRequest() {
     $this->messenger->expects($this->once())
       ->method('addError');
 
-    $this->subscriber = $this->getMockBuilder('\Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber')
-      ->setConstructorArgs([
-        $this->formAjaxResponseBuilder,
-        $this->getStringTranslationStub(),
-        $this->messenger,
-      ])
-      ->onlyMethods(['formatSize'])
-      ->getMock();
-
-    $this->subscriber->expects($this->once())
-      ->method('formatSize')
-      ->with(32 * 1e6)
-      ->willReturn('32M');
+    $this->subscriber = new FormAjaxSubscriber($this->formAjaxResponseBuilder, $this->getStringTranslationStub(), $this->messenger);
+
     $rendered_output = 'the rendered output';
     // CommandWithAttachedAssetsTrait::getRenderedContent() will call the
     // renderer service via the container.
diff --git a/core/tests/Drupal/Tests/Core/StringTranslation/ByteSizeMarkupTest.php b/core/tests/Drupal/Tests/Core/StringTranslation/ByteSizeMarkupTest.php
new file mode 100644
index 000000000000..a0eb2e40c030
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/StringTranslation/ByteSizeMarkupTest.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\Tests\Core\StringTranslation;
+
+use Drupal\Component\Utility\Bytes;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\StringTranslation\ByteSizeMarkup
+ * @group StringTranslation
+ */
+class ByteSizeMarkupTest extends UnitTestCase {
+
+  /**
+   * @covers ::create
+   * @dataProvider providerTestCommonFormatSize
+   */
+  public function testCommonFormatSize($expected, $input) {
+    $size = ByteSizeMarkup::create($input, NULL, $this->getStringTranslationStub());
+    $this->assertInstanceOf(TranslatableMarkup::class, $size);
+    $this->assertEquals($expected, $size);
+  }
+
+  /**
+   * Provides a list of byte size to test.
+   */
+  public function providerTestCommonFormatSize() {
+    $kb = Bytes::KILOBYTE;
+    return [
+      ['0 bytes', 0],
+      // @todo https://www.drupal.org/node/3161118 Prevent display of fractional
+      //   bytes for size less then 1KB.
+      ['0.1 bytes', 0.1],
+      ['0.6 bytes', 0.6],
+      ['1 byte', 1],
+      ['-1 bytes', -1],
+      ['2 bytes', 2],
+      ['-2 bytes', -2],
+      ['1023 bytes', $kb - 1],
+      ['1 KB', $kb],
+      ['1 MB', pow($kb, 2)],
+      ['1 GB', pow($kb, 3)],
+      ['1 TB', pow($kb, 4)],
+      ['1 PB', pow($kb, 5)],
+      ['1 EB', pow($kb, 6)],
+      ['1 ZB', pow($kb, 7)],
+      ['1 YB', pow($kb, 8)],
+      ['1024 YB', pow($kb, 9)],
+      // Rounded to 1 MB - not 1000 or 1024 kilobytes
+      ['1 MB', ($kb * $kb) - 1],
+      ['-1 MB', -(($kb * $kb) - 1)],
+      // Decimal Megabytes
+      ['3.46 MB', 3623651],
+      ['3.77 GB', 4053371676],
+      // Decimal Petabytes
+      ['59.72 PB', 67234178751368124],
+      // Decimal Yottabytes
+      ['194.67 YB', 235346823821125814962843827],
+    ];
+  }
+
+  /**
+   * @covers ::create
+   */
+  public function testTranslatableMarkupObject(): void {
+    $result = ByteSizeMarkup::create(1, NULL, $this->getStringTranslationStub());
+    $this->assertInstanceOf(PluralTranslatableMarkup::class, $result);
+    $this->assertEquals("1 byte\03@count bytes", $result->getUntranslatedString());
+
+    $result = ByteSizeMarkup::create(1048576, 'fr', $this->getStringTranslationStub());
+    $this->assertInstanceOf(TranslatableMarkup::class, $result);
+    $this->assertEquals("@size MB", $result->getUntranslatedString());
+    $this->assertEquals('fr', $result->getOption('langcode'));
+  }
+
+}
-- 
GitLab