Shell: Document two more approaches.

* gettext-tools/doc/lang-sh.texi (sh): Tweaks.
(sh - Three approaches): New subsubsection.
(The gettext.sh approach): New subsubsection, incorporating the "gettext.sh"
subsubsection.
(The printf approach, The printf_gettext approach): New subsubsections.
(Preparing for gettext.sh): Renamed from "Preparing Shell Scripts".
(Preparing for printf, Preparing for printf_gettext): New subsubsections.
* gettext-tools/doc/gettext.texi: Update detailed node list.
* NEWS: Mention the change.
This commit is contained in:
Bruno Haible 2025-06-27 23:19:00 +02:00
parent f1fc656bcc
commit b719e5bfd2
3 changed files with 402 additions and 43 deletions

6
NEWS
View File

@ -12,13 +12,15 @@ Version 0.26 - July 2025
in a context that requires a format string. You can override this
heuristic by using a comment of the form /* xgettext: c-format */.
* Shell:
- The documentation now mentions two other approaches for
internationalizing messages with parameters in shell scripts.
- xgettext now recognizes format strings in the 'printf' command syntax.
They are marked as 'sh-printf-format' in POT and PO files.
- xgettext now recognizes the \c, \u, and \U escape sequences in dollar-
single-quoted strings $'...'.
- Two new programs 'printf_gettext' and 'printf_ngettext' are provided,
that do formatted output with a localized format string in a more
efficient way (without spawning a subshell).
- xgettext now recognizes the \c, \u, and \U escape sequences in dollar-
single-quoted strings $'...'.
# Bug fixes:
- The AM_GNU_GETTEXT macro now rejects the dysfunctional gettext() function

View File

@ -463,13 +463,18 @@ Individual Programming Languages
sh - Shell Script
* 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
* envsubst Invocation:: Invoking the @code{envsubst} program
* printf_gettext Invocation:: Invoking the @code{printf_gettext} program
* printf_ngettext Invocation:: Invoking the @code{printf_ngettext} program
* envsubst Invocation:: Invoking the @code{envsubst} program
* eval_gettext Invocation:: Invoking the @code{eval_gettext} function
* eval_ngettext Invocation:: Invoking the @code{eval_ngettext} function
* eval_pgettext Invocation:: Invoking the @code{eval_pgettext} function

View File

@ -28,6 +28,7 @@ bash, gettext-base
@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}
@ -41,7 +42,7 @@ environment variable @code{TEXTDOMAINDIR}
automatic
@item Prerequisite
@code{. gettext.sh}
For the shell functions approach: @code{. gettext.sh}
@item Use or emulate GNU gettext
use
@ -50,11 +51,7 @@ 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
@ -66,8 +63,13 @@ 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
@ -79,13 +81,157 @@ An example is available in the @file{examples} directory: @code{hello-sh}.
* 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
@ -95,8 +241,8 @@ Insert the line
. 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
@ -105,7 +251,8 @@ You have to ensure that @code{gettext.sh} can be found in the @code{PATH}.
@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.
@ -124,7 +271,8 @@ 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.
@code{$?}).
This can always be done through simple local code restructuring.
For example,
@smallexample
@ -156,14 +304,16 @@ 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{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"
@ -176,9 +326,11 @@ eval_gettext "Remaining files: \$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
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
@ -198,29 +350,229 @@ error "`eval_gettext \"file not found: \\\$filename\"`"
@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