Locales in Linux: setlocale() & Internationalisation

Locales in Linux: setlocale() & Internationalisation
Chapter 10 Series — Part 7: Language, Culture & LC_* Variables
I18N
Internationalisation
6
Locale Categories
LC_ALL
Master Override

What is a Locale?

A locale is a collection of rules and data that tell a program how to format and display information for a specific language and region. The same date — 15 May 2025 — is written as 15/05/2025 in India, 05/15/2025 in the USA, and 15.05.2025 in Germany. The same decimal number 1234.56 becomes 1.234,56 in Germany (period as thousands separator, comma as decimal separator). Locales handle all of this automatically.

Keywords in This Post

setlocale() LC_ALL LC_TIME LC_NUMERIC LANG /usr/share/locale POSIX locale I18N / L10N

🌎 The Six Standard Locale Categories

A locale is not one monolithic setting. It is split into categories, each controlling a different aspect of cultural formatting. You can mix them independently.

Category What it controls Example difference (de_DE vs en_US)
LC_CTYPE Character classification (is ‘ä’ a letter? uppercase of ‘ß’?) German ‘ä’, ‘ö’, ‘ü’ are letters; in C locale they are not
LC_COLLATE Sort order of strings (strcoll, strxfrm) German ‘ü’ sorts as ‘ue’; Spanish ‘ñ’ after ‘n’
LC_MONETARY Currency symbol, decimal/thousands separators for money €1.234,56 vs $1,234.56
LC_NUMERIC Decimal point and thousands separator for non-monetary numbers 1.234,56 (Germany) vs 1,234.56 (US)
LC_TIME Date/time format strings used by strftime() “Dienstag, 15 Mai” vs “Tuesday, May 15”
LC_MESSAGES Language for yes/no responses and program messages “Ja/Nein” vs “Yes/No”

🔧 setlocale() — Setting and Querying the Locale

The setlocale() function controls which locale a program uses. It can set one category, all categories, or just query the current setting.

#include <locale.h>

char *setlocale(int category, const char *locale);
/* Returns the locale name for that category on success, or NULL on error */
Call What it does
setlocale(LC_ALL, “”) Read all LC_* / LANG environment variables and apply them
setlocale(LC_TIME, “de_DE”) Force German date/time formatting regardless of system locale
setlocale(LC_ALL, NULL) Query the current locale without changing it
setlocale(LC_ALL, “C”) Reset to the default POSIX/C locale (ASCII, US conventions)

The most important call: setlocale(LC_ALL, "")
Passing an empty string "" means “read the locale from environment variables”. Without this call, your program ignores LANG, LC_TIME etc. completely and behaves as if locale is “C”. Always call this at the start of any user-facing program that should be locale-aware.

📁 Where Locale Data Lives

Locale data files are stored under /usr/share/locale/. Each subdirectory is named using the convention language[_territory[.codeset]]:

Locale name Language Territory Encoding
en_IN.UTF-8 English India UTF-8
hi_IN.UTF-8 Hindi India UTF-8
de_DE.utf-8 German Germany UTF-8
fr_CH French Switzerland (default)
C (or POSIX) ASCII English Default (always present) ASCII
# List all locales installed on your system
locale -a

# Check what locale your current shell session is using
locale

▸ Locale Environment Variable Priority

When you call setlocale(LC_ALL, ""), the C library reads these environment variables in order of precedence:

Priority Variable Scope
1 (highest) LC_ALL Overrides everything — all 6 categories
2 LC_TIME, LC_NUMERIC, etc. Individual category overrides
3 (lowest) LANG Default for any category not set above
# Set German as default, but use Italian for date/time
LANG=de_DE LC_TIME=it_IT ./my_program

# Force French for everything, even if LC_TIME says something else
LC_ALL=fr_FR LC_TIME=en_US ./my_program  # LC_ALL wins

🔨 Practical Example: Locale-Aware Date Output

When strftime() is called after setting a locale, it uses localised names for weekdays and months. The same format string produces different output in different locales.

#include <locale.h>
#include <time.h>
#include <stdio.h>

void show_date(const char *locale_name)
{
    time_t t = 1747267215;  /* A fixed time for consistent demo */
    struct tm *tm_ptr;
    char buf[128];

    setlocale(LC_TIME, locale_name);
    tm_ptr = localtime(&t);
    strftime(buf, sizeof(buf), "%A, %d %B %Y", tm_ptr);
    printf("[%-12s] %s\n", locale_name, buf);
}

int main(void)
{
    show_date("en_US");    /* Thursday, 15 May 2025 */
    show_date("de_DE");    /* Donnerstag, 15 Mai 2025 */
    show_date("fr_FR");    /* jeudi, 15 mai 2025 */
    show_date("hi_IN");    /* (depends on available locales) */
    return 0;
}

The I18N abbreviation: “Internationalisation” is often written as I18N (I + 18 letters + N). The process of adapting a program for a specific locale is called L10N (Localisation = L + 10 letters + N). A well-written program does I18N once (uses locale-aware functions), and L10N is then “free” — just install the right locale data.

Up Next in This Series

Part 8 covers updating the system clock with settimeofday() and adjtime(), and why gradual adjustment is preferred.

Next: Updating the System Clock → All Linux Posts

Leave a Reply

Your email address will not be published. Required fields are marked *