Python’s locale module provides locale-aware number, currency, and string formatting using POSIX locale categories. import locale. setlocale: locale.setlocale(locale.LC_ALL, "de_DE.UTF-8") — activates locale; "" uses system default; not thread-safe. getlocale: locale.getlocale(locale.LC_NUMERIC) → ("de_DE", "UTF-8"). localeconv: lc = locale.localeconv() → dict with "decimal_point", "thousands_sep", "grouping", "currency_symbol", "frac_digits", "int_curr_symbol", "mon_thousands_sep". format_string: locale.format_string("%.2f", 1234567.89, grouping=True) → "1,234,567.89" (en_US) or "1.234.567,89" (de_DE). currency: locale.currency(1234.50, grouping=True) → "$1,234.50" or "1.234,50 €". str: locale.str(3.14) → locale-formatted float string. atof: locale.atof("1.234,56") → 1234.56 (de_DE). atoi: locale.atoi("1.234") → 1234. strcoll: locale.strcoll("ä", "z") — returns <0, 0, >0 for locale-aware comparison. strxfrm: locale.strxfrm(s) — for use as sort key. normalize: locale.normalize("en_US") → "en_US.ISO8859-1". locale.LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME — individual categories. Claude Code generates locale-aware formatters, number parsers, currency renderers, and collation sorters.
CLAUDE.md for locale
## locale Stack
- Stdlib: import locale
- Set: locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
- Num: locale.format_string("%.2f", n, grouping=True)
- Money: locale.currency(amount, grouping=True)
- Parse: locale.atof("1,234.56"); locale.atoi("1,234")
- Sort: sorted(strings, key=locale.strxfrm)
- Conv: lc = locale.localeconv() # decimal_point, thousands_sep, ...
locale Locale-Aware Formatting Pipeline
# app/localeutil.py — format, parse, currency, collation, convention, context manager
from __future__ import annotations
import locale
import threading
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Generator
# ─────────────────────────────────────────────────────────────────────────────
# 1. Thread-safe locale context manager
# ─────────────────────────────────────────────────────────────────────────────
_locale_lock = threading.Lock()
@contextmanager
def use_locale(
name: str,
category: int = locale.LC_ALL,
) -> Generator[None, None, None]:
"""
Activate a locale for the duration of a with-block, restoring it afterwards.
Uses a module-level lock to prevent concurrent locale changes in threaded apps.
Example:
with use_locale("de_DE.UTF-8"):
print(locale.format_string("%.2f", 1234.5, grouping=True))
# → "1.234,50"
"""
with _locale_lock:
prev = locale.setlocale(category)
try:
locale.setlocale(category, name)
yield
finally:
locale.setlocale(category, prev)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Number formatting
# ─────────────────────────────────────────────────────────────────────────────
def format_number(
value: int | float,
decimals: int = 2,
grouping: bool = True,
locale_name: str | None = None,
) -> str:
"""
Format a number with locale-aware decimal point and thousands separator.
Example:
format_number(1234567.89) # "1,234,567.89" (en_US)
format_number(1234567.89, locale_name="de_DE.UTF-8") # "1.234.567,89"
"""
fmt = f"%.{decimals}f"
if locale_name:
with use_locale(locale_name):
return locale.format_string(fmt, value, grouping=grouping)
return locale.format_string(fmt, value, grouping=grouping)
def format_integer(
value: int,
grouping: bool = True,
locale_name: str | None = None,
) -> str:
"""
Format an integer with locale-aware thousands separator.
Example:
format_integer(1000000) # "1,000,000" (en_US)
"""
if locale_name:
with use_locale(locale_name):
return locale.format_string("%d", value, grouping=grouping)
return locale.format_string("%d", value, grouping=grouping)
def format_percent(
value: float,
decimals: int = 1,
locale_name: str | None = None,
) -> str:
"""
Format a ratio (0.0–1.0) as a locale-aware percentage string.
Example:
format_percent(0.1234) # "12.3%"
"""
pct = value * 100
num = format_number(pct, decimals=decimals, locale_name=locale_name)
return f"{num}%"
# ─────────────────────────────────────────────────────────────────────────────
# 3. Currency formatting
# ─────────────────────────────────────────────────────────────────────────────
def format_currency(
amount: float,
grouping: bool = True,
international: bool = False,
locale_name: str | None = None,
) -> str:
"""
Format a monetary value with the locale's currency symbol.
international=True uses the ISO 4217 code (e.g. "USD") instead of symbol.
Example:
format_currency(1234.50) # "$1,234.50"
format_currency(1234.50, locale_name="de_DE.UTF-8") # "1.234,50 €"
format_currency(1234.50, international=True) # "USD 1,234.50"
"""
if locale_name:
with use_locale(locale_name):
return locale.currency(amount, grouping=grouping, international=international)
return locale.currency(amount, grouping=grouping, international=international)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Number parsing
# ─────────────────────────────────────────────────────────────────────────────
def parse_float(s: str, locale_name: str | None = None) -> float:
"""
Parse a locale-formatted float string to a Python float.
Example:
parse_float("1.234,56", "de_DE.UTF-8") # 1234.56
parse_float("1,234.56") # 1234.56 (en_US)
"""
if locale_name:
with use_locale(locale_name):
return locale.atof(s)
return locale.atof(s)
def parse_int(s: str, locale_name: str | None = None) -> int:
"""
Parse a locale-formatted integer string.
Example:
parse_int("1.234", "de_DE.UTF-8") # 1234
parse_int("1,234") # 1234 (en_US)
"""
if locale_name:
with use_locale(locale_name):
return locale.atoi(s)
return locale.atoi(s)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Locale conventions and collation
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class LocaleConventions:
"""Snapshot of locale.localeconv() for a given locale."""
locale_name: str
decimal_point: str
thousands_sep: str
grouping: list[int]
currency_symbol: str
int_curr_symbol: str
frac_digits: int
mon_decimal_point: str
mon_thousands_sep: str
def __str__(self) -> str:
return (f"{self.locale_name}: "
f"dec={self.decimal_point!r} "
f"thou={self.thousands_sep!r} "
f"sym={self.currency_symbol!r} "
f"frac={self.frac_digits}")
def get_conventions(locale_name: str) -> LocaleConventions:
"""
Return locale convention data for a given locale name.
Example:
c = get_conventions("en_US.UTF-8")
print(c.decimal_point, c.thousands_sep) # "." ","
c2 = get_conventions("de_DE.UTF-8")
print(c2.decimal_point, c2.thousands_sep) # "," "."
"""
with use_locale(locale_name):
lc = locale.localeconv()
return LocaleConventions(
locale_name=locale_name,
decimal_point=lc["decimal_point"],
thousands_sep=lc["thousands_sep"],
grouping=lc["grouping"],
currency_symbol=lc["currency_symbol"],
int_curr_symbol=lc["int_curr_symbol"],
frac_digits=lc["frac_digits"],
mon_decimal_point=lc["mon_decimal_point"],
mon_thousands_sep=lc["mon_thousands_sep"],
)
def locale_sort(
strings: list[str],
locale_name: str | None = None,
reverse: bool = False,
) -> list[str]:
"""
Sort strings using locale-aware collation (strcoll/strxfrm).
Example:
locale_sort(["ä", "z", "a"], "de_DE.UTF-8") # ["a", "ä", "z"]
locale_sort(["café", "cave", "cab"]) # locale-ordered
"""
if locale_name:
with use_locale(locale_name, locale.LC_COLLATE):
return sorted(strings, key=locale.strxfrm, reverse=reverse)
return sorted(strings, key=locale.strxfrm, reverse=reverse)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== locale demo ===")
# Try to use en_US; fall back gracefully if not installed
def _try_locale(name: str) -> str | None:
try:
with use_locale(name):
pass
return name
except locale.Error:
return None
en = _try_locale("en_US.UTF-8") or _try_locale("en_US") or ""
de = _try_locale("de_DE.UTF-8") or _try_locale("de_DE") or ""
# ── number formatting ──────────────────────────────────────────────────────
print("\n--- format_number ---")
for lname in filter(None, [en, de]):
print(f" [{lname}] format_number(1234567.89) = "
f"{format_number(1234567.89, locale_name=lname or None)!r}")
# ── currency ───────────────────────────────────────────────────────────────
print("\n--- format_currency ---")
for lname in filter(None, [en, de]):
print(f" [{lname}] format_currency(1234.50) = "
f"{format_currency(1234.50, locale_name=lname or None)!r}")
# ── parse_float ────────────────────────────────────────────────────────────
print("\n--- parse_float ---")
if en:
print(f" [en_US] parse_float('1,234.56') = {parse_float('1,234.56', en)}")
if de:
print(f" [de_DE] parse_float('1.234,56') = {parse_float('1.234,56', de)}")
# ── format_percent ─────────────────────────────────────────────────────────
print("\n--- format_percent ---")
if en:
print(f" [en_US] format_percent(0.1234) = {format_percent(0.1234, locale_name=en)!r}")
# ── localeconv snapshot ────────────────────────────────────────────────────
print("\n--- get_conventions ---")
for lname in filter(None, [en, de]):
try:
c = get_conventions(lname)
print(f" {c}")
except Exception as e:
print(f" [{lname}] unavailable: {e}")
# ── locale_sort ────────────────────────────────────────────────────────────
print("\n--- locale_sort ---")
words = ["café", "cave", "cab", "cap", "can"]
if en:
print(f" [en_US] {locale_sort(words, en)}")
# ASCII fallback
print(f" [C] {sorted(words)}")
# ── use_locale context manager ─────────────────────────────────────────────
print("\n--- use_locale context manager ---")
if de:
with use_locale(de):
num = locale.format_string("%.2f", 9876543.21, grouping=True)
print(f" [de_DE] 9876543.21 → {num!r}")
print("\n=== done ===")
For the babel alternative — Babel (PyPI) provides locale-aware number and currency formatting without the thread-safety issues of locale.setlocale(), because Babel’s formatters accept a locale parameter per call (format_number(1234.56, locale="de_DE")) rather than mutating process-global state— use Babel in multi-threaded web applications (Flask, Django) where concurrent requests need different locales simultaneously, or when you need CLDR-backed data for 700+ locales; use the stdlib locale module for CLI tools, single-threaded scripts, or when you need POSIX collation via strcoll/strxfrm. For the decimal alternative — Python’s decimal.Decimal handles arbitrary-precision arithmetic and rounds monetary values correctly using ROUND_HALF_UP or ROUND_HALF_EVEN (banker’s rounding) — use decimal.Decimal for financial calculations to avoid IEEE 754 floating-point rounding errors (0.1 + 0.2 != 0.3); combine decimal with locale.currency() or Babel’s format_currency() for values that need both precise arithmetic and locale-aware display. The Claude Skills 360 bundle includes locale skill sets covering use_locale() thread-safe context manager with lock, format_number()/format_integer()/format_percent() number formatters, format_currency() monetary formatter with international flag, parse_float()/parse_int() locale-aware parsers, LocaleConventions dataclass with get_conventions(), and locale_sort() collation-aware sorter. Start with the free tier to try locale-aware formatting patterns and locale pipeline code generation.