From 5807760aa283bb3a56d160b1727a72a3e335bd29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCp=20Can=20Akman?= Date: Wed, 20 May 2026 11:38:12 +0300 Subject: [PATCH] du: honor LC_NUMERIC for decimal separator (#12357) * du: honor LC_NUMERIC for decimal separator Route the fractional digit in `uucore::format::human::format_prefixed` through `locale_decimal_separator()`, the same helper #11941 added for `numfmt`. Fixes #11956. * du: spell-check: ignore `replacen` Matches the directive in `src/uu/numfmt/src/format.rs` for the same helper introduced by #11941. --- src/uu/du/Cargo.toml | 1 + src/uucore/src/lib/features/format/human.rs | 22 +++++++++++++++++++-- tests/by-util/test_du.rs | 20 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index a63186016..b2c4cda1d 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -25,6 +25,7 @@ clap = { workspace = true } uucore = { workspace = true, features = [ "format", "fsext", + "i18n-decimal", "parser-size", "parser-glob", "time", diff --git a/src/uucore/src/lib/features/format/human.rs b/src/uucore/src/lib/features/format/human.rs index 7777103b9..1c6ca708b 100644 --- a/src/uucore/src/lib/features/format/human.rs +++ b/src/uucore/src/lib/features/format/human.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore gnulibs sfmt +// spell-checker:ignore gnulibs sfmt replacen //! `human`-size formatting //! @@ -24,6 +24,7 @@ pub enum SizeFormat { /// 3. The human-readable format uses powers for 1024, but does not display the "i" /// that is commonly used to denote Kibi, Mebi, etc. /// 4. Kibi and Kilo are denoted differently ("k" and "K", respectively) +/// 5. The decimal separator follows LC_NUMERIC ("1.3M" in C, "1,3M" in fr_FR) fn format_prefixed(prefixed: &NumberPrefix) -> String { match prefixed { NumberPrefix::Standalone(bytes) => bytes.to_string(), @@ -36,12 +37,29 @@ fn format_prefixed(prefixed: &NumberPrefix) -> String { if (10.0 * bytes).ceil() >= 100.0 { format!("{:.0}{prefix_str}", bytes.ceil()) } else { - format!("{:.1}{prefix_str}", (10.0 * bytes).ceil() / 10.0) + let number = format!("{:.1}", (10.0 * bytes).ceil() / 10.0); + format!("{}{prefix_str}", localize_decimal(number)) } } } } +fn localize_decimal(s: String) -> String { + #[cfg(feature = "i18n-decimal")] + { + let sep = crate::i18n::decimal::locale_decimal_separator(); + if sep == "." { + s + } else { + s.replacen('.', sep, 1) + } + } + #[cfg(not(feature = "i18n-decimal"))] + { + s + } +} + pub fn human_readable(size: u64, sfmt: SizeFormat) -> String { match sfmt { SizeFormat::Binary => format_prefixed(&NumberPrefix::binary(size as f64)), diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index fbb1761af..7dfda01fd 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -948,6 +948,26 @@ fn test_du_h_precision() { } } +#[test] +#[cfg_attr(wasi_runner, ignore = "WASI: locale env vars not propagated")] +fn test_du_h_locale_decimal_separator() { + for (locale, expected) in [("fr_FR.UTF-8", "8,4K"), ("C", "8.4K")] { + let (at, mut ucmd) = at_and_ucmd!(); + + let fpath = at.plus("test.txt"); + std::fs::File::create(&fpath) + .expect("cannot create test file") + .set_len(8500) + .expect("cannot truncate test len to size"); + ucmd.env("LC_ALL", locale) + .arg("-h") + .arg("--apparent-size") + .arg(&fpath) + .succeeds() + .stdout_only(format!("{expected}\t{}\n", fpath.to_string_lossy())); + } +} + #[allow(clippy::too_many_lines)] #[cfg(feature = "touch")] #[test]