Use Git's git-sh-i18n Library in Portable Shell Scripts
By the end of this guide, a shell script will source Git's i18n helper, print messages through its wrappers, and keep working when GNU gettext is not available. This is for people writing or studying Git's shell-based porcelain, not for ordinary Git command use. Allow about 15 minutes for a small test and integration.
The route
Jump straight to the step you need, or tick off Done means at the end.
What you need
You need Git installed and a POSIX-style shell such as sh. The examples below were checked against Git 2.43.0, whose Debian package is git-man for the manual and whose helper is found below Git's exec path. The helper is a shell library: it is meant to be read with the dot command, not launched as a user-facing command.
No root access is required. Do not edit the copy under /usr/lib/git-core; it belongs to the Git installation and package upgrades may replace it.
Checkpoint 1: locate the helper
- Ask Git where its private support programs are installed.
git --version
git --exec-path
ls -l "$(git --exec-path)/git-sh-i18n"
On the system used for this guide, the first command reports:
git version 2.43.0
The final command should identify a readable shell script named git-sh-i18n. Use Git's reported path rather than hard-coding /usr/lib/git-core. That keeps the script aligned with the Git installation that will run it.
Checkpoint 2: source it in the right order
- Source the helper before calling any of its functions.
#!/bin/sh
. "$(git --exec-path)/git-sh-i18n"
gettextln "Repository is ready"
The leading dot is the POSIX shell's source operation. It loads definitions into the current shell, which is why gettextln is available on the next line. Running the file as a separate process would not populate the caller's environment or function table.
The helper sets TEXTDOMAIN to git. It uses /usr/share/locale unless GIT_TEXTDOMAINDIR is already set, then exports both values for the message functions. This is setup state for the script, not a permanent system configuration change.
Checkpoint 3: use the message wrappers
- Pass a literal message to
gettextwhen it does not contain shell variables.
. "$(git --exec-path)/git-sh-i18n"
gettext "Repository is ready"
printf '
'
gettext writes the translated form of its argument without adding a newline. The explicit printf makes line endings obvious and does not depend on a shell-specific echo. Git's helper also provides gettextln, which writes the message and then adds one newline.
Keep the message as one quoted argument. Unquoted text is split by the shell and can change what the wrapper receives. Treat messages as output, not as shell code: do not pass an untrusted string to eval_gettext.
Checkpoint 4: expand variables with eval_gettext
- Use
eval_gettextonly when a translated message needs values from the shell.
#!/bin/sh
. "$(git --exec-path)/git-sh-i18n"
name=${1:-operator}
eval_gettext "Hello, $name"
printf '
'
With no argument, this prints a greeting for operator. With Ada as the argument, the local fallback path prints:
Hello, Ada
eval_gettext runs the helper's environment-substitution step so variables can be expanded while the message is processed. That convenience has a boundary: values that contain shell syntax deserve extra care, and the message template itself must be trusted. If a value is only data for a log or diagnostic, consider formatting it with printf instead.
For a message that needs both expansion and a newline, use eval_gettextln. The helper defines it as the variable-aware equivalent of gettextln.
Understand the fallback and check it
The installed script first chooses a scheme. It uses GNU gettext.sh when that is available, supports a gettext binary without eval_gettext, and otherwise installs pass-through wrappers. In the pass-through case, gettext prints the original message and eval_gettext still expands variables through Git's git-sh-i18n--envsubst helper. Your script therefore remains usable in an English-only environment, although it will not provide translated messages there.
Run this harmless verification command:
sh -c '. "$(git --exec-path)/git-sh-i18n"; printf "scheme=%s domain=%s dir=%s\n" "$GIT_INTERNAL_GETTEXT_SH_SCHEME" "$TEXTDOMAIN" "$TEXTDOMAINDIR"; gettext "Plain message"; printf "\n"; name="Ada"; eval_gettext "Hello $name"; printf "\n"'
On the checked machine, the output includes:
scheme=gnu domain=git dir=/usr/share/locale
Plain message
Hello Ada
The scheme can differ between hosts. Do not write a script that requires the value to be gnu; the point of this library is to keep the caller's message interface stable across those cases.
Common traps and recovery
- Calling a wrapper too early: source the helper before the first call, or the function will be missing.
- Expecting a newline:
gettextandeval_gettextdo not add one. Use thelnvariants or an explicitprintf. - Hard-coding the locale directory: respect
GIT_TEXTDOMAINDIRso a deliberate installation-specific catalogue location is not ignored. - Testing translations with a random locale: the wrapper can be working while the selected catalogue has no translated entry. First verify the function call and scheme, then investigate locale and catalogue installation.
- Editing the installed helper: reinstalling or upgrading Git is the recovery. Put project-specific changes in your own script and source the packaged helper.
Done means
- The script sources
"$(git --exec-path)/git-sh-i18n". - Literal messages use
gettextorgettextln. - Variable-aware messages use a trusted template with
eval_gettextoreval_gettextln. - Newlines are intentional and tested.
- The script works with the host's reported scheme, including a pass-through fallback.