diff --git a/plugins/statusline/assets/costs.sh b/plugins/statusline/assets/costs.sh index ae621d3..cff374a 100755 --- a/plugins/statusline/assets/costs.sh +++ b/plugins/statusline/assets/costs.sh @@ -51,6 +51,15 @@ # used to exercise the day/month rollover branches deterministically (e.g. the # month-boundary transition). Unset in production; real `date` is used. +# Numeric formatting must be locale-independent. Under a comma-decimal locale +# (e.g. fr_FR) awk/printf emit "19,78"; interpolated into a downstream awk +# program that value is read as TWO arguments and the amount is truncated. +# Only the numeric category is forced, matching statusline-command.sh: LC_ALL=C +# would also pin LC_CTYPE, which breaks ${#s} width measurement. LC_ALL must be +# unset first — it outranks LC_NUMERIC when the environment sets it. +unset LC_ALL +export LC_NUMERIC=C + set -u COST_LOG="${STATUSLINE_COST_LOG:-${HOME}/.claude/statusline-costs.jsonl}" diff --git a/plugins/statusline/assets/statusline-command.sh b/plugins/statusline/assets/statusline-command.sh index 34784a1..7d6ba34 100755 --- a/plugins/statusline/assets/statusline-command.sh +++ b/plugins/statusline/assets/statusline-command.sh @@ -63,6 +63,16 @@ # (test harnesses pointing at a working copy). # Load order is dependency order: platform and palette own no dependencies, # everything else builds on them. +# Numeric formatting must be locale-independent. Under a comma-decimal locale +# (e.g. fr_FR) awk/printf emit "19,78"; interpolated into a downstream awk +# program that value is read as TWO arguments and the amount is truncated. +# Only the numeric category may be forced: LC_ALL=C would also pin LC_CTYPE, +# and vislen() measures with ${#s}, which then counts UTF-8 bytes instead of +# columns (a 3-byte glyph reads as width 3). LC_ALL must be unset first — +# it outranks LC_NUMERIC when the environment sets it. +unset LC_ALL +export LC_NUMERIC=C + STATUSLINE_LIB="${STATUSLINE_LIB:-$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/statusline-lib}" for _mod in platform palette fit severity format config gitctx session_state layout render; do if [ -r "${STATUSLINE_LIB}/${_mod}.sh" ]; then diff --git a/plugins/statusline/assets/statusline-lib/format.sh b/plugins/statusline/assets/statusline-lib/format.sh index 7299445..74b4abf 100644 --- a/plugins/statusline/assets/statusline-lib/format.sh +++ b/plugins/statusline/assets/statusline-lib/format.sh @@ -19,8 +19,11 @@ fmt_tokens() { # fmt_usd — a dollar amount, thousands folded to "$1.2k". fmt_usd() { - local v="$1" - LC_NUMERIC=C awk "BEGIN{ if($v>=1000) printf \"\$%.1fk\",$v/1000; else printf \"\$%.2f\",$v }" + # Value passed via -v, never interpolated into the program text: a stray + # comma decimal must not become an argument separator. + local v="${1:-0}" + v="${v/,/.}" + LC_ALL=C awk -v v="$v" 'BEGIN{ v=v+0; if (v>=1000) printf "$%.1fk", v/1000; else printf "$%.2f", v }' } # fmt_dur — compact duration: "Xh Ym" / "Xm Ys" / "Xs" from a seconds count. diff --git a/plugins/statusline/assets/statusline-lib/render.sh b/plugins/statusline/assets/statusline-lib/render.sh index 95e961c..f67c516 100644 --- a/plugins/statusline/assets/statusline-lib/render.sh +++ b/plugins/statusline/assets/statusline-lib/render.sh @@ -128,10 +128,10 @@ render_session() { # "main" so an understated number is never shown as if it were the total. if [ "$RANK" -ge 1 ]; then if [ -n "$cost_session" ]; then - cost_fmt=$(LC_NUMERIC=C awk "BEGIN{printf \"%.2f\",$cost_session}") + cost_fmt=$(LC_ALL=C awk -v v="${cost_session/,/.}" 'BEGIN{printf "%.2f", v+0}') line="${line:+$line ${SEP} }${KEY}session ${SUBTEXT}${DOLLAR}${cost_fmt}${RESET}" elif [ -n "$cost" ]; then - cost_fmt=$(LC_NUMERIC=C awk "BEGIN{printf \"%.2f\",$cost}") + cost_fmt=$(LC_ALL=C awk -v v="${cost/,/.}" 'BEGIN{printf "%.2f", v+0}') line="${line:+$line ${SEP} }${KEY}session main ${SUBTEXT}${DOLLAR}${cost_fmt}${RESET}" fi fi