Skip to content

Commit 53fb650

Browse files
committed
Make file size messages human-readable
1 parent 92acabd commit 53fb650

6 files changed

Lines changed: 327 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## 2.6.1 under development
44

5-
- no changes in this release.
5+
- Bug #802: Use translatable human-readable file sizes in `File` validation messages and add raw byte placeholders (@samdark)
66

77
## 2.6.0 June 02, 2026
88

docs/guide/en/built-in-rules-file.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ This option should be used with care because the client can send any media type
7979
For filesystem-backed uploads, size checks use the actual file size on disk. For pathless streams, size checks use the
8080
PSR-7 upload size when available. If a size constraint is configured and the size can't be determined, validation fails.
8181

82+
Default size error messages use translatable human-readable units, for example `50 MB` when `maxSize` is
83+
`50 * 1024 * 1024`.
84+
In custom messages, `{limit}` and `{exactly}` contain the numeric value converted to a human-readable unit, while
85+
`{limitUnit}` and `{exactlyUnit}` contain the unit identifier. Use `{limitBytes}` or `{exactlyBytes}` when a custom
86+
message needs the raw byte value for ICU number or plural formatting.
87+
8288
`size` is mutually exclusive with `minSize` and `maxSize`. When both `minSize` and `maxSize` are set, `minSize` must be
8389
less than or equal to `maxSize`.
8490

messages/ru/yii-validator.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Yiisoft\Validator\Rule\Each;
1010
use Yiisoft\Validator\Rule\Email;
1111
use Yiisoft\Validator\Rule\Equal;
12+
use Yiisoft\Validator\Rule\File;
1213
use Yiisoft\Validator\Rule\FilledAtLeast;
1314
use Yiisoft\Validator\Rule\FilledOnlyOneOf;
1415
use Yiisoft\Validator\Rule\GreaterThan;
@@ -196,6 +197,44 @@
196197
/** @see AnyRule */
197198
'At least one of the inner rules must pass the validation.' => 'Как минимум одно из внутренних правил должно пройти валидацию.',
198199

200+
/** @see File */
201+
'The size of {property} must be exactly {exactly, number} {exactly, plural, one{byte} other{bytes}}.' =>
202+
'Размер {property} должен быть ровно {exactly, number} {exactly, plural, one{байт} few{байта} many{байтов} other{байта}}.',
203+
'The size of {property} must be exactly {exactly, number} KB.' =>
204+
'Размер {property} должен быть ровно {exactly, number} КБ.',
205+
'The size of {property} must be exactly {exactly, number} MB.' =>
206+
'Размер {property} должен быть ровно {exactly, number} МБ.',
207+
'The size of {property} must be exactly {exactly, number} GB.' =>
208+
'Размер {property} должен быть ровно {exactly, number} ГБ.',
209+
'The size of {property} must be exactly {exactly, number} TB.' =>
210+
'Размер {property} должен быть ровно {exactly, number} ТБ.',
211+
'The size of {property} must be exactly {exactly, number} PB.' =>
212+
'Размер {property} должен быть ровно {exactly, number} ПБ.',
213+
'The size of {property} cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' =>
214+
'Размер {property} не может быть меньше {limit, number} {limit, plural, one{байта} few{байтов} many{байтов} other{байта}}.',
215+
'The size of {property} cannot be smaller than {limit, number} KB.' =>
216+
'Размер {property} не может быть меньше {limit, number} КБ.',
217+
'The size of {property} cannot be smaller than {limit, number} MB.' =>
218+
'Размер {property} не может быть меньше {limit, number} МБ.',
219+
'The size of {property} cannot be smaller than {limit, number} GB.' =>
220+
'Размер {property} не может быть меньше {limit, number} ГБ.',
221+
'The size of {property} cannot be smaller than {limit, number} TB.' =>
222+
'Размер {property} не может быть меньше {limit, number} ТБ.',
223+
'The size of {property} cannot be smaller than {limit, number} PB.' =>
224+
'Размер {property} не может быть меньше {limit, number} ПБ.',
225+
'The size of {property} cannot be larger than {limit, number} {limit, plural, one{byte} other{bytes}}.' =>
226+
'Размер {property} не может быть больше {limit, number} {limit, plural, one{байта} few{байтов} many{байтов} other{байта}}.',
227+
'The size of {property} cannot be larger than {limit, number} KB.' =>
228+
'Размер {property} не может быть больше {limit, number} КБ.',
229+
'The size of {property} cannot be larger than {limit, number} MB.' =>
230+
'Размер {property} не может быть больше {limit, number} МБ.',
231+
'The size of {property} cannot be larger than {limit, number} GB.' =>
232+
'Размер {property} не может быть больше {limit, number} ГБ.',
233+
'The size of {property} cannot be larger than {limit, number} TB.' =>
234+
'Размер {property} не может быть больше {limit, number} ТБ.',
235+
'The size of {property} cannot be larger than {limit, number} PB.' =>
236+
'Размер {property} не может быть больше {limit, number} ПБ.',
237+
199238
/** @see Image */
200239
'{Property} must be an image.' => '{Property} должно быть изображением.',
201240
'The width of {property} must be exactly {exactly, number} {exactly, plural, one{pixel} other{pixels}}.' =>

src/Rule/File.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,23 +127,29 @@ final class File implements DumpedRuleInterface, SkipOnErrorInterface, WhenInter
127127
* - `{property}`: the translated label of the property being validated.
128128
* - `{Property}`: the translated label of the property being validated, capitalized.
129129
* - `{file}`: the validated file name when it is available.
130-
* - `{exactly}`: expected exact size in bytes.
130+
* - `{exactly}`: expected exact size value converted to a human-readable unit.
131+
* - `{exactlyUnit}`: expected exact size unit identifier. Possible values are `byte`, `KB`, `MB`, `GB`, `TB`, `PB`.
132+
* - `{exactlyBytes}`: expected exact size in bytes.
131133
* @param string $tooSmallMessage A message used when the file size is less than {@see $minSize}.
132134
*
133135
* You may use the following placeholders in the message:
134136
*
135137
* - `{property}`: the translated label of the property being validated.
136138
* - `{Property}`: the translated label of the property being validated, capitalized.
137139
* - `{file}`: the validated file name when it is available.
138-
* - `{limit}`: expected minimum size in bytes.
140+
* - `{limit}`: expected minimum size value converted to a human-readable unit.
141+
* - `{limitUnit}`: expected minimum size unit identifier. Possible values are `byte`, `KB`, `MB`, `GB`, `TB`, `PB`.
142+
* - `{limitBytes}`: expected minimum size in bytes.
139143
* @param string $tooBigMessage A message used when the file size is greater than {@see $maxSize}.
140144
*
141145
* You may use the following placeholders in the message:
142146
*
143147
* - `{property}`: the translated label of the property being validated.
144148
* - `{Property}`: the translated label of the property being validated, capitalized.
145149
* - `{file}`: the validated file name when it is available.
146-
* - `{limit}`: expected maximum size in bytes.
150+
* - `{limit}`: expected maximum size value converted to a human-readable unit.
151+
* - `{limitUnit}`: expected maximum size unit identifier. Possible values are `byte`, `KB`, `MB`, `GB`, `TB`, `PB`.
152+
* - `{limitBytes}`: expected maximum size in bytes.
147153
* @param string $unableToDetermineSizeMessage A message used when file size constraints are configured, but the
148154
* file size can't be determined.
149155
*

src/Rule/FileHandler.php

Lines changed: 141 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use function is_string;
2222
use function mime_content_type;
2323
use function pathinfo;
24+
use function round;
2425
use function str_contains;
2526
use function str_ends_with;
2627
use function str_replace;
@@ -51,6 +52,19 @@
5152
*/
5253
final class FileHandler implements RuleHandlerInterface
5354
{
55+
private const DEFAULT_NOT_EXACT_SIZE_MESSAGES = [
56+
'The size of {property} must be exactly {exactly, number} {exactly, plural, one{byte} other{bytes}}.',
57+
'The size of {property} must be exactly {exactly}.',
58+
];
59+
private const DEFAULT_TOO_SMALL_MESSAGES = [
60+
'The size of {property} cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.',
61+
'The size of {property} cannot be smaller than {limit}.',
62+
];
63+
private const DEFAULT_TOO_BIG_MESSAGES = [
64+
'The size of {property} cannot be larger than {limit, number} {limit, plural, one{byte} other{bytes}}.',
65+
'The size of {property} cannot be larger than {limit}.',
66+
];
67+
5468
public function validate(mixed $value, RuleInterface $rule, ValidationContext $context): Result
5569
{
5670
if (!$rule instanceof File) {
@@ -279,27 +293,148 @@ private function validateSize(array $file, File $rule, ValidationContext $contex
279293
}
280294

281295
if ($rule->getSize() !== null && $size !== $rule->getSize()) {
296+
$ruleSize = $rule->getSize();
297+
$sizeParameters = $this->getSizeParameters($ruleSize);
282298
$result->addError(
283-
$rule->getNotExactSizeMessage(),
284-
$this->getParameters($context, $file, ['exactly' => $rule->getSize()]),
299+
$this->getNotExactSizeMessage($rule, $sizeParameters['unit']),
300+
$this->getParameters(
301+
$context,
302+
$file,
303+
[
304+
'exactly' => $sizeParameters['value'],
305+
'exactlyUnit' => $sizeParameters['unit'],
306+
'exactlyBytes' => $sizeParameters['bytes'],
307+
],
308+
),
285309
);
286310
}
287311

288312
if ($rule->getMinSize() !== null && $size < $rule->getMinSize()) {
313+
$minSize = $rule->getMinSize();
314+
$sizeParameters = $this->getSizeParameters($minSize);
289315
$result->addError(
290-
$rule->getTooSmallMessage(),
291-
$this->getParameters($context, $file, ['limit' => $rule->getMinSize()]),
316+
$this->getTooSmallMessage($rule, $sizeParameters['unit']),
317+
$this->getParameters(
318+
$context,
319+
$file,
320+
[
321+
'limit' => $sizeParameters['value'],
322+
'limitUnit' => $sizeParameters['unit'],
323+
'limitBytes' => $sizeParameters['bytes'],
324+
],
325+
),
292326
);
293327
}
294328

295329
if ($rule->getMaxSize() !== null && $size > $rule->getMaxSize()) {
330+
$maxSize = $rule->getMaxSize();
331+
$sizeParameters = $this->getSizeParameters($maxSize);
296332
$result->addError(
297-
$rule->getTooBigMessage(),
298-
$this->getParameters($context, $file, ['limit' => $rule->getMaxSize()]),
333+
$this->getTooBigMessage($rule, $sizeParameters['unit']),
334+
$this->getParameters(
335+
$context,
336+
$file,
337+
[
338+
'limit' => $sizeParameters['value'],
339+
'limitUnit' => $sizeParameters['unit'],
340+
'limitBytes' => $sizeParameters['bytes'],
341+
],
342+
),
299343
);
300344
}
301345
}
302346

347+
private function getNotExactSizeMessage(File $rule, string $unit): string
348+
{
349+
$message = $rule->getNotExactSizeMessage();
350+
if (!in_array($message, self::DEFAULT_NOT_EXACT_SIZE_MESSAGES, true)) {
351+
return $message;
352+
}
353+
354+
return match ($unit) {
355+
'byte' => 'The size of {property} must be exactly {exactly, number} '
356+
. '{exactly, plural, one{byte} other{bytes}}.',
357+
'KB' => 'The size of {property} must be exactly {exactly, number} KB.',
358+
'MB' => 'The size of {property} must be exactly {exactly, number} MB.',
359+
'GB' => 'The size of {property} must be exactly {exactly, number} GB.',
360+
'TB' => 'The size of {property} must be exactly {exactly, number} TB.',
361+
'PB' => 'The size of {property} must be exactly {exactly, number} PB.',
362+
default => $message,
363+
};
364+
}
365+
366+
private function getTooSmallMessage(File $rule, string $unit): string
367+
{
368+
$message = $rule->getTooSmallMessage();
369+
if (!in_array($message, self::DEFAULT_TOO_SMALL_MESSAGES, true)) {
370+
return $message;
371+
}
372+
373+
return match ($unit) {
374+
'byte' => 'The size of {property} cannot be smaller than {limit, number} '
375+
. '{limit, plural, one{byte} other{bytes}}.',
376+
'KB' => 'The size of {property} cannot be smaller than {limit, number} KB.',
377+
'MB' => 'The size of {property} cannot be smaller than {limit, number} MB.',
378+
'GB' => 'The size of {property} cannot be smaller than {limit, number} GB.',
379+
'TB' => 'The size of {property} cannot be smaller than {limit, number} TB.',
380+
'PB' => 'The size of {property} cannot be smaller than {limit, number} PB.',
381+
default => $message,
382+
};
383+
}
384+
385+
private function getTooBigMessage(File $rule, string $unit): string
386+
{
387+
$message = $rule->getTooBigMessage();
388+
if (!in_array($message, self::DEFAULT_TOO_BIG_MESSAGES, true)) {
389+
return $message;
390+
}
391+
392+
return match ($unit) {
393+
'byte' => 'The size of {property} cannot be larger than {limit, number} '
394+
. '{limit, plural, one{byte} other{bytes}}.',
395+
'KB' => 'The size of {property} cannot be larger than {limit, number} KB.',
396+
'MB' => 'The size of {property} cannot be larger than {limit, number} MB.',
397+
'GB' => 'The size of {property} cannot be larger than {limit, number} GB.',
398+
'TB' => 'The size of {property} cannot be larger than {limit, number} TB.',
399+
'PB' => 'The size of {property} cannot be larger than {limit, number} PB.',
400+
default => $message,
401+
};
402+
}
403+
404+
/**
405+
* @return array{value: int|float, unit: string, bytes: int}
406+
*/
407+
private function getSizeParameters(int $size): array
408+
{
409+
if ($size < 1024) {
410+
return [
411+
'value' => $size,
412+
'unit' => 'byte',
413+
'bytes' => $size,
414+
];
415+
}
416+
417+
$value = (float) $size;
418+
foreach (['KB', 'MB', 'GB', 'TB'] as $unit) {
419+
$value /= 1024;
420+
$roundedValue = round($value, 2);
421+
if ($roundedValue < 1024) {
422+
return [
423+
'value' => $roundedValue,
424+
'unit' => $unit,
425+
'bytes' => $size,
426+
];
427+
}
428+
}
429+
430+
$value /= 1024;
431+
return [
432+
'value' => round($value, 2),
433+
'unit' => 'PB',
434+
'bytes' => $size,
435+
];
436+
}
437+
303438
/**
304439
* @psalm-param FileData $file
305440
*/

0 commit comments

Comments
 (0)