From b719e5bfd2aa8e00f766dab5ab67a9f55d386ee9 Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Fri, 27 Jun 2025 23:19:00 +0200 Subject: [PATCH] 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. --- NEWS | 6 +- gettext-tools/doc/gettext.texi | 11 +- gettext-tools/doc/lang-sh.texi | 428 ++++++++++++++++++++++++++++++--- 3 files changed, 402 insertions(+), 43 deletions(-) diff --git a/NEWS b/NEWS index 2394045e8..f6ebce9c6 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index 6cb70fbc8..b1c359c3a 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -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 diff --git a/gettext-tools/doc/lang-sh.texi b/gettext-tools/doc/lang-sh.texi index 47d7f422b..6db228b5b 100644 --- a/gettext-tools/doc/lang-sh.texi +++ b/gettext-tools/doc/lang-sh.texi @@ -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 -- 2.47.3