]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
intl: Handle LC_MESSAGES correctly on native Windows.
authorBruno Haible <bruno@clisp.org>
Mon, 23 Dec 2024 16:50:14 +0000 (17:50 +0100)
committerBruno Haible <bruno@clisp.org>
Tue, 24 Dec 2024 17:29:36 +0000 (18:29 +0100)
* gettext-runtime/intl/setlocale.c (libintl_setlocale): Revamp the LC_ALL case
if LC_MESSAGES is 1729.

gettext-runtime/intl/setlocale.c

index 3665dc465a0ab3b59bbfb0714e476405ed5247ae..71a61a3f1efa06c3e3dcf7450a54572356710f2a 100644 (file)
@@ -1,5 +1,5 @@
 /* setlocale() function that respects the locale chosen by the user.
-   Copyright (C) 2009, 2011, 2013, 2018-2019, 2022-2023 Free Software Foundation, Inc.
+   Copyright (C) 2009, 2011-2024 Free Software Foundation, Inc.
    Written by Bruno Haible <bruno@clisp.org>, 2009.
 
    This program is free software: you can redistribute it and/or modify
@@ -1572,7 +1572,7 @@ libintl_setlocale (int category, const char *locale)
                            For LC_COLLATE, the application should use the locale
                            properties kCFLocaleCollationIdentifier,
                            kCFLocaleCollatorIdentifier.
-                           For LC_MONETARY, the applicationshould use the locale
+                           For LC_MONETARY, the application should use the locale
                            properties kCFLocaleCurrencySymbol,
                            kCFLocaleCurrencyCode.
                            But since most applications don't have macOS specific
@@ -1607,7 +1607,7 @@ libintl_setlocale (int category, const char *locale)
           /* All steps were successful.  */
           ++_nl_msg_cat_cntr;
           free (saved_locale);
-          return setlocale (LC_ALL, NULL);
+          goto ret_all;
 
         fail:
           if (saved_locale[0] != '\0') /* don't risk an endless recursion */
@@ -1631,50 +1631,158 @@ libintl_setlocale (int category, const char *locale)
     }
   else
     {
-# if defined _WIN32 && ! defined __CYGWIN__
-      if (category == LC_ALL && locale != NULL && strchr (locale, '.') != NULL)
+# if LC_MESSAGES == 1729
+      if (locale != NULL)
         {
-          char *saved_locale;
-
-          /* Back up the old locale.  */
-          saved_locale = setlocale (LC_ALL, NULL);
-          if (saved_locale == NULL)
-            return NULL;
-          saved_locale = strdup (saved_locale);
-          if (saved_locale == NULL)
-            return NULL;
+          char truncated_locale[SETLOCALE_NULL_ALL_MAX];
+          const char *native_locale;
+          const char *messages_locale;
 
-          if (setlocale_unixlike (LC_ALL, locale) == NULL)
+          if (strncmp (locale, "LC_COLLATE=", 11) == 0)
             {
-              free (saved_locale);
-              return NULL;
+              /* The locale argument indicates a mixed locale.  It must be of
+                 the form
+                 "LC_COLLATE=...;LC_CTYPE=...;LC_MONETARY=...;LC_NUMERIC=...;LC_TIME=...;LC_MESSAGES=..."
+                 since that is what this function returns (see ret_all below).
+                 Decompose it.  */
+              const char *last_semicolon = strrchr (locale, ';');
+              if (!(last_semicolon != NULL
+                    && strncmp (last_semicolon + 1, "LC_MESSAGES=", 12) == 0))
+                return NULL;
+              if (category == LC_MESSAGES)
+                return setlocale_single (category, last_semicolon + 13);
+              size_t truncated_locale_len = last_semicolon - locale;
+              if (truncated_locale_len >= sizeof (truncated_locale))
+                return NULL;
+              memcpy (truncated_locale, locale, truncated_locale_len);
+              truncated_locale[truncated_locale_len] = '\0';
+              native_locale = truncated_locale;
+              messages_locale = last_semicolon + 13;
             }
-
-          /* On native Windows, setlocale(LC_ALL,...) may succeed but set the
-             LC_CTYPE category to an invalid value ("C") when it does not
-             support the specified encoding.  Report a failure instead.  */
-          if (strcmp (setlocale (LC_CTYPE, NULL), "C") == 0)
+          else
             {
-              if (saved_locale[0] != '\0') /* don't risk an endless recursion */
-                setlocale (LC_ALL, saved_locale);
-              free (saved_locale);
-              return NULL;
+              native_locale = locale;
+              messages_locale = locale;
             }
 
-          /* It was really successful.  */
-          ++_nl_msg_cat_cntr;
-          free (saved_locale);
-          return setlocale (LC_ALL, NULL);
+          if (category == LC_ALL)
+            {
+              /* In the underlying implementation, LC_ALL does not contain
+                 LC_MESSAGES.  Therefore we need to handle LC_MESSAGES
+                 separately.  */
+              char *result;
+
+#  if defined _WIN32 && ! defined __CYGWIN__
+              if (strchr (native_locale, '.') != NULL)
+                {
+                  char *saved_locale;
+
+                  /* Back up the old locale.  */
+                  saved_locale = setlocale (LC_ALL, NULL);
+                  if (saved_locale == NULL)
+                    return NULL;
+                  saved_locale = strdup (saved_locale);
+                  if (saved_locale == NULL)
+                    return NULL;
+
+                  if (setlocale_unixlike (LC_ALL, native_locale) == NULL)
+                    {
+                      free (saved_locale);
+                      return NULL;
+                    }
+
+                  /* On native Windows, setlocale(LC_ALL,...) may succeed but
+                     set the LC_CTYPE category to an invalid value ("C") when
+                     it does not support the specified encoding.  Report a
+                     failure instead.  */
+                  if (strcmp (setlocale (LC_CTYPE, NULL), "C") == 0)
+                    {
+                      /* Don't risk an endless recursion.  */
+                      if (saved_locale[0] != '\0')
+                        setlocale (LC_ALL, saved_locale);
+                      free (saved_locale);
+                      return NULL;
+                    }
+
+                  /* It was really successful.  */
+                  free (saved_locale);
+                  result = setlocale (LC_ALL, NULL);
+                }
+              else
+#  endif
+                result = setlocale_single (LC_ALL, native_locale);
+              if (result == NULL)
+                return NULL;
+
+              setlocale_single (LC_MESSAGES, messages_locale);
+
+              ++_nl_msg_cat_cntr;
+              goto ret_all;
+            }
+          else
+            {
+              char *result;
+
+              if (category == LC_MESSAGES)
+                result = setlocale_single (category, messages_locale);
+              else
+                result = setlocale_single (category, native_locale);
+              if (result != NULL)
+                ++_nl_msg_cat_cntr;
+              return result;
+            }
         }
-      else
-# endif
+      else /* locale == NULL */
         {
-          char *result = setlocale_single (category, locale);
-          if (result != NULL)
-            ++_nl_msg_cat_cntr;
-          return result;
+          if (category == LC_ALL)
+            goto ret_all;
+          else
+            return setlocale_single (category, NULL);
         }
+# else
+      return setlocale_single (category, locale);
+# endif
     }
+
+ ret_all:
+  /* Return the name of all categories of the current locale.  */
+# if LC_MESSAGES == 1729 /* native Windows */
+  /* The locale name for mixed locales looks like this:
+     "LC_COLLATE=...;LC_CTYPE=...;LC_MONETARY=...;LC_NUMERIC=...;LC_TIME=..."
+     If necessary, add ";LC_MESSAGES=..." at the end.  */
+  {
+    char *name1 = setlocale (LC_ALL, NULL);
+    char *name2 = setlocale_single (LC_MESSAGES, NULL);
+    if (strcmp (name1, name2) == 0)
+      /* Not a mixed locale.  */
+      return name1;
+    else
+      {
+        static char resultbuf[SETLOCALE_NULL_ALL_MAX];
+        /* Prepare the result in a stack-allocated buffer, in order to reduce
+           race conditions in a multithreaded situation.  */
+        char stackbuf[SETLOCALE_NULL_ALL_MAX];
+        if (strncmp (name1, "LC_COLLATE=", 11) == 0)
+          {
+            if (strlen (name1) + strlen (name2) + 13 >= sizeof (stackbuf))
+              return NULL;
+            sprintf (stackbuf, "%s;LC_MESSAGES=%s", name1, name2);
+          }
+        else
+          {
+            if (5 * strlen (name1) + strlen (name2) + 68 >= sizeof (stackbuf))
+              return NULL;
+            sprintf (stackbuf,
+                     "LC_COLLATE=%s;LC_CTYPE=%s;LC_MONETARY=%s;LC_NUMERIC=%s;LC_TIME=%s;LC_MESSAGES=%s",
+                     name1, name1, name1, name1, name1, name2);
+          }
+        strcpy (resultbuf, stackbuf);
+        return resultbuf;
+      }
+  }
+# else
+  return setlocale (LC_ALL, NULL);
+# endif
 }
 
 # if HAVE_NEWLOCALE