[PATCH v4 3/5] posix: let the getopt caller set the translation context

Vivien Kraus vivien@planete-kraus.eu
Tue Jun 3 19:12:38 GMT 2025


Option names are typically one word, so they could be translated
differently in different parts of the program.  The use of a context
lets the translator pick the most appropriate translation when used in
the command-line.

Another possibility would be to prepend two dashes to the option name
before translation, such that it would be obvious this is the
command-line option name.  However, it would be difficult to mark this
string for translation (to be processed by xgettext).

The context is now mandatory.

pgettext_expr is not available yet, so we use a custom function to
combine the context and the long option name.

This creates a new global variable / reentrant state field, optctxt,
so that the caller can override it.
---
 manual/getopt.texi       | 14 ++++++++++++-
 posix/Versions           |  2 +-
 posix/bits/getopt_core.h |  5 +++++
 posix/getopt.c           | 43 +++++++++++++++++++++++++++++++++++-----
 posix/getopt_int.h       |  1 +
 posix/tstgetoptl.c       |  6 +++++-
 posix/tstgetoptl.po      |  2 ++
 7 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/manual/getopt.texi b/manual/getopt.texi
index 36d409e1f7..15994400dd 100644
--- a/manual/getopt.texi
+++ b/manual/getopt.texi
@@ -53,6 +53,16 @@ This variable is set by @code{getopt} to point at the value of the
 option argument, for those options that accept arguments.
 @end deftypevar
 
+@deftypevar {const char *} optctxt
+In order to match translated option names, @code{getopt} looks the
+names in the current textdomain. Since option names may be short words
+instead of long sentences, they may have different translations in
+other places of the program.  @xref{Contexts, , Using contexts for
+solving ambiguities, gettext, the GNU Gettext manual}, for more
+information.  If this is @code{NULL}, then the translated option names
+will not be processed.
+@end deftypevar
+
 @deftypefun int getopt (int @var{argc}, char *const *@var{argv}, const char *@var{options})
 @standards{POSIX.2, unistd.h}
 @safety{@prelim{}@mtunsafe{@mtasurace{:getopt} @mtsenv{}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
@@ -215,7 +225,9 @@ The @code{struct option} structure has these fields:
 @item const char *name
 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.
+translated form, you should mark this string for translation with a
+translation context, and set @code{optctxt} to the translation
+context.
 
 @item int has_arg
 This field says whether the option takes an argument.  It is an integer,
diff --git a/posix/Versions b/posix/Versions
index 0624d24bcc..3ef3609711 100644
--- a/posix/Versions
+++ b/posix/Versions
@@ -18,7 +18,7 @@ libc {
     __environ; _environ;
 
     # variables in normal name space
-    environ; optarg; opterr; optind; optopt;
+    environ; optarg; opterr; optind; optopt; optctxt;
     re_max_failures; re_syntax_options;
 
     # a*
diff --git a/posix/bits/getopt_core.h b/posix/bits/getopt_core.h
index 95d01103f2..0a145bc4ad 100644
--- a/posix/bits/getopt_core.h
+++ b/posix/bits/getopt_core.h
@@ -58,6 +58,11 @@ extern int opterr;
 
 extern int optopt;
 
+/* Callers store the translation context in which to retrieve option
+   names.  If unset, the option names will not be translated. */
+
+extern const char *optctxt;
+
 /* Get definitions and prototypes for functions to process the
    arguments in ARGV (ARGC of them, minus the program name) for
    options given in OPTS.
diff --git a/posix/getopt.c b/posix/getopt.c
index 451dfe8c0e..2fc928962a 100644
--- a/posix/getopt.c
+++ b/posix/getopt.c
@@ -114,6 +114,11 @@ int opterr = 1;
 
 int optopt = '?';
 
+/* Callers store an optional context to enable option name
+   translation. */
+
+const char *optctxt = NULL;
+
 /* Keep a global copy of all internal members of getopt_data.  */
 
 static struct _getopt_data getopt_data;
@@ -182,20 +187,46 @@ exchange (char **argv, struct _getopt_data *d)
   d->__last_nonopt = d->optind;
 }
 
+/* FIXME: use pgettext_expr when available. */
 static const char *
-translate_option_name (const char *long_option_name)
+translate_option_name (const char *context, const char *long_option_name)
 {
-  return gettext (long_option_name);
+  char *msgid;
+  const char *translated = long_option_name;
+
+  if (context)
+    {
+      msgid = malloc (strlen (context) + 1 /* ^D */ + strlen (long_option_name) + 1);
+      if (msgid)
+        {
+          strcpy (msgid, context);
+          msgid[strlen (context)] = '\004';
+          strcpy (msgid + strlen (context) + 1, long_option_name);
+          translated = gettext (msgid);
+          if (!strcmp (translated, msgid))
+            {
+              translated = long_option_name;
+            }
+        }
+      free (msgid);
+    }
+  else
+    translated = long_option_name;
+  return translated;
 }
 
 /* Return 1 iff a translation for opt_name has been found and it
    matches the substring from argument, length argument_length.
+
+   The translation is disambiguated iff translation_context is not
+   NULL.
 */
 static const int
 match_translated_option_name (const char *argument, size_t argument_length,
+                              const char *translation_context,
                               const char *opt_name)
 {
-  const char *translated = translate_option_name (opt_name);
+  const char *translated = translate_option_name (translation_context, opt_name);
   return (!strncmp (translated, argument, argument_length)
           && argument_length == strlen (translated));
 }
@@ -243,7 +274,8 @@ process_long_option (int argc, char **argv, const char *optstring,
       /* 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 (d->__nextchar, namelen, p->name))
+        if (match_translated_option_name (d->__nextchar, namelen,
+                                          d->optctxt, p->name))
           {
             /* Exact match found with translation.  */
             pfound = p;
@@ -367,7 +399,7 @@ process_long_option (int argc, char **argv, const char *optstring,
   /* We have found a matching long option.  Consume it.  */
   d->optind++;
   d->__nextchar = NULL;
-  translated_option_name = translate_option_name (pfound->name);
+  translated_option_name = translate_option_name (d->optctxt, pfound->name);
   if (*nameend)
     {
       /* Don't test has_arg with >, because some C compilers don't
@@ -770,6 +802,7 @@ _getopt_internal (int argc, char **argv, const char *optstring,
 
   getopt_data.optind = optind;
   getopt_data.opterr = opterr;
+  getopt_data.optctxt = optctxt;
 
   result = _getopt_internal_r (argc, argv, optstring, longopts,
 			       longind, long_only, &getopt_data,
diff --git a/posix/getopt_int.h b/posix/getopt_int.h
index 94c1945c5f..5c10d7b99e 100644
--- a/posix/getopt_int.h
+++ b/posix/getopt_int.h
@@ -67,6 +67,7 @@ struct _getopt_data
   int opterr;
   int optopt;
   char *optarg;
+  const char *optctxt;
 
   /* Internal members.  */
 
diff --git a/posix/tstgetoptl.c b/posix/tstgetoptl.c
index 04b07093e4..bffd56f47d 100644
--- a/posix/tstgetoptl.c
+++ b/posix/tstgetoptl.c
@@ -15,6 +15,8 @@
    precedence over translated options, by translated "optional" as
    "required". */
 
+#define TRANSLATION_CONTEXT "command-line option"
+
 static int
 prepare_localedir (void)
 {
@@ -31,7 +33,7 @@ prepare_localedir (void)
       return -1;
     }
   /* Check that the catalog is OK: */
-  if (strcmp (gettext ("color"), "colour") != 0)
+  if (strcmp (gettext (TRANSLATION_CONTEXT "\004" "color"), "colour") != 0)
     {
       fputs ("The mo file does not work.\n", stderr);
       return -1;
@@ -42,6 +44,7 @@ prepare_localedir (void)
 int
 main (int argc, char **argv)
 {
+  static const char *translation_context = TRANSLATION_CONTEXT;
   static const struct option options[] =
     {
       {"required", required_argument, NULL, 'r'},
@@ -68,6 +71,7 @@ main (int argc, char **argv)
       fputs ("Error while setting up localedir.\n", stderr);
       return 1;
     }
+  optctxt = translation_context;
   while ((c = getopt_long (argc, argv, "abc:", options, NULL)) >= 0)
     switch (c)
       {
diff --git a/posix/tstgetoptl.po b/posix/tstgetoptl.po
index 25cd595790..e060c0d6e3 100644
--- a/posix/tstgetoptl.po
+++ b/posix/tstgetoptl.po
@@ -16,10 +16,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
 #: xxx.c:yy
+msgctxt "command-line option"
 msgid "color"
 msgstr "colour"
 
 # This is to make sure the translator cannot redirect options.
 #: xxx.c:yy
+msgctxt "command-line option"
 msgid "optional"
 msgstr "required"
-- 
2.49.0



More information about the Libc-alpha mailing list