]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
intl: Fix translation lookup failure when wbindtextdomain is used.
authorBruno Haible <bruno@clisp.org>
Sat, 17 Jun 2023 08:49:04 +0000 (10:49 +0200)
committerBruno Haible <bruno@clisp.org>
Sat, 17 Jun 2023 10:58:50 +0000 (12:58 +0200)
Reported by Luca Bacci <luca.bacci@outlook.com> at
<https://savannah.gnu.org/bugs/index.php?64311>.

The bug showed up when a call to bindtextdomain() with an absolute directory
and a call to wbindtextdomain() for a different domain was in effect.
Translations in the first referenced domain worked, translations in the second
referenced domain did not.

* gettext-runtime/intl/loadinfo.h (struct loaded_l10nfile, _nl_make_l10nflist):
Improve comments.
* gettext-runtime/intl/finddomain.c (_nl_loaded_domains): Likewise.
* gettext-runtime/intl/l10nflist.c (_nl_make_l10nflist): Fix code that looks up
the loaded_l10nfile in the given list, and make it more maintainable.
* gettext-tools/tests/intl-6: Improve comments.
* gettext-tools/tests/intl-6-prg.c (main): Restructure.
* gettext-tools/tests/intl-7: New file, based on gettext-tools/tests/intl-6.
* gettext-tools/tests/intl-6-prg.c: New file, based on
gettext-tools/tests/intl-6-prg.c.
* gettext-tools/tests/Makefile.am (TESTS): Add intl-7.
(check_PROGRAMS): Add intl-7-prg.
(intl_7_prg_SOURCES, intl_7_prg_LDADD): New variables.

.gitignore
gettext-runtime/intl/finddomain.c
gettext-runtime/intl/l10nflist.c
gettext-runtime/intl/loadinfo.h
gettext-tools/tests/Makefile.am
gettext-tools/tests/intl-6
gettext-tools/tests/intl-6-prg.c
gettext-tools/tests/intl-7 [new file with mode: 0755]
gettext-tools/tests/intl-7-prg.c [new file with mode: 0644]

index 2983e47b1e5bca36110840212bac4dfd59579561..3a15ea8a2989258b8a6f7ff918dc9b207d69a4fd 100644 (file)
@@ -773,6 +773,8 @@ autom4te.cache/
 /gettext-tools/tests/intl-5-prg.exe
 /gettext-tools/tests/intl-6-prg
 /gettext-tools/tests/intl-6-prg.exe
+/gettext-tools/tests/intl-7-prg
+/gettext-tools/tests/intl-7-prg.exe
 /gettext-tools/tests/intl-setlocale-1-prg
 /gettext-tools/tests/intl-setlocale-1-prg.exe
 /gettext-tools/tests/intl-setlocale-2-prg
index e54b7346fbc4793340594eff979e1c88dd8333e9..59352b5d2e6084d6aca695aa2cf8d94354aa98d3 100644 (file)
@@ -1,5 +1,5 @@
 /* Handle list of needed message catalogs
-   Copyright (C) 1995-2021 Free Software Foundation, Inc.
+   Copyright (C) 1995-2023 Free Software Foundation, Inc.
    Written by Ulrich Drepper <drepper@gnu.org>, 1995.
 
    This program is free software: you can redistribute it and/or modify
 #endif
 
 /* @@ end of prolog @@ */
-/* List of already loaded domains.  */
+/* List of already loaded domains.
+   On most platforms, it is sorted in decreasing order of ->filename.
+   On native Windows platforms, the elements with ->filename != NULL
+   are sorted in decreasing order of ->filename, and the elements with
+   ->wfilename != NULL are sorted in decreasing order of ->wfilename.  */
 static struct loaded_l10nfile *_nl_loaded_domains;
 
 /* Lock that protects the access to _NL_LOADED_DOMAINS.  */
index d97060628ccc218590e151f4a83deb9f74a959c9..8d9eae81137a2f4d5997f3195339c568e72e8e93 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 1995-2020 Free Software Foundation, Inc.
+/* Copyright (C) 1995-2023 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
@@ -218,31 +218,48 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
   /* 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 defined _WIN32 && !defined __CYGWIN__
-        || retval->wfilename != NULL
+  if (abs_wfilename != NULL)
+    {
+      for (retval = *l10nfile_list; retval != NULL; retval = retval->next)
+       {
+         if (retval->wfilename != NULL)
+           {
+             int compare = wcscmp (retval->wfilename, abs_wfilename);
+             if (compare == 0)
+               /* We found it!  */
+               break;
+             if (compare < 0)
+               {
+                 /* It's not in the list, and we have found the place where it
+                    needs to be inserted: at *LASTP.  */
+                 retval = NULL;
+                 break;
+               }
+           }
+         lastp = &retval->next;
+       }
+    }
+  else
 #endif
-       )
+    for (retval = *l10nfile_list; retval != NULL; retval = retval->next)
       {
-       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);
+       if (retval->filename != NULL)
 #endif
-       if (compare == 0)
-         /* We found it!  */
-         break;
-       if (compare < 0)
          {
-           /* It's not in the list.  */
-           retval = NULL;
-           break;
+           int compare = strcmp (retval->filename, abs_filename);
+           if (compare == 0)
+             /* We found it!  */
+             break;
+           if (compare < 0)
+             {
+               /* It's not in the list, and we have found the place where it
+                  needs to be inserted: at *LASTP.  */
+               retval = NULL;
+               break;
+             }
          }
-
        lastp = &retval->next;
       }
 
index c0a31d23b9de1ebb63e69a1e4f8b6263c507caec..97d8dd4a1bad770d34e217182b1c5385d9cf9c43 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 1996-2020 Free Software Foundation, Inc.
+/* Copyright (C) 1996-2023 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
 
@@ -62,6 +62,9 @@
 
 struct loaded_l10nfile
 {
+  /* The file name of the localization file.
+     It is set at construction time and then never changed.
+     Exactly one of filename, wfilename is non-NULL.  */
   const char *filename;
 #if defined _WIN32 && !defined __CYGWIN__
   const wchar_t *wfilename;
@@ -84,7 +87,11 @@ 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.
+   files of the same kind.
+   On most platforms, it is sorted in decreasing order of ->filename.
+   On native Windows platforms, the elements with ->filename != NULL
+   are sorted in decreasing order of ->filename, and the elements with
+   ->wfilename != NULL are sorted in decreasing order of ->wfilename.
 
    DIRLIST and DIRLIST_LEN are an argz list of directories in which to
    look.
index f39459e79859c6c53cb94954c80faf7e18078b05..f4e3d227d55c7695857359ec67ea84cadcdd490f 100644 (file)
@@ -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-6 \
+       intl-1 intl-2 intl-3 intl-4 intl-5 intl-6 intl-7 \
        intl-setlocale-1 intl-setlocale-2 \
        intl-thread-1 intl-thread-2 intl-thread-3 \
        intl-version \
@@ -294,7 +294,7 @@ DEFS = -DLOCALEDIR=$(localedir_c_make) @DEFS@
 LDADD = $(LDADD_@USE_INCLUDED_LIBINTL@) @INTL_MACOSX_LIBS@
 LDADD_yes = ../../gettext-runtime/intl/libintl.la @LTLIBTHREAD@
 LDADD_no = ../../gettext-runtime/intl/libgnuintl.la @LTLIBTHREAD@ @LTLIBINTL@
-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
+check_PROGRAMS = tstgettext tstngettext testlocale intl-1-prg intl-3-prg intl-4-prg intl-5-prg intl-6-prg intl-7-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
@@ -314,6 +314,8 @@ 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_7_prg_SOURCES = intl-7-prg.c
+intl_7_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
index 970c58caf343f47654826ec741693eea8c179a9f..c1b3637edaa9b9a48d7b247a472b3b0281acf983 100755 (executable)
@@ -3,6 +3,7 @@
 
 # Test that gettext() does basic translation lookup, even when the directory
 # with the message catalogs contains arbitrary Unicode characters.
+# On native Windows, it uses wbindtextdomain().
 
 test -d in-6 || mkdir in-6
 test -d in-6/fr || mkdir in-6/fr
index f2f9a65dc151872581b583c686be80084eed43ba..0cf4bf8433a5a752d68ce93ee5da82411a9fa7ec 100644 (file)
@@ -1,5 +1,5 @@
 /* Test program, used by the intl-6 test.
-   Copyright (C) 2000, 2005, 2007, 2013, 2018, 2020 Free Software Foundation, Inc.
+   Copyright (C) 2000, 2005, 2007, 2013, 2018, 2020, 2023 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
@@ -49,6 +49,16 @@ main (int argc, char *argv[])
   wchar_t *wdir;
   int ret;
 
+  /* Clean up environment.  */
+  unsetenv ("LANGUAGE");
+  unsetenv ("OUTPUT_CHARSET");
+
+  xsetenv ("LC_ALL", locale, 1);
+  if (setlocale (LC_ALL, "") == NULL)
+    setlocale (LC_ALL, "C");
+
+  /* Set up translation domain.  */
+
   wdir = (wchar_t *) malloc ((strlen (dir) + 1) * sizeof (wchar_t));
   mbstowcs (wdir, dir, strlen (dir) + 1);
 
@@ -64,22 +74,15 @@ main (int argc, char *argv[])
       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
 
+  /* Look up the translation.  */
   printf ("%s\n", gettext ("cheese"));
 
   /* Rename the directory back.  */
diff --git a/gettext-tools/tests/intl-7 b/gettext-tools/tests/intl-7
new file mode 100755 (executable)
index 0000000..59a2fd8
--- /dev/null
@@ -0,0 +1,137 @@
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test that bindtextdomain() and wbindtextdomain(), in the same program,
+# but applied to several different domains, work fine.
+
+# Try to find a writable absolute directory name.
+case "$host_os" in
+  mingw*)
+    # On native Windows, it is important that ${ldir} is an absolute directory,
+    # so that the test exercises a mix between directories specified as 'char *'
+    # and directories specified as 'wchar_t *'. (If we used a relative
+    # directory, the code in dcigettext.c would transform it to a 'wchar_t *'.)
+    if (cygpath --version) >/dev/null 2>/dev/null; then
+      writable=`cygpath -w "$HOME"`
+    else
+      if test -n "${HOMEDRIVE}${HOMEPATH}" && test -w "${HOMEDRIVE}${HOMEPATH}"; then
+        writable="${HOMEDRIVE}${HOMEPATH}"
+      else
+        writable="C:"
+      fi
+    fi
+    ldir="${writable}"'\gettext-intl-7-'$$
+    trap 'rm -fr "${ldir}"' HUP INT QUIT TERM
+    ;;
+  *)
+    ldir="`pwd`/in-7l"
+    ;;
+esac
+
+test -d "${ldir}" || mkdir "${ldir}"
+test -d "${ldir}"/fr || mkdir "${ldir}"/fr
+test -d "${ldir}"/fr/LC_MESSAGES || mkdir "${ldir}"/fr/LC_MESSAGES
+
+test -d in-7 || mkdir in-7
+test -d in-7/fr || mkdir in-7/fr
+test -d in-7/fr/LC_MESSAGES || mkdir in-7/fr/LC_MESSAGES
+test -d in-7/fr_FR || mkdir in-7/fr_FR
+test -d in-7/fr_FR/LC_MESSAGES || mkdir in-7/fr_FR/LC_MESSAGES
+
+cat <<EOF > in-7l.po
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=US-ASCII\n"
+"Content-Transfer-Encoding: 7-bit\n"
+
+msgid "dog"
+msgstr "chien"
+EOF
+
+cat <<EOF > in-7-1.po
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=US-ASCII\n"
+"Content-Transfer-Encoding: 7-bit\n"
+
+msgid "cheese"
+msgstr "fromage"
+EOF
+
+cat <<EOF > in-7-2.po
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=US-ASCII\n"
+"Content-Transfer-Encoding: 7-bit\n"
+
+msgid "cheese"
+msgstr "camembert"
+EOF
+
+cat <<EOF > in-7-3.po
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=US-ASCII\n"
+"Content-Transfer-Encoding: 7-bit\n"
+
+msgid "jam"
+msgstr "confiture"
+EOF
+
+: ${MSGFMT=msgfmt}
+${MSGFMT} -o "${ldir}"/fr/LC_MESSAGES/tstlib.mo in-7l.po
+${MSGFMT} -o in-7/fr/LC_MESSAGES/tstfoo.mo in-7-1.po
+${MSGFMT} -o in-7/fr_FR/LC_MESSAGES/tstfoo.mo in-7-2.po
+${MSGFMT} -o in-7/fr/LC_MESSAGES/tstbar.mo in-7-3.po
+
+: ${DIFF=diff}
+cat <<EOF > in-7.ok
+chien
+confiture
+camembert
+EOF
+
+: ${LOCALE_FR=fr_FR}
+: ${LOCALE_FR_UTF8=fr_FR.UTF-8}
+if test $LOCALE_FR != none; then
+  case "$host_os" in
+    cygwin*)
+      # On Cygwin, file names are interpreted according to the character
+      # encoding of the current locale, see
+      # <https://cygwin.com/cygwin-ug-net/setup-locale.html>.
+      # Therefore arbitrary Unicode characters are only supported in UTF-8
+      # locales (including "C.UTF-8") and in the "C" locale. In particular,
+      # they are not supported in the fr_FR.ISO-8859-1 locale.
+      ;;
+    *)
+      prepare_locale_ in-7/fr in-7/$LOCALE_FR
+      prepare_locale_ in-7/fr_FR in-7/$LOCALE_FR
+      prepare_locale_ "${ldir}"/fr "${ldir}"/$LOCALE_FR
+      ../intl-7-prg "${ldir}" in-7 $LOCALE_FR > in-7.tmp || Exit 1
+      LC_ALL=C tr -d '\r' < in-7.tmp > in-7.out || Exit 1
+      ${DIFF} in-7.ok in-7.out || Exit 1
+      ;;
+  esac
+fi
+if test $LOCALE_FR_UTF8 != none; then
+  prepare_locale_ in-7/fr in-7/$LOCALE_FR_UTF8
+  prepare_locale_ in-7/fr_FR in-7/$LOCALE_FR_UTF8
+  prepare_locale_ "${ldir}"/fr "${ldir}"/$LOCALE_FR_UTF8
+  ../intl-7-prg "${ldir}" in-7 $LOCALE_FR_UTF8 > in-7.tmp || Exit 1
+  LC_ALL=C tr -d '\r' < in-7.tmp > in-7.out || Exit 1
+  ${DIFF} in-7.ok in-7.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-7-prg.c b/gettext-tools/tests/intl-7-prg.c
new file mode 100644 (file)
index 0000000..6c62c7f
--- /dev/null
@@ -0,0 +1,106 @@
+/* Test program, used by the intl-7 test.
+   Copyright (C) 2000, 2005, 2007, 2013, 2018, 2020, 2023 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 *lib_dir = argv[1];
+  const char *dir = argv[2];
+  const char *locale = argv[3];
+  wchar_t *wdir;
+  int ret;
+
+  /* Clean up environment.  */
+  unsetenv ("LANGUAGE");
+  unsetenv ("OUTPUT_CHARSET");
+
+  xsetenv ("LC_ALL", locale, 1);
+  if (setlocale (LC_ALL, "") == NULL)
+    setlocale (LC_ALL, "C");
+
+  /* Set up translation domains.  */
+
+  bindtextdomain ("tstlib", lib_dir);
+
+  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);
+    }
+
+#if defined _WIN32 && !defined __CYGWIN__
+  wbindtextdomain ("tstbar", wunicodedir);
+  wbindtextdomain ("tstfoo", wunicodedir);
+#else
+  bindtextdomain ("tstbar", unicodedir);
+  bindtextdomain ("tstfoo", unicodedir);
+#endif
+
+  /* Look up the translations.  */
+  printf ("%s\n", dgettext ("tstlib", "dog"));
+  printf ("%s\n", dgettext ("tstbar", "jam"));
+  printf ("%s\n", dgettext ("tstfoo", "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;
+}