# How fmtlib Handles Locale-Specific Formatting: A Deep Dive into `locale_ref`

> Discover how fmtlib handles locale-specific formatting using L specifiers and lightweight locale_ref wrappers for efficient, efficient number formatting without heavy C++ locale dependencies.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: deep-dive
- Published: 2026-09-06

---

**fmtlib implements locale-specific formatting through the `L` format specifier and lightweight `locale_ref` wrappers that extract thousands separators and decimal points without importing heavy C++ locale machinery.**

The fmtlib/fmt repository provides high-performance string formatting for C++ with optional locale-aware numeric output. When you need locale-specific formatting for thousands separators or decimal points, the library uses a minimal abstraction layer that avoids the overhead of standard C++ locale facilities. This article examines the internal mechanism that powers the `{:L}` format specifier.

## The `L` Specifier for Locale-Aware Numeric Output

When you include the **`L`** presentation type in a format string, fmtlib queries the active locale for numeric formatting rules. The syntax `fmt::format("{:L}", 1234567)` instructs the formatter to insert locale-specific thousands separators and decimal points.

Unlike standard C++ iostreams, which tie locale handling deeply into the stream state, fmtlib isolates these concerns through a lightweight reference type. You can pass a `std::locale` explicitly as the first argument to `fmt::format`, or rely on the global locale when omitted.

## Core Implementation: `locale_ref` and Helper Functions

The localization machinery centers on **`fmt::locale_ref`**, a thin wrapper around `std::locale` defined in the generated [`include/fmt/locale.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/locale.h). This abstraction prevents the formatter from dragging in heavy locale dependencies unless explicitly requested.

### `thousands_sep_impl` and `decimal_point_impl`

The actual extraction of locale data occurs in `src/format.cc` through two template functions:

- **`thousands_sep_impl(locale_ref)`** retrieves the thousands separator character and grouping pattern (lines 26‑30).
- **`decimal_point_impl(locale_ref)`** extracts the decimal point character (lines 28‑29).

Both functions are explicitly instantiated for `char` and `wchar_t` to support narrow and wide character formatting without code duplication.

### Public API Wrappers

In [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) (lines 25‑48), fmtlib declares inline wrapper functions named `thousands_sep` and `decimal_point`. These forward to the implementation templates while providing a clean interface for the internal formatting engine. The wrappers ensure that formatting code can query locale properties without direct dependency on the heavyweight implementation details.

## Conditional Compilation with `FMT_USE_LOCALE`

Because fmtlib supports header-only compilation, locale support is guarded by the **`FMT_USE_LOCALE`** preprocessor macro. When this macro is undefined or disabled (the default configuration), the library formats all numbers using the classic "C" locale, producing locale-independent output with standard ASCII separators.

This design guarantees that projects requiring deterministic, portable output pay no penalty for locale features they do not use. To enable locale-specific formatting, you must compile with locale support enabled, allowing the conditional code in `src/format.cc` (lines 12‑15) to instantiate the required templates.

## Practical Examples of Locale-Specific Formatting

The following example demonstrates locale-specific formatting for German, US English, and Japanese locales:

```cpp
#include <fmt/format.h>
#include <locale>

int main() {
    // Use the global locale (e.g. set by std::setlocale or std::locale::global)
    std::locale::global(std::locale("de_DE.UTF-8"));
    fmt::print("German locale: {}\n", fmt::format("{:L}", 1234567));
    // → German locale: 1.234.567

    // Override the locale for a single call
    std::locale us("en_US.UTF-8");
    fmt::print("US locale: {}\n", fmt::format(us, "{:L}", 1234567));
    // → US locale: 1,234,567

    // Wide-character version
    fmt::print(L"Japanese locale: {}\n",
               fmt::format(std::locale("ja_JP.UTF-8"), L"{:L}", 1234567));
    // → Japanese locale: 1,234,567
}

```

The `L` specifier tells fmtlib to fetch locale-specific separators; the locale can be passed explicitly or taken from the global locale. The library handles both narrow (`char`) and wide (`wchar_t`) character outputs through the same underlying mechanism in `src/format.cc`.

## Summary

- fmtlib implements locale-specific formatting through the `{:L}` format specifier and the `fmt::locale_ref` abstraction layer.
- The **`thousands_sep_impl`** and **`decimal_point_impl`** functions in `src/format.cc` extract locale data without importing heavy C++ locale machinery.
- Support for both `char` and `wchar_t` is provided via explicit template instantiations.
- Locale support is conditionally compiled via **`FMT_USE_LOCALE`**; when disabled, the library defaults to the classic "C" locale for deterministic output.
- Unit tests in `test/locale_test.cc` verify correct behavior across different locales and character types.

## Frequently Asked Questions

### How do I enable locale-specific formatting in fmtlib?

You must compile fmtlib with the `FMT_USE_LOCALE` macro defined. When enabled, the library instantiates the locale helper templates in `src/format.cc` and links against your system's locale support. Without this macro, fmtlib defaults to the "C" locale regardless of system settings to ensure consistent, portable output.

### What is the performance impact of using `{:L}` in fmtlib?

The performance impact is minimal because fmtlib uses `locale_ref`, a lightweight wrapper that caches only the necessary numeric formatting facets. Unlike standard iostreams, which may lock locales or perform virtual function calls per character, fmtlib extracts the thousands separator and decimal point once per format operation through the inline wrappers in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h).

### Does fmtlib support wide-character locale formatting?

Yes. The implementation provides explicit instantiations for both `char` and `wchar_t` in `src/format.cc`. You can use `fmt::format` with wide string literals (prefixed with `L`) and `std::wstring` types, and the library will correctly apply locale-specific grouping rules to wide character output.

### Why does my fmtlib output ignore system locale settings?

By default, fmtlib disables locale support to guarantee consistent behavior across platforms. If you require locale-specific formatting, verify that `FMT_USE_LOCALE` is defined during compilation and that you have explicitly passed a `std::locale` object or set the global locale with `std::locale::global()` before formatting.