Users in different countries expect decimal separators, currency symbols, month names, and sorting rules that match their conventions. Python locale exposes the operating system’s POSIX locale database so applications can format and parse culturally sensitive values.
The feature requires care because locale is a process-wide property inherited from the C library. Changing it affects unrelated code and is not thread-safe on most systems. This guide explains startup configuration, categories, numbers, money, dates, collation, encodings, deployment failures, and safer alternatives for concurrent servers.
It complements our guides to Decimal, zoneinfo, statistics, platform, and sysconfig.
What locale represents
A locale is a collection of cultural conventions installed on the system. It can define the decimal point, thousands separator, currency symbol, sign placement, month and weekday names, date formats, and string collation.
Available locale names depend on the operating system and installed data. A name available on a developer workstation may be missing in a production container.
The process starts conservatively
C programs normally start in the portable C locale. Python configures parts of LC_CTYPE during startup to establish text encoding, but other categories retain portable behavior until the application requests user preferences.
import locale
locale.setlocale(locale.LC_ALL, "")An empty locale string asks the operating system for the user’s default settings, often based on environment variables.
Reading the current setting
current = locale.setlocale(locale.LC_ALL)
print(current)When the locale argument is omitted, setlocale() returns the current setting. The value can later restore the state, but save-and-restore code remains unsafe when other threads run in between.
setlocale is not thread-safe
The setting is global to the process on most platforms. One thread can switch the decimal separator while another formats an invoice.
Set the locale once at startup in a desktop or command-line program and leave it unchanged. A server that formats responses for different users should use a library with independent locale objects per request.
Locale categories
LC_NUMERIC: numeric formatting and parsing.LC_MONETARY: currency conventions.LC_TIME: date and time names and formats.LC_COLLATE: string ordering.LC_CTYPE: character environment and encoding.LC_MESSAGES: system messages on POSIX systems.LC_ALL: all categories together.
Changing only one category reduces the surface of side effects, but the state remains global.
Handling an unavailable locale
try:
locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
except locale.Error:
locale.setlocale(locale.LC_ALL, "")Do not assume one locale name works on Windows, Linux, and macOS. Treat locale installation as a deployment dependency or provide an explicit fallback.
Normalizing locale names
normalized = locale.normalize("en_US.UTF-8")Normalization applies aliases and platform conventions, but it does not install missing data. If normalization fails, the original name may be returned unchanged.
Numeric and monetary conventions
conventions = locale.localeconv()
print(conventions["decimal_point"])
print(conventions["thousands_sep"])
print(conventions["currency_symbol"])The dictionary also describes grouping, monetary decimal digits, symbol placement, and sign position. Some numeric fields can equal CHAR_MAX when the locale provides no value.
Formatting numbers
text = locale.format_string(
"%.2f",
1234567.89,
grouping=True,
)The function follows LC_NUMERIC and percent-format conventions. It is not the full formatting mini-language used by f-strings.
Localizing a normalized number
localized = locale.localize(
"1234567.89",
grouping=True,
)localize(), added in Python 3.10, converts a normalized numeric string to the current locale’s separators.
Parsing a localized number
value = locale.atof("1,234.56")The example assumes a locale where comma groups thousands and dot marks decimals. atof() calls delocalize() and then converts with float by default.
For money, pass Decimal.
from decimal import Decimal
amount = locale.atof("1,234.56", func=Decimal)Parsing integers
count = locale.atoi("1,234")Validate ranges and reject ambiguous forms. Text from another culture can be misinterpreted instead of raising an error.
delocalize
normalized = locale.delocalize("1,234.56")The function removes grouping and converts the decimal separator. It does not validate business rules such as maximum precision, allowed signs, or value limits.
Formatting currency
text = locale.currency(
1234.50,
symbol=True,
grouping=True,
)currency() does not work in the C locale. A valid monetary locale must be configured first.
A local symbol such as $ is ambiguous. For cross-border documents, consider international=True and always store the ISO currency code separately.
Locale does not choose the currency
Locale determines presentation, not what currency a value represents. A French user can view a US-dollar invoice. Store amount and currency code as explicit data.
Dates and times
time.strftime() and datetime.strftime() use LC_TIME for month and weekday names and cultural formats.
from datetime import datetime
text = datetime.now().strftime("%A, %B %d, %Y")Locale is not a time zone. Convert the instant with zoneinfo before formatting it.
Reading system format information
On supported platforms, nl_langinfo() can return date patterns, month names, encoding, decimal characters, and other locale data.
if hasattr(locale, "nl_langinfo"):
date_format = locale.nl_langinfo(locale.D_FMT)Available constants vary across systems. Guard platform-specific calls and test the real deployment environment.
Cultural string sorting
Unicode code-point order does not always match user expectations.
words = ["ábaco", "action", "zebra"]
sorted_words = sorted(words, key=locale.strxfrm)strxfrm() creates keys suitable for repeated comparisons under the current LC_COLLATE. It is more efficient than repeatedly invoking a comparator during sorting.
strcoll
result = locale.strcoll("fan", "foo")A negative, zero, or positive result represents ordering. The behavior depends on the active collation locale.
Collation is not identity
Strings that sort together are not necessarily equal for login names, keys, or deduplication. Identity needs separate Unicode normalization and domain rules.
Preferred text encoding
encoding = locale.getpreferredencoding(False)The function estimates the user’s preferred encoding. In Python UTF-8 Mode and on Android, it returns UTF-8.
New persistent files and protocols should declare UTF-8 explicitly instead of inheriting the machine’s locale.
getencoding
Since Python 3.11, getencoding() reports the current locale encoding while ignoring Python UTF-8 Mode.
current_encoding = locale.getencoding()Choose the function according to the question: user preference or effective LC_CTYPE encoding.
getlocale
language, encoding = locale.getlocale(locale.LC_NUMERIC)The C locale is represented as (None, None). LC_ALL is not a valid category for getlocale().
Environment variables
On POSIX systems, LC_ALL, category-specific variables, and LANG influence the locale. Containers often fail because an environment variable names a locale that was never installed.
Use UTF-8-capable images and test with the exact production environment.
C and C.UTF-8
The C locale is portable and deterministic. C.UTF-8 is common on Linux but not guaranteed on every system.
Python includes locale-coercion and UTF-8 Mode mechanisms to reduce encoding failures in containers and remote sessions.
Libraries should not change locale
A reusable library does not know which threads or components share the process. Calling setlocale() inside a public function creates an unexpected global side effect.
Accept normalized values, expose configuration, or let the caller provide a formatter.
Web servers
Never switch the process locale for every request. Concurrent requests can mix separators, currency symbols, and month names.
Use an internationalization library with independent locale objects, such as Babel, or deterministic formatters that accept locale data as an explicit argument.
gettext and translated messages
Locale handles cultural conventions, not full application translation. Use the gettext module for message catalogs.
The C-library gettext functions exposed by locale mainly support integration with native libraries.
Testing
Tests should verify only locales installed in the environment and skip unavailable ones explicitly. Avoid changing global locale in parallel.
def try_locale(name):
try:
locale.setlocale(locale.LC_ALL, name)
except locale.Error:
return False
return TrueUse separate processes when a suite must test multiple cultures reliably.
Security
Localized strings remain external input. Limit their size, validate the accepted characters, and never use a parsed value directly as SQL, a shell command, or a path.
Cultural validity is not business validity. Negative, infinite, or over-precise amounts can still be forbidden.
Common mistakes
- Changing locale per request.
- Assuming one locale name exists everywhere.
- Using float for money.
- Confusing locale with time zone.
- Confusing locale with currency code.
- Comparing localized values as strings.
- Using collation as identity.
- Letting a library mutate global locale.
Best practices
- Configure locale once at startup in simple programs.
- Use specific categories when possible.
- Handle
locale.Error. - Use Decimal for amounts.
- Keep currency and time zone explicit.
- Use
strxfrm()for repeated sorting. - Prefer UTF-8 for persistent formats.
- Use independent formatters in concurrent servers.
Conclusion
Python locale connects an application to operating-system cultural conventions for numbers, money, dates, encodings, and sorting.
Its main limitation is global, non-thread-safe state. It works well for local tools configured once, while multi-user servers should use independent per-request formatters. Consult the official locale documentation and Unicode CLDR for broader international applications.







