@@ -18,6 +18,8 @@ Major new features:
* New locale added: hrx_BR (Hunsrik language spoken in Brazil).
+* The getopt_long function now accepts translated long option names.
+
Deprecated and removed features, and other changes affecting compatibility:
* Although malloc and related functions currently return pointers
@@ -202,6 +202,15 @@ declared in @file{getopt.h}, not @file{unistd.h}. You should make every
program accept long options if it uses any options, for this takes
little extra work and helps beginners remember how to use the program.
+Both long option names and their translations provided by the program
+for the user's current locale are recognized. This helps users of
+your program who do not speak English understand the meaning of the
+options, and it does not break the function of the program in scripts
+if the untranslated option names are used. If international
+communication involves the invocation of your program, the program
+users should be encouraged to use untranslated option names or publish
+the locale used for this invocation.
+
@deftp {Data Type} {struct option}
@standards{GNU, getopt.h}
This structure describes a single long option name for the sake of
@@ -213,7 +222,9 @@ The @code{struct option} structure has these fields:
@table @code
@item const char *name
-This field is the name of the option. It is a string.
+This field is the name of the option. It is a string. In order for
+@command{getopt_long} to accept either the long option name or its
+translated form, you should mark this string for translation.
@item int has_arg
This field says whether the option takes an argument. It is an integer,
@@ -248,10 +259,14 @@ When @code{getopt_long} encounters a short option, it does the same
thing that @code{getopt} would do: it returns the character code for the
option, and stores the option's argument (if it has one) in @code{optarg}.
-When @code{getopt_long} encounters a long option, it takes actions based
-on the @code{flag} and @code{val} fields of the definition of that
-option. The option name may be abbreviated as long as the abbreviation is
-unique.
+When @code{getopt_long} encounters a long option or its translation in
+the current textdomain, it takes actions based on the @code{flag} and
+@code{val} fields of the definition of that option. The English name
+of the option name may be abbreviated as long as the abbreviation is
+unique. No abbreviation of the translated option name is recognized.
+Since the untranslated option names have precedence over the
+translated option names, it is not possible to hide or divert an
+option with a translation.
If @code{flag} is a null pointer, then @code{getopt_long} returns the
contents of @code{val} to indicate which option it found. You should
@@ -332,6 +332,7 @@ tests := \
tst-wordexp-nocmd \
tst-wordexp-reuse \
tstgetopt \
+ tstgetoptl \
# tests
# Test for the glob symbol version that was replaced in glibc 2.27.
@@ -529,6 +530,7 @@ LOCALES := \
da_DK.ISO-8859-1 \
de_DE.ISO-8859-1 \
de_DE.UTF-8 \
+ en_GB.UTF-8 \
en_US.UTF-8 \
es_US.ISO-8859-1 \
es_US.UTF-8 \
@@ -802,3 +804,14 @@ tst-wordexp-reuse-ENV += MALLOC_TRACE=$(objpfx)tst-wordexp-reuse.mtrace \
$(objpfx)tst-wordexp-reuse-mem.out: $(objpfx)tst-wordexp-reuse.out
$(common-objpfx)malloc/mtrace $(objpfx)tst-wordexp-reuse.mtrace > $@; \
$(evaluate-test)
+
+# tstgetoptl uses a translation catalog for translated option names.
+tstgetoptl_mo = $(objpfx)domaindir/en_GB/LC_MESSAGES/tstgetoptl.mo
+
+$(tstgetoptl_mo): tstgetoptl.po
+ $(make-target-directory)
+ msgfmt -o $@T $<
+ mv -f $@T $@
+
+$(objpfx)tstgetoptl.out: $(tstgetoptl_mo) $(gen-locales)
+CFLAGS-tstgetoptl.c += -DOBJPFX=\"$(objpfx)\"
@@ -182,6 +182,24 @@ exchange (char **argv, struct _getopt_data *d)
d->__last_nonopt = d->optind;
}
+/* Return true iff a translation for opt_name has been found and it
+ matches the substring from argument, length argument_length.
+*/
+static bool
+match_translated_option_name (char *(*translate) (const char *msgid),
+ const char *argument, size_t argument_length,
+ const char *opt_name)
+{
+ const char *translated = opt_name;
+ if (translate != NULL)
+ translated = translate (opt_name);
+
+ if (strncmp (translated, argument, argument_length) != 0)
+ return false;
+ /* We know that argument is a prefix of translated. */
+ return translated[argument_length] == '\0';
+}
+
/* Process the argument starting with d->__nextchar as a long option.
d->optind should *not* have been advanced over this argument.
@@ -194,7 +212,8 @@ static int
process_long_option (int argc, char **argv, const char *optstring,
const struct option *longopts, int *longind,
int long_only, struct _getopt_data *d,
- int print_errors, const char *prefix)
+ int print_errors, const char *prefix,
+ char *(*translate) (const char *msgid))
{
char *nameend;
size_t namelen;
@@ -202,6 +221,7 @@ process_long_option (int argc, char **argv, const char *optstring,
const struct option *pfound = NULL;
int n_options;
int option_index;
+ const char *translated_option_name;
for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++)
/* Do nothing. */ ;
@@ -221,7 +241,22 @@ process_long_option (int argc, char **argv, const char *optstring,
if (pfound == NULL)
{
- /* Didn't find an exact match, so look for abbreviations. */
+ /* Didn't find an exact match, try with translated option
+ names. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (match_translated_option_name (translate, d->__nextchar, namelen, p->name))
+ {
+ /* Exact match found with translation. */
+ pfound = p;
+ break;
+ }
+ }
+
+ if (pfound == NULL)
+ {
+ /* Didn't find an exact match with translations, so look for
+ abbreviations, but only for the option name in the C
+ locale. */
unsigned char *ambig_set = NULL;
int ambig_malloced = 0;
int ambig_fallback = 0;
@@ -341,10 +376,20 @@ process_long_option (int argc, char **argv, const char *optstring,
else
{
if (print_errors)
- fprintf (stderr,
- _("%s: option '%s%s' doesn't allow an argument\n"),
- argv[0], prefix, pfound->name);
-
+ {
+ translated_option_name = translate (pfound->name);
+ if (strcmp (translated_option_name, pfound->name) != 0)
+ /* Print both names of the option. */
+ fprintf (stderr,
+ _("%s: option '%s%s' / '%s%s' doesn't allow an argument\n"),
+ argv[0], prefix, translated_option_name, prefix, pfound->name);
+ else
+ /* Either the option name is not translated, or its
+ translation is the same as the option name. */
+ fprintf (stderr,
+ _("%s: option '%s%s' doesn't allow an argument\n"),
+ argv[0], prefix, pfound->name);
+ }
d->optopt = pfound->val;
return '?';
}
@@ -356,9 +401,19 @@ process_long_option (int argc, char **argv, const char *optstring,
else
{
if (print_errors)
- fprintf (stderr,
- _("%s: option '%s%s' requires an argument\n"),
- argv[0], prefix, pfound->name);
+ {
+ /* Same dichotomy as when the option does not allow an
+ argument. */
+ translated_option_name = translate (pfound->name);
+ if (strcmp (translated_option_name, pfound->name) != 0)
+ fprintf (stderr,
+ _("%s: option '%s%s' / '%s%s' requires an argument\n"),
+ argv[0], prefix, translated_option_name, prefix, pfound->name);
+ else
+ fprintf (stderr,
+ _("%s: option '%s%s' requires an argument\n"),
+ argv[0], prefix, pfound->name);
+ }
d->optopt = pfound->val;
return optstring[0] == ':' ? ':' : '?';
@@ -470,7 +525,8 @@ _getopt_initialize (_GL_UNUSED int argc,
int
_getopt_internal_r (int argc, char **argv, const char *optstring,
const struct option *longopts, int *longind,
- int long_only, struct _getopt_data *d, int posixly_correct)
+ int long_only, struct _getopt_data *d, int posixly_correct,
+ char *(*translate) (const char *msgid))
{
int print_errors = d->opterr;
@@ -573,7 +629,8 @@ _getopt_internal_r (int argc, char **argv, const char *optstring,
d->__nextchar = argv[d->optind] + 2;
return process_long_option (argc, argv, optstring, longopts,
longind, long_only, d,
- print_errors, "--");
+ print_errors, "--",
+ translate);
}
/* If long_only and the ARGV-element has the form "-f",
@@ -595,7 +652,8 @@ _getopt_internal_r (int argc, char **argv, const char *optstring,
d->__nextchar = argv[d->optind] + 1;
code = process_long_option (argc, argv, optstring, longopts,
longind, long_only, d,
- print_errors, "-");
+ print_errors, "-",
+ translate);
if (code != -1)
return code;
}
@@ -649,7 +707,8 @@ _getopt_internal_r (int argc, char **argv, const char *optstring,
d->__nextchar = d->optarg;
d->optarg = NULL;
return process_long_option (argc, argv, optstring, longopts, longind,
- 0 /* long_only */, d, print_errors, "-W ");
+ 0 /* long_only */, d, print_errors, "-W ",
+ translate);
}
if (temp[1] == ':')
{
@@ -702,7 +761,7 @@ _getopt_internal_r (int argc, char **argv, const char *optstring,
int
_getopt_internal (int argc, char **argv, const char *optstring,
const struct option *longopts, int *longind, int long_only,
- int posixly_correct)
+ int posixly_correct, char *(*translate) (const char *))
{
int result;
@@ -711,7 +770,7 @@ _getopt_internal (int argc, char **argv, const char *optstring,
result = _getopt_internal_r (argc, argv, optstring, longopts,
longind, long_only, &getopt_data,
- posixly_correct);
+ posixly_correct, translate);
optind = getopt_data.optind;
optarg = getopt_data.optarg;
@@ -729,7 +788,8 @@ _getopt_internal (int argc, char **argv, const char *optstring,
NAME (int argc, char *const *argv, const char *optstring) \
{ \
return _getopt_internal (argc, (char **)argv, optstring, \
- NULL, NULL, 0, POSIXLY_CORRECT); \
+ NULL, NULL, 0, POSIXLY_CORRECT, \
+ NULL); \
}
#ifdef _LIBC
@@ -19,6 +19,9 @@
#ifndef _LIBC
# include <config.h>
+# include "gettext.h"
+#else
+# include <libintl.h>
#endif
#include "getopt.h"
@@ -29,7 +32,7 @@ getopt_long (int argc, char *__getopt_argv_const *argv, const char *options,
const struct option *long_options, int *opt_index)
{
return _getopt_internal (argc, (char **) argv, options, long_options,
- opt_index, 0, 0);
+ opt_index, 0, 0, gettext);
}
int
@@ -38,7 +41,7 @@ _getopt_long_r (int argc, char **argv, const char *options,
struct _getopt_data *d)
{
return _getopt_internal_r (argc, argv, options, long_options, opt_index,
- 0, d, 0);
+ 0, d, 0, gettext);
}
/* Like getopt_long, but '-' as well as '--' can indicate a long option.
@@ -52,7 +55,7 @@ getopt_long_only (int argc, char *__getopt_argv_const *argv,
const struct option *long_options, int *opt_index)
{
return _getopt_internal (argc, (char **) argv, options, long_options,
- opt_index, 1, 0);
+ opt_index, 1, 0, gettext);
}
int
@@ -61,7 +64,7 @@ _getopt_long_only_r (int argc, char **argv, const char *options,
struct _getopt_data *d)
{
return _getopt_internal_r (argc, argv, options, long_options, opt_index,
- 1, d, 0);
+ 1, d, 0, gettext);
}
@@ -22,10 +22,14 @@
#include <getopt.h>
+/* The translate argument here is optional (can be NULL), it is used
+ to avoid depending on the gettext functions in the posix getopt
+ function. */
extern int _getopt_internal (int ___argc, char **___argv,
const char *__shortopts,
const struct option *__longopts, int *__longind,
- int __long_only, int __posixly_correct);
+ int __long_only, int __posixly_correct,
+ char *(*translate) (const char *msgid));
/* Reentrant versions which can handle parsing multiple argument
@@ -102,7 +106,8 @@ extern int _getopt_internal_r (int ___argc, char **___argv,
const char *__shortopts,
const struct option *__longopts, int *__longind,
int __long_only, struct _getopt_data *__data,
- int __posixly_correct);
+ int __posixly_correct,
+ char *(*translate) (const char *msgid));
extern int _getopt_long_r (int ___argc, char **___argv,
const char *__shortopts,
new file mode 100644
@@ -0,0 +1,139 @@
+/* Check that getopt uses translated option names. */
+/* 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 <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libintl.h>
+#include <locale.h>
+#include <array_length.h>
+#include <support/support.h>
+#include <support/check.h>
+
+/* This tests that --colour is accepted as a translation of --color.
+ This echoes tstgetopt.c, where --colour was an option name alias
+ for --color, so it had to be listed twice. */
+
+/* This uses the en_GB locale so that colour means color. As a
+ special case, we also check that non-translated options have
+ precedence over translated options, by translating "optional" as
+ "required". We also check that getopt only matches translations
+ for actual options, by having the user pass --flavour (which is a
+ known translation of flavor) without the program recognizing a
+ --flavor option. */
+
+static void
+prepare_localedir (void)
+{
+ unsetenv ("LANGUAGE");
+ xsetlocale (LC_MESSAGES, "en_GB.UTF-8");
+ TEST_VERIFY_EXIT (bindtextdomain ("tstgetoptl", OBJPFX "domaindir") != NULL);
+ TEST_VERIFY_EXIT (textdomain ("tstgetoptl") != NULL);
+ /* Check that the catalog is OK: */
+ TEST_COMPARE_STRING (gettext ("color"), "colour");
+ TEST_COMPARE_STRING (gettext ("flavor"), "flavour");
+}
+
+static char **
+prepare_argv (int *argc)
+{
+ static char *argv[] =
+ {
+ (char *) "tstgetoptl", (char *) "--required", (char *) "foobar",
+ (char *) "--optional=bazbug", (char *) "--col", (char *) "--color",
+ (char *) "--colour", (char *) "--flavour", NULL
+ };
+ *argc = array_length (argv) - 1;
+ return argv;
+}
+
+static void
+do_my_test (void)
+{
+ int argc;
+ char **argv = prepare_argv (&argc);
+ static const struct option options[] =
+ {
+ {"required", required_argument, NULL, 'r'},
+ {"optional", optional_argument, NULL, 'o'},
+ {"color", no_argument, NULL, 'C'},
+ /* Now colour is handled as a translation of color. */
+ /* Note that there’s no "--flavor" option, so the "flavor" ->
+ "flavour" translation is useless. */
+ {NULL, 0, NULL, 0 }
+ };
+
+ /* This tests the same arguments as tstgetopt.c. */
+
+ int Cflag = 0;
+ int index;
+ int c;
+ bool found_flavor = false;
+
+ optind = 0;
+ fputs ("Reminder that --flavor is not an option of the program.\n", stderr);
+ while ((c = getopt_long (argc, argv, "", options, NULL)) >= 0)
+ switch (c)
+ {
+ case 'C':
+ ++Cflag;
+ break;
+ case '?':
+ TEST_VERIFY (!found_flavor);
+ found_flavor = true;
+ break;
+ default:
+ /* This should not happen. */
+ support_record_failure_reset ();
+ return;
+
+ case 'r':
+ printf ("--required %s\n", optarg);
+ TEST_COMPARE_STRING (optarg, "foobar");
+ break;
+ case 'o':
+ printf ("--optional %s\n", optarg);
+ if (optarg != NULL)
+ TEST_COMPARE_STRING (optarg, "bazbug");
+ break;
+ }
+
+ TEST_VERIFY (found_flavor);
+
+ printf ("Cflags = %d\n", Cflag);
+
+ TEST_COMPARE (Cflag, 3);
+
+ for (index = optind; index < argc; index++)
+ printf ("Non-option argument %s\n", argv[index]);
+
+ TEST_COMPARE (optind, argc);
+}
+
+int
+do_test (void)
+{
+ prepare_localedir ();
+ do_my_test ();
+ return 0;
+}
+
+#define TEST_FUNCTION do_test
+#include <support/test-driver.c>
new file mode 100644
@@ -0,0 +1,29 @@
+# English translations for tstgetoptl, a test case in glibc.
+# Copyright (C) 2026 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the glibc package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: tstgetoptl 0.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-05-27 19:29+0200\n"
+"PO-Revision-Date: 2025-05-27 19:30+0200\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"
+
+#: xxx.c:yy
+msgid "color"
+msgstr "colour"
+
+#: xxx.c:yy
+msgid "flavor"
+msgstr "flavour"
+
+# This is to make sure the translator cannot redirect options.
+#: xxx.c:yy
+msgid "optional"
+msgstr "required"