]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
intl: Support any Unicode characters in the locale dir on native Windows.
authorBruno Haible <bruno@clisp.org>
Sun, 19 Apr 2020 22:46:44 +0000 (00:46 +0200)
committerBruno Haible <bruno@clisp.org>
Sun, 19 Apr 2020 22:46:44 +0000 (00:46 +0200)
Reported at <https://savannah.gnu.org/bugs/?57714>.

* gettext-runtime/intl/libgnuintl.in.h (libintl_wbindtextdomain): New
declaration.
(wbindtextdomain): New redirect.
* gettext-runtime/intl/bindtextdom.c (set_binding_values): Accept a wdirnamep
argument. Set not only binding->dirname but also binding->wdirname.
(BINDTEXTDOMAIN, BIND_TEXTDOMAIN_CODESET): Pass NULL as wdirnamep.
(libintl_wbindtextdomain): New function.
* gettext-runtime/intl/loadinfo.h (struct loaded_l10nfile): Add a wfilename
field.
(_nl_make_l10nflist): On native Windows, accept wdirlist, wdirlist_len
parameters.
* gettext-runtime/intl/loadmsgcat.c (_nl_load_domain): On native Windows, use
_wopen() instead of open() to open a file with a name given as wchar_t[].
* gettext-runtime/intl/l10nflist.c: Include <wchar.h>.
(_nl_make_l10nflist): On native Windows, accept wdirlist, wdirlist_len
parameters. Construct abs_wfilename and use in the search for existing
'struct loaded_l10nfile' and when allocating a fresh 'struct loaded_l10nfile'.
* gettext-runtime/intl/gettextP.h (struct binding): Add a wdirname field.
(_nl_find_domain): On native Windows, accept a wdirname parameter.
* gettext-runtime/intl/finddomain.c (_nl_find_domain): On native Windows, accept
a wdirname parameter. Pass it to _nl_make_l10nflist.
* gettext-runtime/intl/dcigettext.c (DCIGETTEXT): Consider not only
binding->dirname but also binding->wdirname. On native Windows, use _wgetcwd
instead of getcwd. Pass also wdirname to _nl_find_domain.
* gettext-tools/tests/intl-6: New file, based on gettext-tools/tests/intl-1.
* gettext-tools/tests/intl-6-prg.c: New file, based on
gettext-tools/tests/intl-1-prg.c.
* gettext-tools/tests/Makefile.am (TESTS): Add intl-6.
(check_PROGRAMS): Add intl-6-prg.
(intl_6_prg_SOURCES, intl_6_prg_LDADD): New variables.
* gettext-tools/doc/gettext.texi (Ambiguities, src/Makefile,
Language Implementors, C): Document wbindtextdomain.
* gettext-runtime/NEWS: Mention the change.
* NEWS: Likewise.

14 files changed:
NEWS
gettext-runtime/NEWS
gettext-runtime/intl/bindtextdom.c
gettext-runtime/intl/dcigettext.c
gettext-runtime/intl/finddomain.c
gettext-runtime/intl/gettextP.h
gettext-runtime/intl/l10nflist.c
gettext-runtime/intl/libgnuintl.in.h
gettext-runtime/intl/loadinfo.h
gettext-runtime/intl/loadmsgcat.c
gettext-tools/doc/gettext.texi
gettext-tools/tests/Makefile.am
gettext-tools/tests/intl-6 [new file with mode: 0755]
gettext-tools/tests/intl-6-prg.c [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 81838e0a3742bfb78e9f8311b78107e955dfc79e..443a7c1a66828014b04c96dbcdaad525e5c3ac2a 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,13 @@ Version 0.21 - April 2020
   - JavaScript:
     xgettext parses JSX expressions more reliably.
 
+* Runtime behaviour:
+  - On native Windows platforms, the directory that contains the message
+    catalogs may now contain arbitrary Unicode characters. To make use of
+    this feature, use the new function 'wbindtextdomain' instead of
+    'bindtextdomain'. It allows to pass a directory name in wchar_t[] encoding.
+    Note: 'wbindtextdomain' exists only on native Windows platforms.
+
 * Libtextstyle:
   - Added support for emitting hyperlinks.
   - New API for doing formatted output.
index b98f49d525c4d056e744e4c97a5bf2227200005e..d8b162d61d2f53d313977c792d16f87e42546f0e 100644 (file)
@@ -1,4 +1,12 @@
-Version 0.21 - December 2019
+Version 0.21 - April 2020
+
+* On native Windows platforms, the directory that contains the message
+  catalogs may now contain arbitrary Unicode characters. To make use of
+  this feature, use the new function 'wbindtextdomain' instead of
+  'bindtextdomain'. It allows to pass a directory name in wchar_t[] encoding.
+  Note: 'wbindtextdomain' exists only on native Windows platforms.
+
+Version 0.20.2 - April 2020
 
 * The interpretation of the language preferences on macOS has been improved,
   especially in the case where a system locale does not exist for the
index ec25a700019a255372185c2529481f810d8ac202..57cf1f486d9d715cb428b16496cb71a584665dc8 100644 (file)
@@ -1,5 +1,5 @@
 /* Implementation of the bindtextdomain(3) function
-   Copyright (C) 1995-2016 Free Software Foundation, Inc.
+   Copyright (C) 1995-2020 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
@@ -65,15 +65,18 @@ gl_rwlock_define (extern, _nl_state_lock attribute_hidden)
 # define BIND_TEXTDOMAIN_CODESET libintl_bind_textdomain_codeset
 #endif
 
-/* Specifies the directory name *DIRNAMEP and the output codeset *CODESETP
-   to be used for the DOMAINNAME message catalog.
-   If *DIRNAMEP or *CODESETP is NULL, the corresponding attribute is not
-   modified, only the current value is returned.
-   If DIRNAMEP or CODESETP is NULL, the corresponding attribute is neither
-   modified nor returned.  */
+/* Specifies the directory name *DIRNAMEP, the directory name *WDIRNAMEP
+   (only on native Windows), and the output codeset *CODESETP to be used
+   for the DOMAINNAME message catalog.
+   If *DIRNAMEP or *WDIRNAMEP or *CODESETP is NULL, the corresponding attribute
+   is not modified, only the current value is returned.
+   If DIRNAMEP or WDIRNAMEP or CODESETP is NULL, the corresponding attribute is
+   neither modified nor returned, except that setting WDIRNAME erases DIRNAME
+   and vice versa.  */
 static void
 set_binding_values (const char *domainname,
-                   const char **dirnamep, const char **codesetp)
+                   const char **dirnamep, const wchar_t **wdirnamep,
+                   const char **codesetp)
 {
   struct binding *binding;
   int modified;
@@ -83,6 +86,10 @@ set_binding_values (const char *domainname,
     {
       if (dirnamep)
        *dirnamep = NULL;
+#if defined _WIN32 && !defined __CYGWIN__
+      if (wdirnamep)
+       *wdirnamep = NULL;
+#endif
       if (codesetp)
        *codesetp = NULL;
       return;
@@ -121,7 +128,7 @@ set_binding_values (const char *domainname,
                 one are equal we simply do nothing.  Otherwise replace the
                 old binding.  */
              char *result = binding->dirname;
-             if (strcmp (dirname, result) != 0)
+             if (result == NULL || strcmp (dirname, result) != 0)
                {
                  if (strcmp (dirname, _nl_default_dirname) == 0)
                    result = (char *) _nl_default_dirname;
@@ -141,8 +148,13 @@ set_binding_values (const char *domainname,
                    {
                      if (binding->dirname != _nl_default_dirname)
                        free (binding->dirname);
-
                      binding->dirname = result;
+
+#if defined _WIN32 && !defined __CYGWIN__
+                     free (binding->wdirname);
+                     binding->wdirname = NULL;
+#endif
+
                      modified = 1;
                    }
                }
@@ -150,6 +162,41 @@ set_binding_values (const char *domainname,
            }
        }
 
+#if defined _WIN32 && !defined __CYGWIN__
+      if (wdirnamep)
+       {
+         const wchar_t *wdirname = *wdirnamep;
+
+         if (wdirname == NULL)
+           /* The current binding has be to returned.  */
+           *wdirnamep = binding->wdirname;
+         else
+           {
+             /* The domain is already bound.  If the new value and the old
+                one are equal we simply do nothing.  Otherwise replace the
+                old binding.  */
+             wchar_t *result = binding->wdirname;
+             if (result == NULL || wcscmp (wdirname, result) != 0)
+               {
+                 result = _wcsdup (wdirname);
+
+                 if (__builtin_expect (result != NULL, 1))
+                   {
+                     if (binding->dirname != _nl_default_dirname)
+                       free (binding->dirname);
+                     binding->dirname = NULL;
+
+                     free (binding->wdirname);
+                     binding->wdirname = result;
+
+                     modified = 1;
+                   }
+               }
+             *wdirnamep = result;
+           }
+       }
+#endif
+
       if (codesetp)
        {
          const char *codeset = *codesetp;
@@ -187,11 +234,18 @@ set_binding_values (const char *domainname,
        }
     }
   else if ((dirnamep == NULL || *dirnamep == NULL)
+#if defined _WIN32 && !defined __CYGWIN__
+          && (wdirnamep == NULL || *wdirnamep == NULL)
+#endif
           && (codesetp == NULL || *codesetp == NULL))
     {
       /* Simply return the default values.  */
       if (dirnamep)
        *dirnamep = _nl_default_dirname;
+#if defined _WIN32 && !defined __CYGWIN__
+      if (wdirnamep)
+       *wdirnamep = NULL;
+#endif
       if (codesetp)
        *codesetp = NULL;
     }
@@ -212,8 +266,15 @@ set_binding_values (const char *domainname,
          const char *dirname = *dirnamep;
 
          if (dirname == NULL)
-           /* The default value.  */
-           dirname = _nl_default_dirname;
+           {
+#if defined _WIN32 && !defined __CYGWIN__
+             if (wdirnamep && *wdirnamep != NULL)
+               dirname = NULL;
+             else
+#endif
+               /* The default value.  */
+               dirname = _nl_default_dirname;
+           }
          else
            {
              if (strcmp (dirname, _nl_default_dirname) == 0)
@@ -239,8 +300,34 @@ set_binding_values (const char *domainname,
          new_binding->dirname = (char *) dirname;
        }
       else
-       /* The default value.  */
-       new_binding->dirname = (char *) _nl_default_dirname;
+       {
+#if defined _WIN32 && !defined __CYGWIN__
+         if (wdirnamep && *wdirnamep != NULL)
+           new_binding->dirname = NULL;
+         else
+#endif
+           /* The default value.  */
+           new_binding->dirname = (char *) _nl_default_dirname;
+       }
+
+#if defined _WIN32 && !defined __CYGWIN__
+      if (wdirnamep)
+       {
+         const wchar_t *wdirname = *wdirnamep;
+
+         if (wdirname != NULL)
+           {
+             wchar_t *result = _wcsdup (wdirname);
+             if (__builtin_expect (result == NULL, 0))
+               goto failed_wdirname;
+             wdirname = result;
+           }
+         *wdirnamep = wdirname;
+         new_binding->wdirname = (wchar_t *) wdirname;
+       }
+      else
+       new_binding->wdirname = NULL;
+#endif
 
       if (codesetp)
        {
@@ -293,6 +380,10 @@ set_binding_values (const char *domainname,
       if (0)
        {
        failed_codeset:
+#if defined _WIN32 && !defined __CYGWIN__
+         free (new_binding->wdirname);
+       failed_wdirname:
+#endif
          if (new_binding->dirname != _nl_default_dirname)
            free (new_binding->dirname);
        failed_dirname:
@@ -300,6 +391,10 @@ set_binding_values (const char *domainname,
        failed:
          if (dirnamep)
            *dirnamep = NULL;
+#if defined _WIN32 && !defined __CYGWIN__
+         if (wdirnamep)
+           *wdirnamep = NULL;
+#endif
          if (codesetp)
            *codesetp = NULL;
        }
@@ -346,19 +441,30 @@ BINDTEXTDOMAIN (const char *domainname, const char *dirname)
         }
     }
 #endif
-  set_binding_values (domainname, &dirname, NULL);
+  set_binding_values (domainname, &dirname, NULL, NULL);
 #ifdef __EMX__
   dirname = saved_dirname;
 #endif
   return (char *) dirname;
 }
 
+#if defined _WIN32 && !defined __CYGWIN__
+/* Specify that the DOMAINNAME message catalog will be found
+   in WDIRNAME rather than in the system locale data base.  */
+wchar_t *
+libintl_wbindtextdomain (const char *domainname, const wchar_t *wdirname)
+{
+  set_binding_values (domainname, NULL, &wdirname, NULL);
+  return (wchar_t *) wdirname;
+}
+#endif
+
 /* Specify the character encoding in which the messages from the
    DOMAINNAME message catalog will be returned.  */
 char *
 BIND_TEXTDOMAIN_CODESET (const char *domainname, const char *codeset)
 {
-  set_binding_values (domainname, NULL, &codeset);
+  set_binding_values (domainname, NULL, NULL, &codeset);
   return (char *) codeset;
 }
 
index d0b574a251aaa66d576e9670fcbe6d0c7ae4c58d..0e9710bcf42b1fa8e3983e1d5f277dda971b75bd 100644 (file)
@@ -480,6 +480,9 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
   const char *categoryname;
   const char *categoryvalue;
   const char *dirname;
+#if defined _WIN32 && !defined __CYGWIN__
+  const wchar_t *wdirname;
+#endif
   char *xdomainname;
   char *single_locale;
   char *retval;
@@ -585,6 +588,9 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
      and _nl_load_domain and _nl_find_domain just pass it through.  */
   binding = NULL;
   dirname = bindtextdomain (domainname, NULL);
+# if defined _WIN32 && !defined __CYGWIN__
+  wdirname = wbindtextdomain (domainname, NULL);
+# endif
 #else
   for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next)
     {
@@ -601,11 +607,79 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
     }
 
   if (binding == NULL)
-    dirname = _nl_default_dirname;
+    {
+      dirname = _nl_default_dirname;
+# if defined _WIN32 && !defined __CYGWIN__
+      wdirname = NULL;
+# endif
+    }
   else
     {
       dirname = binding->dirname;
+# if defined _WIN32 && !defined __CYGWIN__
+      wdirname = binding->wdirname;
+# endif
 #endif
+#if defined _WIN32 && !defined __CYGWIN__
+      if (wdirname != NULL
+         ? IS_RELATIVE_FILE_NAME (wdirname)
+         : IS_RELATIVE_FILE_NAME (dirname))
+       {
+         /* We have a relative path.  Make it absolute now.  */
+         size_t wdirname_len;
+         size_t path_max;
+         wchar_t *resolved_wdirname;
+         wchar_t *ret;
+         wchar_t *p;
+
+         if (wdirname != NULL)
+           wdirname_len = wcslen (wdirname);
+         else
+           {
+             wdirname_len = mbstowcs (NULL, dirname, 0);
+
+             if (wdirname_len == (size_t)(-1))
+               /* dirname contains invalid multibyte characters.  Don't signal
+                  an error but simply return the default string.  */
+               goto return_untranslated;
+           }
+         wdirname_len++;
+
+         path_max = (unsigned int) PATH_MAX;
+         path_max += 2;                /* The getcwd docs say to do this.  */
+
+         for (;;)
+           {
+             resolved_wdirname =
+               (wchar_t *)
+               alloca ((path_max + wdirname_len) * sizeof (wchar_t));
+             ADD_BLOCK (block_list, resolved_wdirname);
+
+             __set_errno (0);
+             ret = _wgetcwd (resolved_wdirname, path_max);
+             if (ret != NULL || errno != ERANGE)
+               break;
+
+             path_max += path_max / 2;
+             path_max += PATH_INCR;
+           }
+
+         if (ret == NULL)
+           /* We cannot get the current working directory.  Don't signal an
+              error but simply return the default string.  */
+           goto return_untranslated;
+
+         p = wcschr (resolved_wdirname, L'\0');
+         *p++ = L'/';
+         if (wdirname != NULL)
+           wcscpy (p, wdirname);
+         else
+           mbstowcs (p, dirname, wdirname_len);
+
+         wdirname = resolved_wdirname;
+         dirname = NULL;
+       }
+#else
       if (IS_RELATIVE_FILE_NAME (dirname))
        {
          /* We have a relative path.  Make it absolute now.  */
@@ -639,6 +713,7 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
          stpcpy (stpcpy (strchr (resolved_dirname, '\0'), "/"), dirname);
          dirname = resolved_dirname;
        }
+#endif
 #ifndef IN_LIBGLOCALE
     }
 #endif
@@ -705,7 +780,11 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
 
       /* Find structure describing the message catalog matching the
         DOMAINNAME and CATEGORY.  */
-      domain = _nl_find_domain (dirname, single_locale, xdomainname, binding);
+      domain = _nl_find_domain (dirname,
+#if defined _WIN32 && !defined __CYGWIN__
+                               wdirname,
+#endif
+                               single_locale, xdomainname, binding);
 
       if (domain != NULL)
        {
index 4e28ca040fc9aa7c215c86c3286e7a792a601edb..830558d5935ac285840057b12aba1d0811f15a50 100644 (file)
@@ -55,11 +55,15 @@ gl_rwlock_define_initialized (static, lock);
 
 
 /* Return a data structure describing the message catalog described by
-   the DIRNAME, LOCALE, and DOMAINNAME parameters with respect to the
-   currently established bindings.  */
+   the DIRNAME or WDIRNAME, LOCALE, and DOMAINNAME parameters with respect
+   to the currently established bindings.  */
 struct loaded_l10nfile *
 internal_function
-_nl_find_domain (const char *dirname, char *locale,
+_nl_find_domain (const char *dirname,
+#if defined _WIN32 && !defined __CYGWIN__
+                const wchar_t *wdirname,
+#endif
+                char *locale,
                 const char *domainname, struct binding *domainbinding)
 {
   struct loaded_l10nfile *retval;
@@ -90,9 +94,15 @@ _nl_find_domain (const char *dirname, char *locale,
 
   /* If we have already tested for this locale entry there has to
      be one data set in the list of loaded domains.  */
-  retval = _nl_make_l10nflist (&_nl_loaded_domains, dirname,
-                              strlen (dirname) + 1, 0, locale, NULL, NULL,
-                              NULL, NULL, domainname, 0);
+  retval = _nl_make_l10nflist (&_nl_loaded_domains,
+                              dirname,
+                              dirname != NULL ? strlen (dirname) + 1 : 0,
+#if defined _WIN32 && !defined __CYGWIN__
+                              wdirname,
+                              wdirname != NULL ? wcslen (wdirname) + 1 : 0,
+#endif
+                              0, locale, NULL, NULL, NULL, NULL,
+                              domainname, 0);
 
   gl_rwlock_unlock (lock);
 
@@ -153,8 +163,14 @@ _nl_find_domain (const char *dirname, char *locale,
 
   /* Create all possible locale entries which might be interested in
      generalization.  */
-  retval = _nl_make_l10nflist (&_nl_loaded_domains, dirname,
-                              strlen (dirname) + 1, mask, language, territory,
+  retval = _nl_make_l10nflist (&_nl_loaded_domains,
+                              dirname,
+                              dirname != NULL ? strlen (dirname) + 1 : 0,
+#if defined _WIN32 && !defined __CYGWIN__
+                              wdirname,
+                              wdirname != NULL ? wcslen (wdirname) + 1 : 0,
+#endif
+                              mask, language, territory,
                               codeset, normalized_codeset, modifier,
                               domainname, 1);
 
index 0d7dbd6d41657eb726e07f7533292f9cfff1b501..1efeb192cfd6177e4e6aea20416bcd6fa11a0726 100644 (file)
@@ -1,5 +1,5 @@
 /* Header describing internals of libintl library.
-   Copyright (C) 1995-2016 Free Software Foundation, Inc.
+   Copyright (C) 1995-2020 Free Software Foundation, Inc.
    Written by Ulrich Drepper <drepper@cygnus.com>, 1995.
 
    This program is free software: you can redistribute it and/or modify
@@ -212,6 +212,9 @@ struct binding
 {
   struct binding *next;
   char *dirname;
+#if defined _WIN32 && !defined __CYGWIN__
+  wchar_t *wdirname;
+#endif
   char *codeset;
   char domainname[ZERO];
 };
@@ -256,7 +259,11 @@ extern const char *_nl_locale_name_default (void);
                                       const char *categoryname); */
 #endif
 
-struct loaded_l10nfile *_nl_find_domain (const char *__dirname, char *__locale,
+struct loaded_l10nfile *_nl_find_domain (const char *__dirname,
+#if defined _WIN32 && !defined __CYGWIN__
+                                        const wchar_t *__wdirname,
+#endif
+                                        char *__locale,
                                         const char *__domainname,
                                         struct binding *__domainbinding)
      internal_function;
index 5add7ad8003e195c1d784a556dc3ff95d4f59169..d97060628ccc218590e151f4a83deb9f74a959c9 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 1995-2016, 2018, 2020 Free Software Foundation, Inc.
+/* Copyright (C) 1995-2020 Free Software Foundation, Inc.
    Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
 
    This program is free software: you can redistribute it and/or modify
@@ -33,6 +33,9 @@
 #include <ctype.h>
 #include <sys/types.h>
 #include <stdlib.h>
+#if defined _WIN32 && !defined __CYGWIN__
+# include <wchar.h>
+#endif
 
 #include "loadinfo.h"
 
@@ -86,23 +89,33 @@ pop (int x)
 struct loaded_l10nfile *
 _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
                    const char *dirlist, size_t dirlist_len,
+#if defined _WIN32 && !defined __CYGWIN__
+                   const wchar_t *wdirlist, size_t wdirlist_len,
+#endif
                    int mask, const char *language, const char *territory,
                    const char *codeset, const char *normalized_codeset,
                    const char *modifier,
                    const char *filename, int do_allocate)
 {
   char *abs_filename;
+#if defined _WIN32 && !defined __CYGWIN__
+  wchar_t *abs_wfilename;
+#endif
   struct loaded_l10nfile **lastp;
   struct loaded_l10nfile *retval;
-  char *cp;
   size_t dirlist_count;
   size_t entries;
   int cnt;
 
   /* If LANGUAGE contains an absolute directory specification, we ignore
-     DIRLIST.  */
+     DIRLIST and WDIRLIST.  */
   if (!IS_RELATIVE_FILE_NAME (language))
-    dirlist_len = 0;
+    {
+      dirlist_len = 0;
+#if defined _WIN32 && !defined __CYGWIN__
+      wdirlist_len = 0;
+#endif
+    }
 
   /* Allocate room for the full file name.  */
   abs_filename = (char *) malloc (dirlist_len
@@ -121,50 +134,105 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
     return NULL;
 
   /* Construct file name.  */
-  cp = abs_filename;
-  if (dirlist_len > 0)
-    {
-      memcpy (cp, dirlist, dirlist_len);
+  {
+    char *cp;
+
+    cp = abs_filename;
+    if (dirlist_len > 0)
+      {
+       memcpy (cp, dirlist, dirlist_len);
 #ifdef _LIBC
-      __argz_stringify (cp, dirlist_len, PATH_SEPARATOR);
+       __argz_stringify (cp, dirlist_len, PATH_SEPARATOR);
 #endif
-      cp += dirlist_len;
-      cp[-1] = '/';
-    }
+       cp += dirlist_len;
+       cp[-1] = '/';
+      }
 
-  cp = stpcpy (cp, language);
+    cp = stpcpy (cp, language);
 
-  if ((mask & XPG_TERRITORY) != 0)
-    {
-      *cp++ = '_';
-      cp = stpcpy (cp, territory);
-    }
-  if ((mask & XPG_CODESET) != 0)
-    {
-      *cp++ = '.';
-      cp = stpcpy (cp, codeset);
-    }
-  if ((mask & XPG_NORM_CODESET) != 0)
-    {
-      *cp++ = '.';
-      cp = stpcpy (cp, normalized_codeset);
-    }
-  if ((mask & XPG_MODIFIER) != 0)
+    if ((mask & XPG_TERRITORY) != 0)
+      {
+       *cp++ = '_';
+       cp = stpcpy (cp, territory);
+      }
+    if ((mask & XPG_CODESET) != 0)
+      {
+       *cp++ = '.';
+       cp = stpcpy (cp, codeset);
+      }
+    if ((mask & XPG_NORM_CODESET) != 0)
+      {
+       *cp++ = '.';
+       cp = stpcpy (cp, normalized_codeset);
+      }
+    if ((mask & XPG_MODIFIER) != 0)
+      {
+       *cp++ = '@';
+       cp = stpcpy (cp, modifier);
+      }
+
+    *cp++ = '/';
+    stpcpy (cp, filename);
+  }
+
+#if defined _WIN32 && !defined __CYGWIN__
+  /* Construct wide-char file name.  */
+  if (wdirlist_len > 0)
     {
-      *cp++ = '@';
-      cp = stpcpy (cp, modifier);
-    }
+      /* Since dirlist_len == 0, just concatenate wdirlist and abs_filename.  */
+      /* An upper bound for wcslen (mbstowcs (abs_filename)).  */
+      size_t abs_filename_bound = mbstowcs (NULL, abs_filename, 0);
+      if (abs_filename_bound == (size_t)-1)
+       {
+         free (abs_filename);
+         return NULL;
+       }
+
+      /* Allocate and fill abs_wfilename.  */
+      abs_wfilename =
+       (wchar_t *)
+       malloc ((wdirlist_len + abs_filename_bound + 1) * sizeof (wchar_t));
+      if (abs_wfilename == NULL)
+       {
+         free (abs_filename);
+         return NULL;
+       }
+      wmemcpy (abs_wfilename, wdirlist, wdirlist_len - 1);
+      abs_wfilename[wdirlist_len - 1] = L'/';
+      if (mbstowcs (abs_wfilename + wdirlist_len, abs_filename,
+                   abs_filename_bound + 1)
+         > abs_filename_bound)
+       {
+         free (abs_filename);
+         free (abs_wfilename);
+         return NULL;
+       }
 
-  *cp++ = '/';
-  stpcpy (cp, filename);
+      free (abs_filename);
+      abs_filename = NULL;
+    }
+  else
+    abs_wfilename = NULL;
+#endif
 
   /* Look in list of already loaded domains whether it is already
      available.  */
   lastp = l10nfile_list;
   for (retval = *l10nfile_list; retval != NULL; retval = retval->next)
-    if (retval->filename != NULL)
+    if (retval->filename != NULL
+#if defined _WIN32 && !defined __CYGWIN__
+        || retval->wfilename != NULL
+#endif
+       )
       {
-       int compare = strcmp (retval->filename, abs_filename);
+       int compare =
+#if defined _WIN32 && !defined __CYGWIN__
+         abs_wfilename != NULL
+         ? retval->wfilename != NULL && wcscmp (retval->wfilename, abs_wfilename)
+         : retval->filename != NULL && strcmp (retval->filename, abs_filename);
+#else
+         strcmp (retval->filename, abs_filename);
+#endif
        if (compare == 0)
          /* We found it!  */
          break;
@@ -181,6 +249,9 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
   if (retval != NULL || do_allocate == 0)
     {
       free (abs_filename);
+#if defined _WIN32 && !defined __CYGWIN__
+      free (abs_wfilename);
+#endif
       return retval;
     }
 
@@ -199,10 +270,16 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
   if (retval == NULL)
     {
       free (abs_filename);
+#if defined _WIN32 && !defined __CYGWIN__
+      free (abs_wfilename);
+#endif
       return NULL;
     }
 
   retval->filename = abs_filename;
+#if defined _WIN32 && !defined __CYGWIN__
+  retval->wfilename = abs_wfilename;
+#endif
 
   /* We set retval->data to NULL here; it is filled in later.
      Setting retval->decided to 1 here means that retval does not
@@ -250,7 +327,11 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
        else
 #endif
          retval->successor[entries++]
-           = _nl_make_l10nflist (l10nfile_list, dirlist, dirlist_len,
+           = _nl_make_l10nflist (l10nfile_list,
+                                 dirlist, dirlist_len,
+#if defined _WIN32 && !defined __CYGWIN__
+                                 wdirlist, wdirlist_len,
+#endif
                                  cnt, language, territory, codeset,
                                  normalized_codeset, modifier, filename, 1);
       }
index cfefb32cb163896c55badef17b5b73417a338471..cad4bcb7810cb63bd2bf6c06c5b4e308f1fa5cb4 100644 (file)
@@ -292,6 +292,27 @@ extern char *bindtextdomain (const char *__domainname, const char *__dirname)
        _INTL_ASM (libintl_bindtextdomain);
 #endif
 
+#if defined _WIN32 && !defined __CYGWIN__
+/* Specify that the DOMAINNAME message catalog will be found
+   in WDIRNAME rather than in the system locale data base.  */
+#ifdef _INTL_REDIRECT_INLINE
+extern wchar_t *libintl_wbindtextdomain (const char *__domainname,
+                                         const wchar_t *__wdirname);
+static inline wchar_t *wbindtextdomain (const char *__domainname,
+                                        const wchar_t *__wdirname)
+{
+  return libintl_wbindtextdomain (__domainname, __wdirname);
+}
+#else
+#ifdef _INTL_REDIRECT_MACROS
+# define wbindtextdomain libintl_wbindtextdomain
+#endif
+extern wchar_t *wbindtextdomain (const char *__domainname,
+                                 const wchar_t *__wdirname)
+       _INTL_ASM (libintl_wbindtextdomain);
+#endif
+#endif
+
 /* Specify the character encoding in which the messages from the
    DOMAINNAME message catalog will be returned.  */
 #ifdef _INTL_REDIRECT_INLINE
index 3d61652c4bbfbcf3a5b3a0ad4c0933c86451d173..c0a31d23b9de1ebb63e69a1e4f8b6263c507caec 100644 (file)
@@ -63,6 +63,9 @@
 struct loaded_l10nfile
 {
   const char *filename;
+#if defined _WIN32 && !defined __CYGWIN__
+  const wchar_t *wfilename;
+#endif
   int decided;
 
   const void *data;
@@ -82,13 +85,22 @@ extern const char *_nl_normalize_codeset (const char *codeset,
 /* Lookup a locale dependent file.
    *L10NFILE_LIST denotes a pool of lookup results of locale dependent
    files of the same kind, sorted in decreasing order of ->filename.
+
    DIRLIST and DIRLIST_LEN are an argz list of directories in which to
-   look, containing at least one directory (i.e. DIRLIST_LEN > 0).
+   look.
+   Likewise, on native Windows, WDIRLIST and WDIRLIST_LEN are wide-char
+   list of directories in which to look. Only one of DIRLIST, WDIRLIST
+   is non-NULL.
+   DIRLIST and WDIRLIST contain at least one directory, i.e.
+   DIRLIST_LEN + WDIRLIST_LEN > 0.
    Outside glibc, only one directory is used, i.e.
-   DIRLIST_LEN == strlen (DIRLIST) + 1.
+   DIRLIST_LEN == strlen (DIRLIST) + 1, WDIRLIST_LEN == 0, or
+   DIRLIST_LEN == 0, WDIRLIST_LEN == wcslen (WDIRLIST) + 1.
+
    MASK, LANGUAGE, TERRITORY, CODESET, NORMALIZED_CODESET, MODIFIER
    are the pieces of the locale name, as produced by _nl_explode_name().
    FILENAME is the filename suffix.
+
    The return value is the lookup result, either found in *L10NFILE_LIST,
    or - if DO_ALLOCATE is nonzero - freshly allocated, or possibly NULL.
    If the return value is non-NULL, it is added to *L10NFILE_LIST, and
@@ -97,7 +109,11 @@ extern const char *_nl_normalize_codeset (const char *codeset,
    results from which this lookup result inherits.  */
 extern struct loaded_l10nfile *
 _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
-                   const char *dirlist, size_t dirlist_len, int mask,
+                   const char *dirlist, size_t dirlist_len,
+#if defined _WIN32 && !defined __CYGWIN__
+                   const wchar_t *wdirlist, size_t wdirlist_len,
+#endif
+                   int mask,
                    const char *language, const char *territory,
                    const char *codeset, const char *normalized_codeset,
                    const char *modifier,
index ea514c101b300dd6668b7f13e46139558ba06a3e..78c40ca1aa0db8ad586055d425308aaaf16587b7 100644 (file)
@@ -1,5 +1,5 @@
 /* Load needed message catalogs.
-   Copyright (C) 1995-2019 Free Software Foundation, Inc.
+   Copyright (C) 1995-2020 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
@@ -836,12 +836,23 @@ _nl_load_domain (struct loaded_l10nfile *domain_file,
      might be NULL.  This can happen when according to the given
      specification the locale file name is different for XPG and CEN
      syntax.  */
-  if (domain_file->filename == NULL)
-    goto out;
-
-  /* Try to open the addressed file.  */
-  fd = open (domain_file->filename, O_RDONLY | O_BINARY);
-  if (fd == -1)
+  if (domain_file->filename != NULL)
+    {
+      /* Try to open the addressed file.  */
+      fd = open (domain_file->filename, O_RDONLY | O_BINARY);
+      if (fd == -1)
+        goto out;
+    }
+#if defined _WIN32 && !defined __CYGWIN__
+  else if (domain_file->wfilename != NULL)
+    {
+      /* Try to open the addressed file.  */
+      fd = _wopen (domain_file->wfilename, O_RDONLY | O_BINARY);
+      if (fd == -1)
+        goto out;
+    }
+#endif
+  else
     goto out;
 
   /* We must know about the size of the file.  */
index 6479786f5e597d3a6585ac81aae62a945c0da98b..2f90372414f4e2e223f99e0a9d2277ea63f8b4b5 100644 (file)
@@ -5894,6 +5894,15 @@ achieved when the program executes a @code{chdir} command.  Relative
 paths should always be avoided to avoid dependencies and
 unreliabilities.
 
+@example
+wchar_t *wbindtextdomain (const char *domain_name,
+                          const wchar_t *dir_name);
+@end example
+
+This function is provided only on native Windows platforms.  It is like
+@code{bindtextdomain}, except that the @var{dir_name} parameter is a
+wide string (in UTF-16 encoding, as usual on Windows).
+
 @node Locating Catalogs
 @subsection Locating Message Catalog Files
 @cindex message catalog files location
@@ -8295,6 +8304,9 @@ bindtextdomain (@var{PACKAGE}, LOCALEDIR);
 textdomain (@var{PACKAGE});
 @end example
 
+On native Windows platforms, the @code{main} function may call
+@code{wbindtextdomain} instead of @code{bindtextdomain}.
+
 To make LOCALEDIR known to the program, add the following lines to
 @file{Makefile.in}:
 
@@ -8971,6 +8983,7 @@ making the @code{textdomain} function available from within the
 language, or by introducing a magic variable called @code{TEXTDOMAIN}.
 Similarly, you should allow the programmer to designate where to search
 for message catalogs, by providing access to the @code{bindtextdomain}
+function or --- on native Windows platforms --- to the @code{wbindtextdomain}
 function.
 
 @item
@@ -9545,7 +9558,7 @@ For C: @code{c}, @code{h}.
 @code{textdomain} function
 
 @item bindtextdomain
-@code{bindtextdomain} function
+@code{bindtextdomain} and @code{wbindtextdomain} functions
 
 @item setlocale
 Programmer must call @code{setlocale (LC_ALL, "")}
index b2bd779e41f018b2abd8c885f5cd02ed0f3eccf5..055b92c74e38129e049ca4a4defbea3910949b70 100644 (file)
@@ -1,5 +1,5 @@
 ## Makefile for the gettext-tools/tests subdirectory of GNU gettext
-## Copyright (C) 1995-1997, 2001-2010, 2012-2016, 2018-2019 Free Software Foundation, Inc.
+## Copyright (C) 1995-1997, 2001-2010, 2012-2016, 2018-2020 Free Software Foundation, Inc.
 ##
 ## This program is free software: you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@ EXTRA_DIST =
 MOSTLYCLEANFILES = core *.stackdump
 
 TESTS = gettext-1 gettext-2 \
-       intl-1 intl-2 intl-3 intl-4 intl-5 \
+       intl-1 intl-2 intl-3 intl-4 intl-5 intl-6 \
        intl-setlocale-1 intl-setlocale-2 \
        intl-thread-1 intl-thread-2 intl-thread-3 \
        intl-version \
@@ -234,7 +234,7 @@ DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
 LDADD = $(LDADD_@USE_INCLUDED_LIBINTL@) @INTL_MACOSX_LIBS@
 LDADD_yes = ../intl/libintl.la @LTLIBTHREAD@
 LDADD_no = ../intl/libgnuintl.la @LTLIBTHREAD@ @LTLIBINTL@
-check_PROGRAMS = tstgettext tstngettext testlocale intl-1-prg intl-3-prg intl-4-prg intl-5-prg intl-setlocale-1-prg intl-setlocale-2-prg intl-thread-1-prg intl-thread-2-prg intl-thread-3-prg intl-version-prg cake fc3 fc4 fc5 gettextpo-1-prg sentence-1-prg
+check_PROGRAMS = tstgettext tstngettext testlocale intl-1-prg intl-3-prg intl-4-prg intl-5-prg intl-6-prg intl-setlocale-1-prg intl-setlocale-2-prg intl-thread-1-prg intl-thread-2-prg intl-thread-3-prg intl-version-prg cake fc3 fc4 fc5 gettextpo-1-prg sentence-1-prg
 tstgettext_SOURCES = \
   tstgettext.c ../../gettext-runtime/src/escapes.h \
   setlocale.c
@@ -252,6 +252,8 @@ intl_4_prg_SOURCES = intl-4-prg.c
 intl_4_prg_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD)
 intl_5_prg_SOURCES = intl-5-prg.c
 intl_5_prg_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD)
+intl_6_prg_SOURCES = intl-6-prg.c
+intl_6_prg_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD)
 intl_setlocale_1_prg_SOURCES = intl-setlocale-1-prg.c
 intl_setlocale_1_prg_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD)
 intl_setlocale_2_prg_SOURCES = intl-setlocale-2-prg.c
diff --git a/gettext-tools/tests/intl-6 b/gettext-tools/tests/intl-6
new file mode 100755 (executable)
index 0000000..0e91569
--- /dev/null
@@ -0,0 +1,42 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test that gettext() does basic translation lookup, even when the directory
+# with the message catalogs contains arbitrary Unicode characters.
+
+test -d in-6 || mkdir in-6
+test -d in-6/fr || mkdir in-6/fr
+test -d in-6/fr/LC_MESSAGES || mkdir in-6/fr/LC_MESSAGES
+
+: ${MSGFMT=msgfmt}
+${MSGFMT} -o in-6/fr/LC_MESSAGES/tstprog.mo "$wabs_srcdir"/intl-1.po
+
+: ${DIFF=diff}
+cat <<EOF > in-6.ok
+fromage
+EOF
+
+: ${LOCALE_FR=fr_FR}
+: ${LOCALE_FR_UTF8=fr_FR.UTF-8}
+if test $LOCALE_FR != none; then
+  prepare_locale_ in-6/fr in-6/$LOCALE_FR
+  ../intl-6-prg in-6 $LOCALE_FR > in-6.tmp || Exit 1
+  LC_ALL=C tr -d '\r' < in-6.tmp > in-6.out || Exit 1
+  ${DIFF} in-6.ok in-6.out || Exit 1
+fi
+if test $LOCALE_FR_UTF8 != none; then
+  prepare_locale_ in-6/fr in-6/$LOCALE_FR_UTF8
+  ../intl-6-prg in-6 $LOCALE_FR_UTF8 > in-6.tmp || Exit 1
+  LC_ALL=C tr -d '\r' < in-6.tmp > in-6.out || Exit 1
+  ${DIFF} in-6.ok in-6.out || Exit 1
+fi
+if test $LOCALE_FR = none && test $LOCALE_FR_UTF8 = none; then
+  if test -f /usr/bin/localedef; then
+    echo "Skipping test: no french locale is installed"
+  else
+    echo "Skipping test: no french locale is supported"
+  fi
+  Exit 77
+fi
+
+Exit 0
diff --git a/gettext-tools/tests/intl-6-prg.c b/gettext-tools/tests/intl-6-prg.c
new file mode 100644 (file)
index 0000000..f2f9a65
--- /dev/null
@@ -0,0 +1,98 @@
+/* Test program, used by the intl-6 test.
+   Copyright (C) 2000, 2005, 2007, 2013, 2018, 2020 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <locale.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#if defined _WIN32 && !defined __CYGWIN__
+# include <wchar.h>
+#endif
+
+#include "xsetenv.h"
+/* Make sure we use the included libintl, not the system's one. */
+#undef _LIBINTL_H
+#include "libgnuintl.h"
+
+const char unicodedir[] = "русский…日本語…हिंदी…😷";
+#if defined _WIN32 && !defined __CYGWIN__
+const wchar_t wunicodedir[] = /* the same string in UTF-16 encoding */
+  { 0x0440, 0x0443, 0x0441, 0x0441, 0x043A, 0x0438, 0x0439, 0x2026,
+    0x65E5, 0x672C, 0x8A9E, 0x2026,
+    0x0939, 0x093F, 0x0902, 0x0926, 0x0940, 0x2026,
+    0xD83D, 0xDE37, 0
+  };
+#endif
+
+int
+main (int argc, char *argv[])
+{
+  const char *dir = argv[1];
+  const char *locale = argv[2];
+  wchar_t *wdir;
+  int ret;
+
+  wdir = (wchar_t *) malloc ((strlen (dir) + 1) * sizeof (wchar_t));
+  mbstowcs (wdir, dir, strlen (dir) + 1);
+
+  /* Rename the directory.  */
+#if defined _WIN32 && !defined __CYGWIN__
+  ret = _wrename (wdir, wunicodedir);
+#else
+  ret = rename (dir, unicodedir);
+#endif
+  if (ret != 0)
+    {
+      fprintf (stderr, "Initial rename failed.\n");
+      exit (1);
+    }
+
+  /* Clean up environment.  */
+  unsetenv ("LANGUAGE");
+  unsetenv ("OUTPUT_CHARSET");
+
+  textdomain ("tstprog");
+
+  xsetenv ("LC_ALL", locale, 1);
+  if (setlocale (LC_ALL, "") == NULL)
+    setlocale (LC_ALL, "C");
+
+#if defined _WIN32 && !defined __CYGWIN__
+  wbindtextdomain ("tstprog", wunicodedir);
+#else
+  bindtextdomain ("tstprog", unicodedir);
+#endif
+
+  printf ("%s\n", gettext ("cheese"));
+
+  /* Rename the directory back.  */
+#if defined _WIN32 && !defined __CYGWIN__
+  ret = _wrename (wunicodedir, wdir);
+#else
+  ret = rename (unicodedir, dir);
+#endif
+  if (ret != 0)
+    {
+      fprintf (stderr, "Final rename failed.\n");
+      exit (1);
+    }
+
+  return 0;
+}