From 60d637792937628fb8f8f478cf18961ae2410b25 Mon Sep 17 00:00:00 2001 From: Yibo Mao Date: Mon, 16 Feb 2026 14:08:12 -0500 Subject: [PATCH 1/4] Add French i18n support for naturalsize() --- src/humanize/filesize.py | 95 ++++++------------- .../locale/fr_FR/LC_MESSAGES/humanize.po | 31 ++++++ 2 files changed, 61 insertions(+), 65 deletions(-) diff --git a/src/humanize/filesize.py b/src/humanize/filesize.py index 13e5edd..03842f7 100644 --- a/src/humanize/filesize.py +++ b/src/humanize/filesize.py @@ -3,80 +3,32 @@ from __future__ import annotations from math import log +from humanize import i18n suffixes = { - "decimal": ( - " kB", - " MB", - " GB", - " TB", - " PB", - " EB", - " ZB", - " YB", - " RB", - " QB", - ), - "binary": ( - " KiB", - " MiB", - " GiB", - " TiB", - " PiB", - " EiB", - " ZiB", - " YiB", - " RiB", - " QiB", - ), + "decimal": ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB"), + "binary": ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"), "gnu": "KMGTPEZYRQ", } +def _translation(): + """Return active gettext translation (or None).""" + try: + return i18n.get_translation() + except Exception: + return None + + def naturalsize( value: float | str, binary: bool = False, gnu: bool = False, format: str = "%.1f", ) -> str: - """Format a number of bytes like a human-readable filesize (e.g. 10 kB). - - By default, decimal suffixes (kB, MB) are used. - - Non-GNU modes are compatible with jinja2's `filesizeformat` filter. - - Examples: - ```pycon - >>> naturalsize(3000000) - '3.0 MB' - >>> naturalsize(300, False, True) - '300B' - >>> naturalsize(3000, False, True) - '2.9K' - >>> naturalsize(3000, False, True, "%.3f") - '2.930K' - >>> naturalsize(3000, True) - '2.9 KiB' - >>> naturalsize(10**28) - '10.0 RB' - >>> naturalsize(10**34 * 3) - '30000.0 QB' - >>> naturalsize(-4096, True) - '-4.0 KiB' - - ``` + t = _translation() + _ = (t.gettext if t is not None else (lambda s: s)) - Args: - value (int, float, str): Integer to convert. - binary (bool): If `True`, uses binary suffixes (KiB, MiB) with base - 210 instead of 103. - gnu (bool): If `True`, the binary argument is ignored and GNU-style - (`ls -sh` style) prefixes are used (K, M) with the 2**10 definition. - format (str): Custom formatter. - - Returns: - str: Human readable representation of a filesize. - """ if gnu: suffix = suffixes["gnu"] elif binary: @@ -89,11 +41,24 @@ def naturalsize( abs_bytes = abs(bytes_) if abs_bytes == 1 and not gnu: - return f"{int(bytes_)} Byte" + return f"{int(bytes_)} {_('Byte')}" if abs_bytes < base: - return f"{int(bytes_)}B" if gnu else f"{int(bytes_)} Bytes" + if gnu: + return f"{int(bytes_)}B" + return f"{int(bytes_)} {_('Bytes')}" exp = int(min(log(abs_bytes, base), len(suffix))) - ret: str = format % (bytes_ / (base**exp)) + suffix[exp - 1] - return ret + number = format % (bytes_ / (base**exp)) + + # French decimal separator: use comma instead of dot + if t is not None: + lang = (t.info().get("language", "") or "").lower() + if lang.startswith("fr"): + number = number.replace(".", ",") + + if gnu: + return number + suffix[exp - 1] + + unit = _(suffix[exp - 1]) + return f"{number} {unit}" diff --git a/src/humanize/locale/fr_FR/LC_MESSAGES/humanize.po b/src/humanize/locale/fr_FR/LC_MESSAGES/humanize.po index 6aae582..d0a07fb 100644 --- a/src/humanize/locale/fr_FR/LC_MESSAGES/humanize.po +++ b/src/humanize/locale/fr_FR/LC_MESSAGES/humanize.po @@ -363,3 +363,34 @@ msgstr "hier" #, python-format msgid "%s and %s" msgstr "%s et %s" + +# --- filesize units (naturalsize) --- +msgid "Byte" +msgstr "octet" + +msgid "Bytes" +msgstr "octets" + +msgid "kB" +msgstr "Ko" + +msgid "MB" +msgstr "Mo" + +msgid "GB" +msgstr "Go" + +msgid "TB" +msgstr "To" + +msgid "PB" +msgstr "Po" + +msgid "EB" +msgstr "Eo" + +msgid "ZB" +msgstr "Zo" + +msgid "YB" +msgstr "Yo" From 79e8faf0fe3d75bee18ba047132e29489d725f18 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:24:20 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/humanize/filesize.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/humanize/filesize.py b/src/humanize/filesize.py index 03842f7..9e97ecf 100644 --- a/src/humanize/filesize.py +++ b/src/humanize/filesize.py @@ -3,6 +3,7 @@ from __future__ import annotations from math import log + from humanize import i18n suffixes = { @@ -27,7 +28,7 @@ def naturalsize( format: str = "%.1f", ) -> str: t = _translation() - _ = (t.gettext if t is not None else (lambda s: s)) + _ = t.gettext if t is not None else (lambda s: s) if gnu: suffix = suffixes["gnu"] From 918a6db6ee4fc259ba15391fc069bcece841bc31 Mon Sep 17 00:00:00 2001 From: Yibo Mao Date: Mon, 16 Feb 2026 14:56:05 -0500 Subject: [PATCH 3/4] Add docstring for naturalsize() --- src/humanize/filesize.py | 122 +++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/src/humanize/filesize.py b/src/humanize/filesize.py index 9e97ecf..272efff 100644 --- a/src/humanize/filesize.py +++ b/src/humanize/filesize.py @@ -4,62 +4,82 @@ from math import log -from humanize import i18n - -suffixes = { - "decimal": ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB"), - "binary": ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"), - "gnu": "KMGTPEZYRQ", +from humanize.i18n import gettext as _ + + +_SUFFIXES = { + "decimal": ( + _(" kB"), + _(" MB"), + _(" GB"), + _(" TB"), + _(" PB"), + _(" EB"), + _(" ZB"), + _(" YB"), + _(" RB"), + _(" QB"), + ), + "binary": ( + _(" KiB"), + _(" MiB"), + _(" GiB"), + _(" TiB"), + _(" PiB"), + _(" EiB"), + _(" ZiB"), + _(" YiB"), + _(" RiB"), + _(" QiB"), + ), } -def _translation(): - """Return active gettext translation (or None).""" - try: - return i18n.get_translation() - except Exception: - return None - - def naturalsize( value: float | str, binary: bool = False, - gnu: bool = False, format: str = "%.1f", ) -> str: - t = _translation() - _ = t.gettext if t is not None else (lambda s: s) - - if gnu: - suffix = suffixes["gnu"] - elif binary: - suffix = suffixes["binary"] - else: - suffix = suffixes["decimal"] - - base = 1024 if (gnu or binary) else 1000 - bytes_ = float(value) - abs_bytes = abs(bytes_) - - if abs_bytes == 1 and not gnu: - return f"{int(bytes_)} {_('Byte')}" - - if abs_bytes < base: - if gnu: - return f"{int(bytes_)}B" - return f"{int(bytes_)} {_('Bytes')}" - - exp = int(min(log(abs_bytes, base), len(suffix))) - number = format % (bytes_ / (base**exp)) - - # French decimal separator: use comma instead of dot - if t is not None: - lang = (t.info().get("language", "") or "").lower() - if lang.startswith("fr"): - number = number.replace(".", ",") - - if gnu: - return number + suffix[exp - 1] - - unit = _(suffix[exp - 1]) - return f"{number} {unit}" + """ + Format a number of bytes like a human-readable file size. + + Examples: + >>> naturalsize(42) + '42 Bytes' + >>> naturalsize(42000) + '42.0 kB' + >>> naturalsize(42000000) + '42.0 MB' + + When a locale is activated via ``humanize.i18n.activate()``, + the unit suffixes will be translated accordingly. + + :param value: The number of bytes. + :param binary: Use binary (powers of 1024) units instead of decimal. + :param format: Numeric format string. + :return: Human-readable file size. + """ + + try: + bytes_value = float(value) + except (TypeError, ValueError): + return str(value) + + if bytes_value == 1: + return _("1 Byte") + if bytes_value < 1024: + return _("%d Bytes") % bytes_value + + base = 1024 if binary else 1000 + exp = int(log(bytes_value, base)) + exp = min(exp, len(_SUFFIXES["binary"]) if binary else len(_SUFFIXES["decimal"])) + + value = bytes_value / base**exp + + suffix = ( + _SUFFIXES["binary"][exp - 1] + if binary + else _SUFFIXES["decimal"][exp - 1] + ) + + return (format % value) + suffix From 22d7f0006e389a69a959f054be61ba2bb8cbd4d6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:18:07 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/humanize/filesize.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/humanize/filesize.py b/src/humanize/filesize.py index 272efff..977b01e 100644 --- a/src/humanize/filesize.py +++ b/src/humanize/filesize.py @@ -6,7 +6,6 @@ from humanize.i18n import gettext as _ - _SUFFIXES = { "decimal": ( _(" kB"), @@ -40,8 +39,7 @@ def naturalsize( binary: bool = False, format: str = "%.1f", ) -> str: - """ - Format a number of bytes like a human-readable file size. + """Format a number of bytes like a human-readable file size. Examples: >>> naturalsize(42) @@ -59,7 +57,6 @@ def naturalsize( :param format: Numeric format string. :return: Human-readable file size. """ - try: bytes_value = float(value) except (TypeError, ValueError): @@ -76,10 +73,6 @@ def naturalsize( value = bytes_value / base**exp - suffix = ( - _SUFFIXES["binary"][exp - 1] - if binary - else _SUFFIXES["decimal"][exp - 1] - ) + suffix = _SUFFIXES["binary"][exp - 1] if binary else _SUFFIXES["decimal"][exp - 1] return (format % value) + suffix