localedef will happily compile a locale definition into a throwaway directory under /tmp, no root and no system archive needed. This walkthrough compiles en_GB.UTF-8, inspects the generated category files, and loads it through LOCPATH without touching anything system-wide. Allow about fifteen minutes. The examples use glibc 2.39 from Ubuntu's localedef 2.39 and Linux man-pages 6.7.
This workflow is deliberately private. Installing a locale into the default archive can affect every localised program on the machine and normally needs elevated privileges; none of the commands below need sudo.
Confirm which executable you are using and record its version:
$ command -v localedef
/usr/bin/localedef
$ localedef --version
localedef (Ubuntu GLIBC 2.39-0ubuntu8.9) 2.39
The exact distribution suffix may differ; the part that matters is the glibc version, because locale source syntax and diagnostics belong to that implementation. Ask for the default search paths before relying on a file name:
$ localedef --help
...
System's directory for character maps : /usr/share/i18n/charmaps
repertoire maps: /usr/share/i18n/repertoiremaps
locale path : /usr/lib/locale:/usr/share/i18n
Your help output includes more options and may show different paths. -f selects a character map and -i selects the locale definition. Without those options, localedef uses its documented defaults, which is not a safe assumption for a reproducible build.
Make a temporary workspace and an empty directory for the compiled locale:
$ work=$(mktemp -d /tmp/localedef-guide.XXXXXX)
$ mkdir "$work/en_GB.UTF-8"
$ printf '%s\n' "$work"
/tmp/localedef-guide.ABC123
The final path contains a slash, so localedef treats it as the directory where separate category files will be stored. The manual says the directory must already exist; if it does not, the command fails before creating output. Keep $work private while testing, since the compiled files influence any program that uses that path through LOCPATH.
Checkpoint: you should now have an empty directory whose name ends in en_GB.UTF-8. Do not replace that path with /usr/lib/locale unless you have a specific administrative change planned and a rollback procedure.
Compile the installed English locale using the UTF-8 character map and the locale definition named en_GB:
$ localedef --no-archive --force \
--charmap=UTF-8 \
--inputfile=en_GB \
"$work/en_GB.UTF-8"
$ printf 'exit status: %s\n' "$?"
exit status: 0
--no-archive keeps this operation away from /usr/lib/locale/locale-archive. --force, also available as -c, writes output even when the source produces warnings. On this machine, compiling en_GB without --force still writes files but returns status 1, because glibc reports warnings about the optional LC_NAME and LC_ADDRESS fields. Status 0 with --force is convenient for this test, but it does not mean every warning has disappeared. List what was created:
$ find "$work/en_GB.UTF-8" -maxdepth 2 -type f -printf '%P\n' | sort
LC_ADDRESS
LC_COLLATE
LC_CTYPE
LC_IDENTIFICATION
LC_MEASUREMENT
LC_MESSAGES/SYS_LC_MESSAGES
LC_MONETARY
LC_NAME
LC_NUMERIC
LC_PAPER
LC_TELEPHONE
LC_TIME
These are binary category files, not copies of the source text. A slash in the output path is significant: it requests this per-category directory form. A locale name without a slash normally selects the archive form instead.
Use the private tree for one command by setting LOCPATH, and check values that prove the locale actually loaded:
$ LOCPATH="$work" LC_ALL=en_GB.UTF-8 \
locale -k decimal_point thousands_sep date_fmt
decimal_point="."
thousands_sep=","
date_fmt="%a %e %b %H:%M:%S %Z %Y"
Whitespace in diagnostic output can vary, and the date format is implementation data. What matters is that the command exits successfully and reports the requested category values while LOCPATH points at your directory. Unset the variable, or start a new shell, when you want normal system locale lookup back:
$ unset LOCPATH
There is no persistent change to undo from this example. When you finish testing, remove only the temporary workspace you created, after checking its path carefully:
$ rm -rf -- "$work"
Warning: that deletion is irreversible. Do not substitute a broad path or a system locale directory for $work.
Modern locale definitions should use Unicode code points directly where possible. A repertoire map is optional and is deprecated in favour of those code points. Use -u or --repertoire-map only when the input refers to symbolic character names that need mapping:
comment_char #
escape_char /
CHARIDS
<Eu> <U20AC> EURO SIGN
END CHARIDS
The file starts its mapping section with CHARIDS. Each mapping has a symbolic name, a Unicode code point and an optional comment; END CHARIDS closes the section. If a source uses <Eu>, pass the map explicitly, for example:
$ localedef --no-archive --force \
--repertoire-map=/path/to/repertoire.map \
--charmap=UTF-8 --inputfile=/path/to/locale.source \
"$work/custom.UTF-8"
Do not add a map merely because the option exists. An incorrect symbolic mapping changes character interpretation and can produce a locale that appears to compile but formats or sorts data incorrectly.
Warning: do not use --add-to-archive, --replace or --delete-from-archive as a first test. They change the shared archive: replacement can overwrite an existing locale, while deletion removes named locales from it. Plan a maintenance window, preserve the current archive through your normal system backup process, and test the private form first.
The exit status also matters in scripts:
Treat status 1 as a review point, not proof the result is safe to deploy. Use --verbose when you need extra warnings, or --quiet when a script should report only fatal errors.
--no-archive into a private tree.LOCPATH loaded the compiled locale for a verification command.