@@ -44,6 +44,8 @@ tests = \
bug-argp2 \
tst-argp1 \
tst-argp2 \
+ tst-argphelp-localized \
+ tst-argpusage-localized \
tst-ldbl-argp \
# tests
@@ -51,6 +53,23 @@ CFLAGS-argp-help.c += $(uses-callbacks) -fexceptions
CFLAGS-argp-parse.c += $(uses-callbacks)
CFLAGS-argp-fmtstream.c += -fexceptions
+tst_argphelp_localized_mo = $(objpfx)domaindir/en_GB/LC_MESSAGES/tst-argphelp-localized.mo
+
+$(tst_argphelp_localized_mo): tst-argphelp-localized.po
+ $(make-target-directory)
+ msgfmt -o $@T $<
+ mv -f $@T $@
+
+LOCALES := \
+ en_GB.UTF-8 \
+ # LOCALES
+include ../gen-locales.mk
+
+$(objpfx)tst-argphelp-localized.out: $(tst_argphelp_localized_mo) $(gen-locales)
+$(objpfx)tst-argpusage-localized.out: $(tst_argphelp_localized_mo) $(gen-locales)
+CFLAGS-tst-argphelp-localized.c += -DOBJPFX=\"$(objpfx)\"
+CFLAGS-tst-argpusage-localized.c += -DOBJPFX=\"$(objpfx)\"
+
bug-argp1-ARGS = -- --help
bug-argp2-ARGS = -- -d 111 --dstaddr 222 -p 333 --peer 444
@@ -1205,6 +1205,35 @@ comma (unsigned col, struct pentry_state *pest)
indent_to (pest->stream, col);
}
+/* Help and usage output show the translated option name. *allocated
+ holds a pointer that should be freed by the caller, or a NULL
+ pointer. */
+static const char *
+translate_option_name (const char *name, char **allocated)
+{
+ /* Argp does not have a configuration for the context, so a default
+ one is used. */
+ /* FIXME: use pgettext_expr. */
+ *allocated = NULL;
+ if (__asprintf (allocated, "command-line option\004%s", name) == -1)
+ {
+ /* *allocated is NULL */
+ return name;
+ }
+ const char *translated = gettext (*allocated);
+ if (strcmp (translated, *allocated) == 0)
+ {
+ /* No translation performed. */
+ free (*allocated);
+ *allocated = NULL;
+ return name;
+ }
+ /* FIXME: is it safe to discard *allocated early here? Won’t the
+ return value alias it? */
+ /* *allocated is to be freed by the caller. */
+ return translated;
+}
+
/* Print help for ENTRY to STREAM. */
static void
hol_entry_help (struct hol_entry *entry, const struct argp_state *state,
@@ -1213,6 +1242,7 @@ hol_entry_help (struct hol_entry *entry, const struct argp_state *state,
unsigned num;
const struct argp_option *real = entry->opt, *opt;
char *so = entry->short_options;
+ const char *translated_option_name;
int have_long_opt = 0; /* We have any long options. */
/* Saved margins. */
int old_lm = __argp_fmtstream_set_lmargin (stream, 0);
@@ -1276,9 +1306,14 @@ hol_entry_help (struct hol_entry *entry, const struct argp_state *state,
if (opt->name && ovisible (opt))
{
comma (uparams.long_opt_col, &pest);
- __argp_fmtstream_printf (stream, "--%s", opt->name);
+ char *name_allocated = NULL;
+ translated_option_name = translate_option_name (opt->name, &name_allocated);
+ __argp_fmtstream_printf (stream, "--%s", translated_option_name);
arg (real, "=%s", "[=%s]",
state == NULL ? NULL : state->root_argp->argp_domain, stream);
+ if (strcmp (translated_option_name, opt->name))
+ __argp_fmtstream_printf (stream, " (--%s)", opt->name);
+ free (name_allocated);
}
}
@@ -1420,6 +1455,7 @@ usage_long_opt (const struct argp_option *opt,
{
argp_fmtstream_t stream = cookie;
const char *arg = opt->arg;
+ const char *translated_option_name = opt->name;
int flags = opt->flags | real->flags;
if (! arg)
@@ -1427,16 +1463,31 @@ usage_long_opt (const struct argp_option *opt,
if (! (flags & OPTION_NO_USAGE))
{
+ char *name_allocated = NULL;
+ translated_option_name =
+ translate_option_name (opt->name, &name_allocated);
+ int translation_differs =
+ (strcmp (translated_option_name, opt->name) != 0);
if (arg)
{
arg = dgettext (domain, arg);
- if (flags & OPTION_ARG_OPTIONAL)
+ if ((flags & OPTION_ARG_OPTIONAL) && translation_differs)
+ __argp_fmtstream_printf (stream, " [--%s[=%s] (--%s)]",
+ translated_option_name, arg, opt->name);
+ else if (flags & OPTION_ARG_OPTIONAL)
__argp_fmtstream_printf (stream, " [--%s[=%s]]", opt->name, arg);
+ else if (translation_differs)
+ __argp_fmtstream_printf (stream, " [--%s=%s (--%s)]",
+ translated_option_name, arg, opt->name);
else
__argp_fmtstream_printf (stream, " [--%s=%s]", opt->name, arg);
}
+ else if (translation_differs)
+ __argp_fmtstream_printf (stream, " [--%s (--%s)]",
+ translated_option_name, opt->name);
else
__argp_fmtstream_printf (stream, " [--%s]", opt->name);
+ free (name_allocated);
}
return 0;
@@ -472,6 +472,7 @@ parser_init (struct parser *parser, const struct argp *argp,
struct parser_sizes szs;
struct _getopt_data opt_data = _GETOPT_DATA_INITIALIZER;
+ opt_data.optctxt = "command-line option";
szs.short_len = (flags & ARGP_NO_ARGS) ? 0 : 1;
szs.long_len = 0;
szs.num_groups = 0;
new file mode 100644
@@ -0,0 +1,92 @@
+/* Test program for argp argument parser
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <argp.h>
+#include <libintl.h>
+#include <locale.h>
+#include <unistd.h>
+#include <support/support.h>
+#include <support/check.h>
+
+/* Note that the final invocation of argp --help will terminate the
+ program with exit code 0. */
+
+#define PN_(ctxt, str) (str)
+#define N_(str) (str)
+
+const char *argp_program_version = "argphelp-test 1.0";
+
+const struct argp_option options[] =
+{
+ {PN_ ("command-line option", "color"), 'c', N_ ("HUE"), 0, "Rainbow!"},
+ {PN_ ("command-line option", "flavor"), 'f',
+ N_ ("COOKIE"), OPTION_ARG_OPTIONAL, "Sweet!"},
+ {PN_ ("command-line option", "texture"), 't', 0, 0, "Smooth!"},
+ {0}
+};
+
+static bool color_set = false;
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ (void) state;
+ if (key == 'c' && color_set)
+ FAIL ("color already set.\n");
+ else if (key == 'c')
+ color_set = true;
+ return 0;
+}
+
+static const struct argp argp = { options, parse_opt };
+
+static int
+do_test (void)
+{
+ char *test1_argv[3] =
+ { (char *) "/bin/tst-argphelp-localized", (char *) "--colour=yellow", NULL };
+ char *test2_argv[3] =
+ { (char *) "/bin/tst-argphelp-localized", (char *) "--help", NULL };
+
+ unsetenv ("LANGUAGE");
+ xsetlocale (LC_ALL, "en_GB.UTF-8");
+ TEST_VERIFY_EXIT (bindtextdomain ("tst-argphelp-localized",
+ OBJPFX "domaindir") != NULL);
+ TEST_VERIFY_EXIT (textdomain ("tst-argphelp-localized") != NULL);
+ /* Check that the catalog is OK: */
+ TEST_COMPARE_STRING (gettext ("command-line option\004color"), "colour");
+ TEST_COMPARE_STRING (gettext ("COOKIE"), "BISCUIT");
+ argp_parse (&argp, 2, test1_argv, 0, 0, NULL);
+ TEST_VERIFY (color_set);
+ color_set = false;
+
+ /* This is the last chance to fail. */
+ if (support_record_failure_is_failed ())
+ FAIL_EXIT1 ("There were test failures before the final invocation of --help");
+ /* This last test will exit the program with code 0 and ignore
+ previous failures. */
+ argp_parse (&argp, 2, test2_argv, 0, 0, NULL);
+ FAIL_EXIT1 ("--help did not exit the program");
+ return 0;
+}
+
+#define TEST_FUNCTION do_test
+#include <support/test-driver.c>
new file mode 100644
@@ -0,0 +1,25 @@
+# English translations for a GNU C Library test.
+# This file is distributed under the same license as the GNU C Library.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: tst-argphelp-localized\n"
+"Report-Msgid-Bugs-To: \n"
+"Language-Team: English (British) <(nothing)>\n"
+"Language: en_GB\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ASCII\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: tst-argphelp-localized.c:73
+msgctxt "command-line option"
+msgid "color"
+msgstr "colour"
+
+msgctxt "command-line option"
+msgid "flavor"
+msgstr "flavour"
+
+msgid "COOKIE"
+msgstr "BISCUIT"
new file mode 100644
@@ -0,0 +1,81 @@
+/* Test program for argp argument parser
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <argp.h>
+#include <libintl.h>
+#include <locale.h>
+#include <unistd.h>
+#include <support/support.h>
+#include <support/check.h>
+
+/* Note that the final invoking argp --usage will terminate the
+ program with exit code 0. */
+
+#define PN_(ctxt, str) (str)
+#define N_(str) (str)
+
+const char *argp_program_version = "argpusage-test 1.0";
+
+const struct argp_option options[] =
+{
+ {PN_ ("command-line option", "color"), 'c', 0, 0, "Rainbow!"},
+ {PN_ ("command-line option", "flavor"), 'f',
+ N_ ("COOKIE"), OPTION_ARG_OPTIONAL, "Sweet!"},
+ {0}
+};
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ return 0;
+}
+
+static const struct argp argp = { options, parse_opt };
+
+static int
+do_test (void)
+{
+ char *test_argv[3] =
+ { (char *) "/bin/tst-argpusage-localized", (char *) "--usage", NULL };
+
+ unsetenv ("LANGUAGE");
+ xsetlocale (LC_ALL, "en_GB.UTF-8");
+ /* We reuse the tst-argphelp-localized domain to avoid making a new
+ PO file. */
+ TEST_VERIFY_EXIT (bindtextdomain ("tst-argphelp-localized",
+ OBJPFX "domaindir") != NULL);
+ TEST_VERIFY_EXIT (textdomain ("tst-argphelp-localized") != NULL);
+ /* Check that the catalog is OK: */
+ TEST_COMPARE_STRING (gettext ("command-line option\004color"), "colour");
+ TEST_COMPARE_STRING (gettext ("COOKIE"), "BISCUIT");
+ /* This is the last chance to fail. */
+ if (support_record_failure_is_failed ())
+ FAIL_EXIT1 (
+ "There were test failures before the final invocation of --usage");
+ /* This last test will exit the program with code 0 and ignore
+ previous failures. */
+ argp_parse (&argp, 2, test_argv, 0, 0, NULL);
+ FAIL_EXIT1 ("--usage did not exit the program");
+ return 0;
+}
+
+#define TEST_FUNCTION do_test
+#include <support/test-driver.c>
@@ -206,8 +206,10 @@ messages. @xref{Argp Help Filtering}.
@item const char *argp_domain
If non-zero, the strings used in the argp library are translated using
-the domain described by this string. If zero, the current default domain
-is used.
+the domain described by this string. If zero, the current default
+domain is used. The long option names are always translated with the
+current default domain, and with the @samp{"command-line option"}
+disambiguation string.
@end table
@end deftp
@@ -233,7 +235,9 @@ beginning, the unused fields left unspecified.
The @code{options} field in a @code{struct argp} points to a vector of
@code{struct argp_option} structures, each of which specifies an option
that the argp parser supports. Multiple entries may be used for a single
-option provided it has multiple names. This should be terminated by an
+option provided it has multiple names. In any case, option names are
+translated, so either the translated or untranslated form is
+recognized for each option. This should be terminated by an
entry with zero in all fields. Note that when using an initialized C
array for options, writing @code{@{ 0 @}} is enough to achieve this.
@@ -247,9 +251,12 @@ the following fields:
@item const char *name
The long name for this option, corresponding to the long option
@samp{--@var{name}}; this field may be zero if this option @emph{only}
-has a short name. To specify multiple names for an option, additional
-entries may follow this one, with the @code{OPTION_ALIAS} flag
-set. @xref{Argp Option Flags}.
+has a short name. You should mark this string for translation with
+the fixed @samp{"command-line option"} context. To specify multiple
+names for an option, additional entries may follow this one, with the
+@code{OPTION_ALIAS} flag set. @xref{Argp Option Flags}. Translations
+are added automatically, it is not necessary to use an alias for
+translations.
@item int key
The integer key provided by the current option to the option parser. If
@@ -323,7 +330,8 @@ This option isn't displayed in any help messages.
This option is an alias for the closest previous non-alias option. This
means that it will be displayed in the same help entry, and will inherit
fields other than @code{name} and @code{key} from the option being
-aliased.
+aliased. It is not necessary to list the translation of an option
+name as an alias.
@item OPTION_DOC