@code{gettext}, @code{ngettext} programs
@*@code{eval_gettext}, @code{eval_ngettext}, @code{eval_pgettext},
@code{eval_npgettext} shell functions
+@*@code{printf_gettext}, @code{printf_ngettext} commands
@item textdomain
@vindex TEXTDOMAIN@r{, environment variable}
automatic
@item Prerequisite
-@code{. gettext.sh}
+For the shell functions approach: @code{. gettext.sh}
@item Use or emulate GNU gettext
use
@code{xgettext}
@item Formatting with positions
-A POSIX compliant
-@url{https://pubs.opengroup.org/onlinepubs/9799919799/utilities/printf.html,
- @code{printf}}
-command, such as the one from GNU coreutils 9.6 or newer.
-@c GNU Bash built-in?
+There are three approaches; see below for details.
@item Portability
fully portable
An example is available in the @file{examples} directory: @code{hello-sh}.
@menu
-* Preparing Shell Scripts:: Preparing Shell Scripts for Internationalization
-* gettext.sh:: Contents of @code{gettext.sh}
+* sh - Three approaches:: Three approaches for Shell Scripts
+* The gettext.sh approach:: The @code{gettext.sh} approach
+* The printf approach:: The @code{printf} approach
+* The printf_gettext approach:: The @code{printf_gettext} approach
+* Preparing for gettext.sh:: Preparing Shell Scripts for the @code{gettext.sh} approach
+* Preparing for printf:: Preparing Shell Scripts for the @code{printf} approach
+* Preparing for printf_gettext:: Preparing Shell Scripts for the @code{printf_gettext} approach
* gettext Invocation:: Invoking the @code{gettext} program
* ngettext Invocation:: Invoking the @code{ngettext} program
* printf_gettext Invocation:: Invoking the @code{printf_gettext} program
* eval_npgettext Invocation:: Invoking the @code{eval_npgettext} function
@end menu
-@node Preparing Shell Scripts
-@subsubsection Preparing Shell Scripts for Internationalization
+@node sh - Three approaches
+@subsubsection Three approaches for Shell Scripts
+
+There are three approaches for internationalizing shell scripts.
+
+The common part is how to internationalize a message without parameters:
+This is done through the invocation of the @code{gettext} program,
+see @ref{gettext Invocation}.
+
+The approaches differ regarding how to internationalize a message with parameters, that is, what is called ``formatted output'' in other programming languages.
+
+@node The gettext.sh approach
+@subsubsection The @code{gettext.sh} approach
+
+This approach uses shell functions defined in the @code{gettext.sh} file.
+
+Here's an example that references a shell variable @code{pid}:
+@example
+eval_gettext "Running as process number \$pid."; echo
+@end example
+
+A ready-to-use example is available in the @file{hello-1.sh} file
+in the @code{examples/hello-sh} directory.
+
+@subheading Contents of @code{gettext.sh}
+
+@code{gettext.sh}, contained in the run-time package of GNU gettext,
+provides the following:
+
+@table @code
+@item $echo
+The variable @code{echo} is set to a command that outputs its first argument
+and a newline, without interpreting backslashes in the argument string.
+
+@item eval_gettext
+See @ref{eval_gettext Invocation}.
+
+@item eval_ngettext
+See @ref{eval_ngettext Invocation}.
+
+@item eval_pgettext
+See @ref{eval_pgettext Invocation}.
+
+@item eval_npgettext
+See @ref{eval_npgettext Invocation}.
+@end table
+
+@subheading Advantages and Drawbacks
+
+Advantages:
+@itemize @bullet
+@item
+Portability: This is fully portable.
+@end itemize
+
+Drawbacks:
+@itemize @bullet
+@item
+The strings passed to the @code{eval_gettext} et al.@: functions
+must not start nor end with a newline.
+This means, in order to add a newline after a message,
+you need to add an explicit invocation of @code{echo}.
+@item
+References to shell variables must be introduced with @samp{\$},
+not just @samp{$}.
+@item
+Speed:
+Each invocation of the @code{eval_gettext} et al.@: functions
+uses a pipe and a subshell.
+This is slower than the @code{printf_gettext} approach.
+@end itemize
+
+@node The printf approach
+@subsubsection The @code{printf} approach
+
+This approach uses a POSIX:2024 compliant @code{printf} command.
+
+Here's an example that references a shell variable @code{pid}:
+@example
+env printf "`gettext \"Running as process number %u.\"`"'\n' $pid
+@end example
+
+An example is available in the @file{hello-2.sh} file
+in the @code{examples/hello-sh} directory.
+
+@subheading Advantages and Drawbacks
+
+Drawbacks:
+@itemize @bullet
+@item
+Portability:
+A POSIX compliant
+@url{https://pubs.opengroup.org/onlinepubs/9799919799/utilities/printf.html,
+ @code{printf}}
+command, such as the one from GNU coreutils 9.6 or newer, is required.
+At the time of this writing (2025),
+no shell is known whose @code{printf} built-in is POSIX compliant.
+For this reason, the code needs to use @code{env printf}, not @code{printf},
+so as to avoid invoking the shell's @code{printf} built-in.
+@item
+The strings passed to the @code{gettext} et al.@: program inside a subshell
+must not start nor end with a newline.
+This means, in order to add a newline after a message, you either need
+to append a newline to the format string argument of @code{printf}, or
+to add an explicit invocation of @code{echo}.
+@item
+Some amount of backslashing is involved, see @ref{Preparing for printf}.
+It is easy to make editing mistakes during this process.
+@item
+Speed:
+Each invocation of @code{env printf} uses a subshell.
+This is slower than the @code{printf_gettext} approach.
+@end itemize
+
+@node The printf_gettext approach
+@subsubsection The @code{printf_gettext} approach
+
+This approach uses
+the @code{printf_gettext} and @code{printf_ngettext} programs,
+contained in the run-time package of GNU gettext 0.26 or newer.
+
+Here's an example that references a shell variable @code{pid}:
+@example
+printf_gettext 'Running as process number %u.' $pid; echo
+@end example
+
+An example is available in the @file{hello-3.sh} file
+in the @code{examples/hello-sh} directory.
+
+@subheading Advantages and Drawbacks
+
+Advantages:
+@itemize @bullet
+@item
+Portability: The only requirement is GNU gettext 0.26 or newer.
+@item
+Speed:
+Since no subshell needs to be created,
+this approach is about 30% faster than the two other approaches.
+@end itemize
+
+An example is available in the @file{hello-3.sh} file
+in the @code{examples/hello-sh} directory.
+
+@node Preparing for gettext.sh
+@subsubsection Preparing Shell Scripts for the @code{gettext.sh} approach
@cindex preparing shell scripts for translation
Preparing a shell script for internationalization is conceptually similar
-to the steps described in @ref{Sources}. The concrete steps for shell
-scripts are as follows.
+to the steps described in @ref{Sources}.
+The concrete steps for shell scripts with this approach are as follows.
@enumerate
@item
. gettext.sh
@end smallexample
-near the top of the script. @code{gettext.sh} is a shell function library
-that provides the functions
+near the top of the script.
+@code{gettext.sh} is a shell function library that provides the functions
@code{eval_gettext} (see @ref{eval_gettext Invocation}),
@code{eval_ngettext} (see @ref{eval_ngettext Invocation}),
@code{eval_pgettext} (see @ref{eval_pgettext Invocation}), and
@item
Set and export the @code{TEXTDOMAIN} and @code{TEXTDOMAINDIR} environment
-variables. Usually @code{TEXTDOMAIN} is the package or program name, and
+variables.
+Usually @code{TEXTDOMAIN} is the package or program name, and
@code{TEXTDOMAINDIR} is the absolute pathname corresponding to
@code{$prefix/share/locale}, where @code{$prefix} is the installation location.
(@code{"`...`"} or @code{"$(...)"}), variable access with defaulting (like
@code{$@{@var{variable}-@var{default}@}}), access to positional arguments
(like @code{$0}, @code{$1}, ...) or highly volatile shell variables (like
-@code{$?}). This can always be done through simple local code restructuring.
+@code{$?}).
+This can always be done through simple local code restructuring.
For example,
@smallexample
@samp{$echo} to @samp{gettext} (if the string contains no references to
shell variables) or to @samp{eval_gettext} (if it refers to shell variables),
followed by a no-argument @samp{echo} command (to account for the terminating
-newline). Similarly, for cases with plural handling, replace a conditional
+newline).
+Similarly, for cases with plural handling, replace a conditional
@samp{echo} command with an invocation of @samp{ngettext} or
@samp{eval_ngettext}, followed by a no-argument @samp{echo} command.
When doing this, you also need to add an extra backslash before the dollar
sign in references to shell variables, so that the @samp{eval_gettext}
function receives the translatable string before the variable values are
-substituted into it. For example,
+substituted into it.
+For example,
@smallexample
echo "Remaining files: $filecount"
@end smallexample
If the output command is not @samp{echo}, you can make it use @samp{echo}
-nevertheless, through the use of backquotes. However, note that inside
-backquotes, backslashes must be doubled to be effective (because the
-backquoting eats one level of backslashes). For example, assuming that
+nevertheless, through the use of backquotes.
+However, note that inside backquotes,
+backslashes must be doubled to be effective
+(because the backquoting eats one level of backslashes).
+For example, assuming that
@samp{error} is a shell function that signals an error,
@smallexample
@end smallexample
@end enumerate
-@node gettext.sh
-@subsubsection Contents of @code{gettext.sh}
+@node Preparing for printf
+@subsubsection Preparing Shell Scripts for the @code{printf} approach
+@cindex preparing shell scripts for translation
-@code{gettext.sh}, contained in the run-time package of GNU gettext, provides
-the following:
+Preparing a shell script for internationalization is conceptually similar
+to the steps described in @ref{Sources}.
+The concrete steps for shell scripts with this approach are as follows.
-@itemize @bullet
-@item $echo
-The variable @code{echo} is set to a command that outputs its first argument
-and a newline, without interpreting backslashes in the argument string.
+@enumerate
+@item
+Set and export the @code{TEXTDOMAIN} and @code{TEXTDOMAINDIR} environment
+variables.
+Usually @code{TEXTDOMAIN} is the package or program name, and
+@code{TEXTDOMAINDIR} is the absolute pathname corresponding to
+@code{$prefix/share/locale}, where @code{$prefix} is the installation location.
-@item eval_gettext
-See @ref{eval_gettext Invocation}.
+@smallexample
+TEXTDOMAIN=@@PACKAGE@@
+export TEXTDOMAIN
+TEXTDOMAINDIR=@@LOCALEDIR@@
+export TEXTDOMAINDIR
+@end smallexample
-@item eval_ngettext
-See @ref{eval_ngettext Invocation}.
+@item
+Prepare the strings for translation, as described in @ref{Preparing Strings}.
-@item eval_pgettext
-See @ref{eval_pgettext Invocation}.
+@item
+Simplify translatable strings so that they don't contain command substitution
+(@code{"`...`"} or @code{"$(...)"}), variable access with defaulting (like
+@code{$@{@var{variable}-@var{default}@}}), access to positional arguments
+(like @code{$0}, @code{$1}, ...) or highly volatile shell variables (like
+@code{$?}).
+This can always be done through simple local code restructuring.
+For example,
-@item eval_npgettext
-See @ref{eval_npgettext Invocation}.
-@end itemize
+@smallexample
+echo "Usage: $0 [OPTION] FILE..."
+@end smallexample
+
+becomes
+
+@smallexample
+program_name=$0
+echo "Usage: $program_name [OPTION] FILE..."
+@end smallexample
+
+Similarly,
+
+@smallexample
+echo "Remaining files: `ls | wc -l`"
+@end smallexample
+
+becomes
+
+@smallexample
+filecount="`ls | wc -l`"
+echo "Remaining files: $filecount"
+@end smallexample
+
+@item
+For each translatable string, change the output command @samp{echo} or
+@samp{$echo} to @samp{gettext} (if the string contains no references to
+shell variables)
+or to @samp{env printf "`gettext ... `"} (if it refers to shell variables),
+followed by a no-argument @samp{echo} command (to account for the terminating
+newline).
+Similarly, for cases with plural handling, replace a conditional
+@samp{echo} command with an invocation of @samp{ngettext} or
+@samp{env printf "`ngettext ... `"},
+followed by a no-argument @samp{echo} command.
+
+When doing this, you also need to replace the references to shell variables
+with format string directives (see @ref{sh-format}),
+so that the @samp{eval_gettext} function receives the translatable string
+before the variable values are substituted into it.
+For example,
+
+@smallexample
+echo "Remaining files: $filecount"
+@end smallexample
+
+becomes
+
+@smallexample
+env printf "`gettext \"Remaining files: %u.\"`"'\n' "$filecount"
+@end smallexample
+
+If the output command is not @samp{echo}, you can make it use @samp{echo}
+nevertheless, through the use of backquotes.
+However, note that inside backquotes,
+backslashes must be doubled to be effective
+(because the backquoting eats one level of backslashes).
+For example, assuming that
+@samp{error} is a shell function that signals an error,
+
+@smallexample
+error "file not found: $filename"
+@end smallexample
+
+is first transformed into
+
+@smallexample
+error "`echo \"file not found: \$filename\"`"
+@end smallexample
+
+which then becomes
+
+@smallexample
+error "`env printf \"\`gettext \\\"file not found: %s\\\"\`\" \"\$filename\"`"
+@end smallexample
+@end enumerate
+
+@node Preparing for printf_gettext
+@subsubsection Preparing Shell Scripts for the @code{printf_gettext} approach
+@cindex preparing shell scripts for translation
+
+Preparing a shell script for internationalization is conceptually similar
+to the steps described in @ref{Sources}.
+The concrete steps for shell scripts with this approach are as follows.
+
+@enumerate
+@item
+Set and export the @code{TEXTDOMAIN} and @code{TEXTDOMAINDIR} environment
+variables.
+Usually @code{TEXTDOMAIN} is the package or program name, and
+@code{TEXTDOMAINDIR} is the absolute pathname corresponding to
+@code{$prefix/share/locale}, where @code{$prefix} is the installation location.
+
+@smallexample
+TEXTDOMAIN=@@PACKAGE@@
+export TEXTDOMAIN
+TEXTDOMAINDIR=@@LOCALEDIR@@
+export TEXTDOMAINDIR
+@end smallexample
+
+@item
+Prepare the strings for translation, as described in @ref{Preparing Strings}.
+
+@item
+Simplify translatable strings so that they don't contain command substitution
+(@code{"`...`"} or @code{"$(...)"}), variable access with defaulting (like
+@code{$@{@var{variable}-@var{default}@}}), access to positional arguments
+(like @code{$0}, @code{$1}, ...) or highly volatile shell variables (like
+@code{$?}).
+This can always be done through simple local code restructuring.
+For example,
+
+@smallexample
+echo "Usage: $0 [OPTION] FILE..."
+@end smallexample
+
+becomes
+
+@smallexample
+program_name=$0
+echo "Usage: $program_name [OPTION] FILE..."
+@end smallexample
+
+Similarly,
+
+@smallexample
+echo "Remaining files: `ls | wc -l`"
+@end smallexample
+
+becomes
+
+@smallexample
+filecount="`ls | wc -l`"
+echo "Remaining files: $filecount"
+@end smallexample
+
+@item
+For each translatable string, change the output command @samp{echo} or
+@samp{$echo} to @samp{gettext} (if the string contains no references to
+shell variables)
+or to @samp{printf_gettext} (if it refers to shell variables),
+followed by a no-argument @samp{echo} command (to account for the terminating
+newline).
+Similarly, for cases with plural handling, replace a conditional
+@samp{echo} command with an invocation of @samp{ngettext} or
+@samp{printf_ngettext},
+followed by a no-argument @samp{echo} command.
+
+When doing this, you also need to replace the references to shell variables
+with format string directives (see @ref{sh-format}),
+so that the @samp{eval_gettext} function receives the translatable string
+before the variable values are substituted into it.
+For example,
+
+@smallexample
+echo "Remaining files: $filecount"
+@end smallexample
+
+becomes
+
+@smallexample
+printf_gettext "Remaining files: %u." "$filecount"; echo
+@end smallexample
+
+If the output command is not @samp{echo}, you can make it use @samp{echo}
+nevertheless, through the use of backquotes.
+However, note that inside backquotes,
+backslashes must be doubled to be effective
+(because the backquoting eats one level of backslashes).
+For example, assuming that
+@samp{error} is a shell function that signals an error,
+
+@smallexample
+error "file not found: $filename"
+@end smallexample
+
+is first transformed into
+
+@smallexample
+error "`echo \"file not found: \$filename\"`"
+@end smallexample
+
+which then becomes
+
+@smallexample
+error "`printf_gettext \"file not found: %s\" \"\$filename\"`"
+@end smallexample
+@end enumerate
@node gettext Invocation
@subsubsection Invoking the @code{gettext} program