about summary refs log tree commit diff
path: root/intl
diff options
context:
space:
mode:
Diffstat (limited to 'intl')
-rw-r--r--intl/Makefile30
-rw-r--r--intl/Versions10
-rw-r--r--intl/dcgettext.c835
-rw-r--r--intl/dcigettext.c1000
-rw-r--r--intl/dcngettext.c64
-rw-r--r--intl/dngettext.c67
-rw-r--r--intl/gettext.c8
-rw-r--r--intl/gettextP.h78
-rw-r--r--intl/libintl.h25
-rw-r--r--intl/loadinfo.h4
-rw-r--r--intl/loadmsgcat.c84
-rw-r--r--intl/ngettext.c78
-rw-r--r--intl/plural.c1218
-rw-r--r--intl/plural.y290
-rw-r--r--intl/po2test.sed70
-rw-r--r--intl/tst-gettext.c322
-rwxr-xr-xintl/tst-gettext.sh41
17 files changed, 3378 insertions, 846 deletions
diff --git a/intl/Makefile b/intl/Makefile
index 4712179290..21c73e7566 100644
--- a/intl/Makefile
+++ b/intl/Makefile
@@ -1,4 +1,4 @@
-# Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
+# Copyright (C) 1995-1999, 2000 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
@@ -21,16 +21,40 @@
 subdir = intl
 headers = libintl.h
 routines = bindtextdom dcgettext dgettext gettext	\
+	   dcigettext dcngettext dngettext ngettext \
 	   finddomain loadmsgcat localealias textdomain	\
-	   l10nflist explodename
-distribute = gettext.h gettextP.h hash-string.h loadinfo.h locale.alias
+	   l10nflist explodename plural
+distribute = gettext.h gettextP.h hash-string.h loadinfo.h locale.alias \
+	     plural.y po2test.sed tst-gettext.sh
+
+test-srcs := tst-gettext
+
+before-compile = $(objpfx)msgs.h
 
 install-others = $(inst_msgcatdir)/locale.alias
 
+plural.c: plural.y
+	$(YACC) $(YFLAGS) $@ $^
+ifeq ($(with-cvs),yes)
+	test ! -d CVS || cvs $(CVSOPTS) commit -m'$(YACC) $(YFLAGS) $@ $^' $@
+endif
+$(objpfx)plural.o: plural.c
+
 include ../Rules
 
+.PHONY: do-gettext-test
+tests: do-gettext-test
+do-gettext-test: tst-gettext.sh $(objpfx)tst-gettext
+	$(SHELL) -e $< $(common-objpfx) $(objpfx)
+
+$(objpfx)msgs.h: po2test.sed ../po/de.po
+	sed -f $^ > $@
+
+CFLAGS-tst-gettext.c = -DTESTSTRS_H=\"$(objpfx)msgs.h\"
+
 CPPFLAGS += -D'GNULOCALEDIR="$(msgcatdir)"' \
 	    -D'LOCALE_ALIAS_PATH="$(msgcatdir):$(i18ndir)"'
+YFLAGS = --name-prefix=__gettext --output
 
 $(inst_msgcatdir)/locale.alias: locale.alias $(+force)
 	$(do-install)
diff --git a/intl/Versions b/intl/Versions
index acf0ce0ed9..c784f67a66 100644
--- a/intl/Versions
+++ b/intl/Versions
@@ -18,4 +18,14 @@ libc {
     # t*
     textdomain;
   }
+  GLIBC_2.2 {
+    # functions used in inline functions or macros
+    __dcngettext;
+
+    # d*
+    dcngettext; dngettext;
+
+    # n*
+    ngettext;
+  }
 }
diff --git a/intl/dcgettext.c b/intl/dcgettext.c
index 8e51970495..5be8e4ff70 100644
--- a/intl/dcgettext.c
+++ b/intl/dcgettext.c
@@ -1,5 +1,5 @@
 /* Implementation of the dcgettext(3) function.
-   Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
+   Copyright (C) 1995-1999, 2000 Free Software Foundation, Inc.
 
    This file is part of the GNU C Library.  Its master source is NOT part of
    the C library, however.
@@ -23,70 +23,6 @@
 # include <config.h>
 #endif
 
-#include <sys/types.h>
-
-#if defined __GNUC__ && !defined C_ALLOCA
-# define alloca __builtin_alloca
-# define HAVE_ALLOCA 1
-#else
-# if (defined HAVE_ALLOCA_H || defined _LIBC) && !defined C_ALLOCA
-#  include <alloca.h>
-# else
-#  ifdef _AIX
- #pragma alloca
-#  else
-#   ifndef alloca
-char *alloca ();
-#   endif
-#  endif
-# endif
-#endif
-
-#include <errno.h>
-#ifndef errno
-extern int errno;
-#endif
-#ifndef __set_errno
-# define __set_errno(val) errno = (val)
-#endif
-
-#if defined STDC_HEADERS || defined _LIBC
-# include <stdlib.h>
-#else
-char *getenv ();
-# ifdef HAVE_MALLOC_H
-#  include <malloc.h>
-# else
-void free ();
-# endif
-#endif
-
-#if defined HAVE_STRING_H || defined _LIBC
-# ifndef _GNU_SOURCE
-#  define _GNU_SOURCE	1
-# endif
-# include <string.h>
-#else
-# include <strings.h>
-#endif
-#if !HAVE_STRCHR && !defined _LIBC
-# ifndef strchr
-#  define strchr index
-# endif
-#endif
-
-#if defined HAVE_UNISTD_H || defined _LIBC
-# include <unistd.h>
-#endif
-
-#if defined HAVE_LOCALE_H || defined _LIBC
-# include <locale.h>
-#endif
-
-#if defined HAVE_SYS_PARAM_H || defined _LIBC
-# include <sys/param.h>
-#endif
-
 #include "gettext.h"
 #include "gettextP.h"
 #ifdef _LIBC
@@ -94,225 +30,19 @@ void free ();
 #else
 # include "libgettext.h"
 #endif
-#include "hash-string.h"
-
-/* Thread safetyness.  */
-#ifdef _LIBC
-# include <bits/libc-lock.h>
-#endif
 
 /* @@ end of prolog @@ */
 
-#ifdef _LIBC
-/* Rename the non ANSI C functions.  This is required by the standard
-   because some ANSI C functions will require linking with this object
-   file and the name space must not be polluted.  */
-# define getcwd __getcwd
-# ifndef stpcpy
-#  define stpcpy __stpcpy
-# endif
-#else
-# if !defined HAVE_GETCWD
-char *getwd ();
-#  define getcwd(buf, max) getwd (buf)
-# else
-char *getcwd ();
-# endif
-# ifndef HAVE_STPCPY
-static char *stpcpy PARAMS ((char *dest, const char *src));
-# endif
-# ifndef HAVE_MEMPCPY
-static void *mempcpy PARAMS ((void *dest, const void *src, size_t n));
-# endif
-#endif
-
-/* Amount to increase buffer size by in each try.  */
-#define PATH_INCR 32
-
-/* The following is from pathmax.h.  */
-/* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define
-   PATH_MAX but might cause redefinition warnings when sys/param.h is
-   later included (as on MORE/BSD 4.3).  */
-#if defined _POSIX_VERSION || (defined HAVE_LIMITS_H && !defined __GNUC__)
-# include <limits.h>
-#endif
-
-#ifndef _POSIX_PATH_MAX
-# define _POSIX_PATH_MAX 255
-#endif
-
-#if !defined PATH_MAX && defined _PC_PATH_MAX
-# define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf ("/", _PC_PATH_MAX))
-#endif
-
-/* Don't include sys/param.h if it already has been.  */
-#if defined HAVE_SYS_PARAM_H && !defined PATH_MAX && !defined MAXPATHLEN
-# include <sys/param.h>
-#endif
-
-#if !defined PATH_MAX && defined MAXPATHLEN
-# define PATH_MAX MAXPATHLEN
-#endif
-
-#ifndef PATH_MAX
-# define PATH_MAX _POSIX_PATH_MAX
-#endif
-
-/* XPG3 defines the result of `setlocale (category, NULL)' as:
-   ``Directs `setlocale()' to query `category' and return the current
-     setting of `local'.''
-   However it does not specify the exact format.  And even worse: POSIX
-   defines this not at all.  So we can use this feature only on selected
-   system (e.g. those using GNU C Library).  */
-#ifdef _LIBC
-# define HAVE_LOCALE_NULL
-#endif
-
-/* We want to allocate a string at the end of the struct.  gcc makes
-   this easy.  */
-#ifdef __GNUC__
-# define ZERO 0
-#else
-# define ZERO 1
-#endif
-
-/* This is the type used for the search tree where known translations
-   are stored.  */
-struct known_translation_t
-{
-  /* Domain in which to search.  */
-  char *domain;
-
-  /* The category.  */
-  int category;
-
-  /* State of the catalog counter at the point the string was found.  */
-  int counter;
-
-  /* And finally the translation.  */
-  const char *translation;
-
-  /* Pointer to the string in question.  */
-  char msgid[ZERO];
-};
-
-/* Root of the search tree with known translations.  We can use this
-   only if the system provides the `tsearch' function family.  */
-#if defined HAVE_TSEARCH || defined _LIBC
-# include <search.h>
-
-static void *root;
-
-# ifdef _LIBC
-#  define tsearch __tsearch
-# endif
-
-/* Function to compare two entries in the table of known translations.  */
-static int
-transcmp (const void *p1, const void *p2)
-{
-  struct known_translation_t *s1 = (struct known_translation_t *) p1;
-  struct known_translation_t *s2 = (struct known_translation_t *) p2;
-  int result;
-
-  result = strcmp (s1->msgid, s2->msgid);
-  if (result == 0)
-    {
-      result = strcmp (s1->msgid, s2->msgid);
-      if (result == 0)
-	/* We compare the category last (though this is the cheapest
-	   operation) since it is hopefully always the same (namely
-	   LC_MESSAGES).  */
-	result = s1->category - s2->category;
-    }
-
-  return result;
-}
-#endif
-
-/* Name of the default domain used for gettext(3) prior any call to
-   textdomain(3).  The default value for this is "messages".  */
-const char _nl_default_default_domain[] = "messages";
-
-/* Value used as the default domain for gettext(3).  */
-const char *_nl_current_default_domain = _nl_default_default_domain;
-
-/* Contains the default location of the message catalogs.  */
-const char _nl_default_dirname[] = GNULOCALEDIR;
-
-/* List with bindings of specific domains created by bindtextdomain()
-   calls.  */
-struct binding *_nl_domain_bindings;
-
-/* Prototypes for local functions.  */
-static const char *category_to_name PARAMS ((int category)) internal_function;
-static const char *guess_category_value PARAMS ((int category,
-						 const char *categoryname))
-     internal_function;
-
-
-/* For those loosing systems which don't have `alloca' we have to add
-   some additional code emulating it.  */
-#ifdef HAVE_ALLOCA
-/* Nothing has to be done.  */
-# define ADD_BLOCK(list, address) /* nothing */
-# define FREE_BLOCKS(list) /* nothing */
-#else
-struct block_list
-{
-  void *address;
-  struct block_list *next;
-};
-# define ADD_BLOCK(list, addr)						      \
-  do {									      \
-    struct block_list *newp = (struct block_list *) malloc (sizeof (*newp));  \
-    /* If we cannot get a free block we cannot add the new element to	      \
-       the list.  */							      \
-    if (newp != NULL) {							      \
-      newp->address = (addr);						      \
-      newp->next = (list);						      \
-      (list) = newp;							      \
-    }									      \
-  } while (0)
-# define FREE_BLOCKS(list)						      \
-  do {									      \
-    while (list != NULL) {						      \
-      struct block_list *old = list;					      \
-      list = list->next;						      \
-      free (old);							      \
-    }									      \
-  } while (0)
-# undef alloca
-# define alloca(size) (malloc (size))
-#endif	/* have alloca */
-
-
 /* Names for the libintl functions are a problem.  They must not clash
    with existing names and they should follow ANSI C.  But this source
    code is also used in GNU C Library where the names have a __
    prefix.  So we have to make a difference here.  */
 #ifdef _LIBC
 # define DCGETTEXT __dcgettext
+# define DCIGETTEXT __dcigettext
 #else
 # define DCGETTEXT dcgettext__
-#endif
-
-/* Checking whether the binaries runs SUID must be done and glibc provides
-   easier methods therefore we make a difference here.  */
-#ifdef _LIBC
-# define ENABLE_SECURE __libc_enable_secure
-# define DETERMINE_SECURE
-#else
-static int enable_secure;
-# define ENABLE_SECURE (enable_secure == 1)
-# define DETERMINE_SECURE \
-  if (enable_secure == 0)						      \
-    {									      \
-      if (getuid () != geteuid () || getgid () != getegid ())		      \
-	enable_secure = 1;						      \
-      else								      \
-	enable_secure = -1;						      \
-    }
+# define DCIGETTEXT dcigettext__
 #endif
 
 /* Look up MSGID in the DOMAINNAME message catalog for the current CATEGORY
@@ -323,567 +53,10 @@ DCGETTEXT (domainname, msgid, category)
      const char *msgid;
      int category;
 {
-#ifndef HAVE_ALLOCA
-  struct block_list *block_list = NULL;
-#endif
-  struct loaded_l10nfile *domain;
-  struct binding *binding;
-  const char *categoryname;
-  const char *categoryvalue;
-  char *dirname, *xdomainname;
-  char *single_locale;
-  char *retval;
-  int saved_errno;
-#if defined HAVE_TSEARCH || defined _LIBC
-  struct known_translation_t *search;
-  struct known_translation_t **foundp;
-  size_t msgid_len = strlen (msgid) + 1;
-#endif
-  size_t domainname_len;
-
-  /* If no real MSGID is given return NULL.  */
-  if (msgid == NULL)
-    return NULL;
-
-#if defined HAVE_TSEARCH || defined _LIBC
-  /* Try to find the translation among those which we found at some time.  */
-  search = (struct known_translation_t *) alloca (sizeof (*search)
-						  + msgid_len);
-  memcpy (search->msgid, msgid, msgid_len);
-  search->domain = (char *) domainname;
-  search->category = category;
-
-  foundp = (struct known_translation_t **) tfind (search, &root, transcmp);
-  if (foundp != NULL && (*foundp)->counter == _nl_msg_cat_cntr)
-    return (char *) (*foundp)->translation;
-#endif
-
-  /* Preserve the `errno' value.  */
-  saved_errno = errno;
-
-  /* See whether this is a SUID binary or not.  */
-  DETERMINE_SECURE;
-
-  /* If DOMAINNAME is NULL, we are interested in the default domain.  If
-     CATEGORY is not LC_MESSAGES this might not make much sense but the
-     definition left this undefined.  */
-  if (domainname == NULL)
-    domainname = _nl_current_default_domain;
-
-  /* First find matching binding.  */
-  for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next)
-    {
-      int compare = strcmp (domainname, binding->domainname);
-      if (compare == 0)
-	/* We found it!  */
-	break;
-      if (compare < 0)
-	{
-	  /* It is not in the list.  */
-	  binding = NULL;
-	  break;
-	}
-    }
-
-  if (binding == NULL)
-    dirname = (char *) _nl_default_dirname;
-  else if (binding->dirname[0] == '/')
-    dirname = binding->dirname;
-  else
-    {
-      /* We have a relative path.  Make it absolute now.  */
-      size_t dirname_len = strlen (binding->dirname) + 1;
-      size_t path_max;
-      char *ret;
-
-      path_max = (unsigned int) PATH_MAX;
-      path_max += 2;		/* The getcwd docs say to do this.  */
-
-      dirname = (char *) alloca (path_max + dirname_len);
-      ADD_BLOCK (block_list, dirname);
-
-      __set_errno (0);
-      while ((ret = getcwd (dirname, path_max)) == NULL && errno == ERANGE)
-	{
-	  path_max += PATH_INCR;
-	  dirname = (char *) alloca (path_max + dirname_len);
-	  ADD_BLOCK (block_list, dirname);
-	  __set_errno (0);
-	}
-
-      if (ret == NULL)
-	{
-	  /* We cannot get the current working directory.  Don't signal an
-	     error but simply return the default string.  */
-	  FREE_BLOCKS (block_list);
-	  __set_errno (saved_errno);
-	  return (char *) msgid;
-	}
-
-      stpcpy (stpcpy (strchr (dirname, '\0'), "/"), binding->dirname);
-    }
-
-  /* Now determine the symbolic name of CATEGORY and its value.  */
-  categoryname = category_to_name (category);
-  categoryvalue = guess_category_value (category, categoryname);
-
-  domainname_len = strlen (domainname);
-  xdomainname = (char *) alloca (strlen (categoryname)
-				 + domainname_len + 5);
-  ADD_BLOCK (block_list, xdomainname);
-
-  stpcpy (mempcpy (stpcpy (stpcpy (xdomainname, categoryname), "/"),
-		  domainname, domainname_len),
-	  ".mo");
-
-  /* Creating working area.  */
-  single_locale = (char *) alloca (strlen (categoryvalue) + 1);
-  ADD_BLOCK (block_list, single_locale);
-
-
-  /* Search for the given string.  This is a loop because we perhaps
-     got an ordered list of languages to consider for the translation.  */
-  while (1)
-    {
-      /* Make CATEGORYVALUE point to the next element of the list.  */
-      while (categoryvalue[0] != '\0' && categoryvalue[0] == ':')
-	++categoryvalue;
-      if (categoryvalue[0] == '\0')
-	{
-	  /* The whole contents of CATEGORYVALUE has been searched but
-	     no valid entry has been found.  We solve this situation
-	     by implicitly appending a "C" entry, i.e. no translation
-	     will take place.  */
-	  single_locale[0] = 'C';
-	  single_locale[1] = '\0';
-	}
-      else
-	{
-	  char *cp = single_locale;
-	  while (categoryvalue[0] != '\0' && categoryvalue[0] != ':')
-	    *cp++ = *categoryvalue++;
-	  *cp = '\0';
-
-	  /* When this is a SUID binary we must not allow accessing files
-	     outside the dedicated directories.  */
-	  if (ENABLE_SECURE
-	      && (memchr (single_locale, '/',
-			  _nl_find_language (single_locale) - single_locale)
-		  != NULL))
-	    /* Ingore this entry.  */
-	    continue;
-	}
-
-      /* If the current locale value is C (or POSIX) we don't load a
-	 domain.  Return the MSGID.  */
-      if (strcmp (single_locale, "C") == 0
-	  || strcmp (single_locale, "POSIX") == 0)
-	{
-	  FREE_BLOCKS (block_list);
-	  __set_errno (saved_errno);
-	  return (char *) msgid;
-	}
-
-
-      /* Find structure describing the message catalog matching the
-	 DOMAINNAME and CATEGORY.  */
-      domain = _nl_find_domain (dirname, single_locale, xdomainname);
-
-      if (domain != NULL)
-	{
-	  retval = _nl_find_msg (domain, msgid);
-
-	  if (retval == NULL)
-	    {
-	      int cnt;
-
-	      for (cnt = 0; domain->successor[cnt] != NULL; ++cnt)
-		{
-		  retval = _nl_find_msg (domain->successor[cnt], msgid);
-
-		  if (retval != NULL)
-		    break;
-		}
-	    }
-
-	  if (retval != NULL)
-	    {
-	      FREE_BLOCKS (block_list);
-	      __set_errno (saved_errno);
-#if defined HAVE_TSEARCH || defined _LIBC
-	      if (foundp == NULL)
-		{
-		  /* Create a new entry and add it to the search tree.  */
-		  struct known_translation_t *newp;
-
-		  newp = (struct known_translation_t *)
-		    malloc (sizeof (*newp) + msgid_len
-			    + domainname_len + 1 - ZERO);
-		  if (newp != NULL)
-		    {
-		      newp->domain = mempcpy (newp->msgid, msgid, msgid_len);
-		      memcpy (newp->domain, domainname, domainname_len + 1);
-		      newp->category = category;
-		      newp->counter = _nl_msg_cat_cntr;
-		      newp->translation = retval;
-
-		      /* Insert the entry in the search tree.  */
-		      foundp = (struct known_translation_t **)
-			tsearch (newp, &root, transcmp);
-		      if (&newp != foundp)
-			/* The insert failed.  */
-			free (newp);
-		    }
-		}
-	      else
-		{
-		  /* We can update the existing entry.  */
-		  (*foundp)->counter = _nl_msg_cat_cntr;
-		  (*foundp)->translation = retval;
-		}
-#endif
-	      return retval;
-	    }
-	}
-    }
-  /* NOTREACHED */
+  return DCIGETTEXT (domainname, msgid, NULL, 0, 0, category);
 }
 
 #ifdef _LIBC
 /* Alias for function name in GNU C Library.  */
 weak_alias (__dcgettext, dcgettext);
 #endif
-
-
-char *
-internal_function
-_nl_find_msg (domain_file, msgid)
-     struct loaded_l10nfile *domain_file;
-     const char *msgid;
-{
-  size_t act = 0;
-  size_t top, bottom;
-  struct loaded_domain *domain;
-
-  if (domain_file->decided == 0)
-    _nl_load_domain (domain_file);
-
-  if (domain_file->data == NULL)
-    return NULL;
-
-  domain = (struct loaded_domain *) domain_file->data;
-
-  /* Locate the MSGID and its translation.  */
-  if (domain->hash_size > 2 && domain->hash_tab != NULL)
-    {
-      /* Use the hashing table.  */
-      nls_uint32 len = strlen (msgid);
-      nls_uint32 hash_val = hash_string (msgid);
-      nls_uint32 idx = hash_val % domain->hash_size;
-      nls_uint32 incr = 1 + (hash_val % (domain->hash_size - 2));
-      nls_uint32 nstr = W (domain->must_swap, domain->hash_tab[idx]);
-
-      if (nstr == 0)
-	/* Hash table entry is empty.  */
-	return NULL;
-
-      if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len
-	  && strcmp (msgid,
-		     domain->data + W (domain->must_swap,
-				       domain->orig_tab[nstr - 1].offset)) == 0)
-	{
-	  /* We found an entry.  If we have to convert the string to use
-	     a different character set this is the time.  */
-	  char *result =
-	    (char *) domain->data + W (domain->must_swap,
-				       domain->trans_tab[nstr - 1].offset);
-
-	  if (
-#ifdef _LIBC
-	      domain->conv != (__gconv_t) -1
-#else
-# if HAVE_ICONV
-	      domain->conv != (iconv_t) -1
-# endif
-#endif
-	      )
-	    {
-	      /* We are supposed to do a conversion.  First allocate an
-		 appropriate table with the same structure as the hash
-		 table in the file where we can put the pointers to the
-		 converted strings in.  */
-	      if (domain->conv_tab == NULL
-		  && ((domain->conv_tab = (char **) calloc (domain->hash_size,
-							    sizeof (char *)))
-		      == NULL))
-		/* Mark that we didn't succeed allocating a table.  */
-		domain->conv_tab = (char **) -1;
-
-	      if (domain->conv_tab == (char **) -1)
-		/* Nothing we can do, no more memory.  */
-		return NULL;
-
-	      if (domain->conv_tab[idx] == NULL)
-		{
-		  /* We haven't used this string so far, so it is not
-		     translated yet.  Do this now.  */
-#ifdef _LIBC
-		  /* For glibc we use a bit more efficient memory handling.
-		     We allocate always larger blocks which get used over
-		     time.  This is faster than many small allocations.   */
-		  __libc_lock_define_initialized (static, lock)
-		  static unsigned char *freemem;
-		  static size_t freemem_size;
-		  /* Note that we include the NUL byte.  */
-		  size_t resultlen = strlen (result) + 1;
-		  const unsigned char *inbuf = result;
-		  unsigned char *outbuf = freemem;
-		  size_t written;
-		  int res;
-
-		  __libc_lock_lock (lock);
-
-		  while ((res = __gconv (domain->conv,
-					 &inbuf, inbuf + resultlen,
-					 &outbuf, outbuf + freemem_size,
-					 &written)) == __GCONV_OK)
-		    {
-		      if (res != __GCONV_FULL_OUTPUT)
-			goto out;
-
-		      /* We must resize the buffer.  */
-		      freemem_size = MAX (2 * freemem_size, 4064);
-		      freemem = (char *) malloc (freemem_size);
-		      if (freemem == NULL)
-			goto out;
-
-		      inbuf = result;
-		      outbuf = freemem;
-		    }
-
-		  /* We have now in our buffer a converted string.  Put this
-		     in the hash table  */
-		  domain->conv_tab[idx] = freemem;
-		  freemem_size -= outbuf - freemem;
-		  freemem = outbuf;
-
-		out:
-		  __libc_lock_unlock (lock);
-#endif
-		}
-
-	      result = domain->conv_tab[idx];
-	    }
-
-	  return result;
-	}
-
-      while (1)
-	{
-	  if (idx >= domain->hash_size - incr)
-	    idx -= domain->hash_size - incr;
-	  else
-	    idx += incr;
-
-	  nstr = W (domain->must_swap, domain->hash_tab[idx]);
-	  if (nstr == 0)
-	    /* Hash table entry is empty.  */
-	    return NULL;
-
-	  if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len
-	      && (strcmp (msgid,
-			  domain->data + W (domain->must_swap,
-					    domain->orig_tab[nstr - 1].offset))
-		  == 0))
-	    return ((char *) domain->data
-		    + W (domain->must_swap,
-			 domain->trans_tab[nstr - 1].offset));
-	}
-      /* NOTREACHED */
-    }
-
-  /* Now we try the default method:  binary search in the sorted
-     array of messages.  */
-  bottom = 0;
-  top = domain->nstrings;
-  while (bottom < top)
-    {
-      int cmp_val;
-
-      act = (bottom + top) / 2;
-      cmp_val = strcmp (msgid, (domain->data
-				+ W (domain->must_swap,
-				     domain->orig_tab[act].offset)));
-      if (cmp_val < 0)
-	top = act;
-      else if (cmp_val > 0)
-	bottom = act + 1;
-      else
-	break;
-    }
-
-  /* If an translation is found return this.  */
-  return bottom >= top ? NULL : ((char *) domain->data
-				 + W (domain->must_swap,
-				      domain->trans_tab[act].offset));
-}
-
-
-/* Return string representation of locale CATEGORY.  */
-static const char *
-internal_function
-category_to_name (category)
-     int category;
-{
-  const char *retval;
-
-  switch (category)
-  {
-#ifdef LC_COLLATE
-  case LC_COLLATE:
-    retval = "LC_COLLATE";
-    break;
-#endif
-#ifdef LC_CTYPE
-  case LC_CTYPE:
-    retval = "LC_CTYPE";
-    break;
-#endif
-#ifdef LC_MONETARY
-  case LC_MONETARY:
-    retval = "LC_MONETARY";
-    break;
-#endif
-#ifdef LC_NUMERIC
-  case LC_NUMERIC:
-    retval = "LC_NUMERIC";
-    break;
-#endif
-#ifdef LC_TIME
-  case LC_TIME:
-    retval = "LC_TIME";
-    break;
-#endif
-#ifdef LC_MESSAGES
-  case LC_MESSAGES:
-    retval = "LC_MESSAGES";
-    break;
-#endif
-#ifdef LC_RESPONSE
-  case LC_RESPONSE:
-    retval = "LC_RESPONSE";
-    break;
-#endif
-#ifdef LC_ALL
-  case LC_ALL:
-    /* This might not make sense but is perhaps better than any other
-       value.  */
-    retval = "LC_ALL";
-    break;
-#endif
-  default:
-    /* If you have a better idea for a default value let me know.  */
-    retval = "LC_XXX";
-  }
-
-  return retval;
-}
-
-/* Guess value of current locale from value of the environment variables.  */
-static const char *
-internal_function
-guess_category_value (category, categoryname)
-     int category;
-     const char *categoryname;
-{
-  const char *retval;
-
-  /* The highest priority value is the `LANGUAGE' environment
-     variable.  This is a GNU extension.  */
-  retval = getenv ("LANGUAGE");
-  if (retval != NULL && retval[0] != '\0')
-    return retval;
-
-  /* `LANGUAGE' is not set.  So we have to proceed with the POSIX
-     methods of looking to `LC_ALL', `LC_xxx', and `LANG'.  On some
-     systems this can be done by the `setlocale' function itself.  */
-#if defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES && defined HAVE_LOCALE_NULL
-  return setlocale (category, NULL);
-#else
-  /* Setting of LC_ALL overwrites all other.  */
-  retval = getenv ("LC_ALL");
-  if (retval != NULL && retval[0] != '\0')
-    return retval;
-
-  /* Next comes the name of the desired category.  */
-  retval = getenv (categoryname);
-  if (retval != NULL && retval[0] != '\0')
-    return retval;
-
-  /* Last possibility is the LANG environment variable.  */
-  retval = getenv ("LANG");
-  if (retval != NULL && retval[0] != '\0')
-    return retval;
-
-  /* We use C as the default domain.  POSIX says this is implementation
-     defined.  */
-  return "C";
-#endif
-}
-
-/* @@ begin of epilog @@ */
-
-/* We don't want libintl.a to depend on any other library.  So we
-   avoid the non-standard function stpcpy.  In GNU C Library this
-   function is available, though.  Also allow the symbol HAVE_STPCPY
-   to be defined.  */
-#if !_LIBC && !HAVE_STPCPY
-static char *
-stpcpy (dest, src)
-     char *dest;
-     const char *src;
-{
-  while ((*dest++ = *src++) != '\0')
-    /* Do nothing. */ ;
-  return dest - 1;
-}
-#endif
-
-#if !_LIBC && !HAVE_MEMPCPY
-static void *
-mempcpy (dest, src, n)
-     void *dest;
-     const void *src;
-     size_t n;
-{
-  return (void *) ((char *) memcpy (dst, src, n) + n);
-}
-#endif
-
-
-#ifdef _LIBC
-/* If we want to free all resources we have to do some work at
-   program's end.  */
-static void __attribute__ ((unused))
-free_mem (void)
-{
-  struct binding *runp;
-
-  for (runp = _nl_domain_bindings; runp != NULL; runp = runp->next)
-    {
-      free (runp->domainname);
-      if (runp->dirname != _nl_default_dirname)
-	/* Yes, this is a pointer comparison.  */
-	free (runp->dirname);
-    }
-
-  if (_nl_current_default_domain != _nl_default_default_domain)
-    /* Yes, again a pointer comparison.  */
-    free ((char *) _nl_current_default_domain);
-
-  /* Remove the search tree with the know translations.  */
-  __tdestroy (root, free);
-}
-
-text_set_element (__libc_subfreeres, free_mem);
-#endif
diff --git a/intl/dcigettext.c b/intl/dcigettext.c
new file mode 100644
index 0000000000..e11fd06147
--- /dev/null
+++ b/intl/dcigettext.c
@@ -0,0 +1,1000 @@
+/* Implementation of the internal dcigettext function.
+   Copyright (C) 1995-1999, 2000 Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#if defined __GNUC__ && !defined C_ALLOCA
+# define alloca __builtin_alloca
+# define HAVE_ALLOCA 1
+#else
+# if (defined HAVE_ALLOCA_H || defined _LIBC) && !defined C_ALLOCA
+#  include <alloca.h>
+# else
+#  ifdef _AIX
+ #pragma alloca
+#  else
+#   ifndef alloca
+char *alloca ();
+#   endif
+#  endif
+# endif
+#endif
+
+#include <errno.h>
+#ifndef errno
+extern int errno;
+#endif
+#ifndef __set_errno
+# define __set_errno(val) errno = (val)
+#endif
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#else
+char *getenv ();
+# ifdef HAVE_MALLOC_H
+#  include <malloc.h>
+# else
+void free ();
+# endif
+#endif
+
+#if defined HAVE_STRING_H || defined _LIBC
+# ifndef _GNU_SOURCE
+#  define _GNU_SOURCE	1
+# endif
+# include <string.h>
+#else
+# include <strings.h>
+#endif
+#if !HAVE_STRCHR && !defined _LIBC
+# ifndef strchr
+#  define strchr index
+# endif
+#endif
+
+#if defined HAVE_UNISTD_H || defined _LIBC
+# include <unistd.h>
+#endif
+
+#if defined HAVE_LOCALE_H || defined _LIBC
+# include <locale.h>
+#endif
+
+#if defined HAVE_SYS_PARAM_H || defined _LIBC
+# include <sys/param.h>
+#endif
+
+#include "gettext.h"
+#include "gettextP.h"
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+#include "hash-string.h"
+
+/* Thread safetyness.  */
+#ifdef _LIBC
+# include <bits/libc-lock.h>
+#endif
+
+/* @@ end of prolog @@ */
+
+#ifdef _LIBC
+/* Rename the non ANSI C functions.  This is required by the standard
+   because some ANSI C functions will require linking with this object
+   file and the name space must not be polluted.  */
+# define getcwd __getcwd
+# ifndef stpcpy
+#  define stpcpy __stpcpy
+# endif
+#else
+# if !defined HAVE_GETCWD
+char *getwd ();
+#  define getcwd(buf, max) getwd (buf)
+# else
+char *getcwd ();
+# endif
+# ifndef HAVE_STPCPY
+static char *stpcpy PARAMS ((char *dest, const char *src));
+# endif
+# ifndef HAVE_MEMPCPY
+static void *mempcpy PARAMS ((void *dest, const void *src, size_t n));
+# endif
+#endif
+
+/* Amount to increase buffer size by in each try.  */
+#define PATH_INCR 32
+
+/* The following is from pathmax.h.  */
+/* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define
+   PATH_MAX but might cause redefinition warnings when sys/param.h is
+   later included (as on MORE/BSD 4.3).  */
+#if defined _POSIX_VERSION || (defined HAVE_LIMITS_H && !defined __GNUC__)
+# include <limits.h>
+#endif
+
+#ifndef _POSIX_PATH_MAX
+# define _POSIX_PATH_MAX 255
+#endif
+
+#if !defined PATH_MAX && defined _PC_PATH_MAX
+# define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf ("/", _PC_PATH_MAX))
+#endif
+
+/* Don't include sys/param.h if it already has been.  */
+#if defined HAVE_SYS_PARAM_H && !defined PATH_MAX && !defined MAXPATHLEN
+# include <sys/param.h>
+#endif
+
+#if !defined PATH_MAX && defined MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+#endif
+
+#ifndef PATH_MAX
+# define PATH_MAX _POSIX_PATH_MAX
+#endif
+
+/* XPG3 defines the result of `setlocale (category, NULL)' as:
+   ``Directs `setlocale()' to query `category' and return the current
+     setting of `local'.''
+   However it does not specify the exact format.  And even worse: POSIX
+   defines this not at all.  So we can use this feature only on selected
+   system (e.g. those using GNU C Library).  */
+#ifdef _LIBC
+# define HAVE_LOCALE_NULL
+#endif
+
+/* We want to allocate a string at the end of the struct.  gcc makes
+   this easy.  */
+#ifdef __GNUC__
+# define ZERO 0
+#else
+# define ZERO 1
+#endif
+
+/* This is the type used for the search tree where known translations
+   are stored.  */
+struct known_translation_t
+{
+  /* Domain in which to search.  */
+  char *domain;
+
+  /* Plural index.  */
+  unsigned long int plindex;
+
+  /* The category.  */
+  int category;
+
+  /* State of the catalog counter at the point the string was found.  */
+  int counter;
+
+  /* And finally the translation.  */
+  const char *translation;
+
+  /* Pointer to the string in question.  */
+  char msgid[ZERO];
+};
+
+/* Root of the search tree with known translations.  We can use this
+   only if the system provides the `tsearch' function family.  */
+#if defined HAVE_TSEARCH || defined _LIBC
+# include <search.h>
+
+static void *root;
+
+# ifdef _LIBC
+#  define tsearch __tsearch
+# endif
+
+/* Function to compare two entries in the table of known translations.  */
+static int
+transcmp (const void *p1, const void *p2)
+{
+  struct known_translation_t *s1 = (struct known_translation_t *) p1;
+  struct known_translation_t *s2 = (struct known_translation_t *) p2;
+  int result;
+
+  result = strcmp (s1->msgid, s2->msgid);
+  if (result == 0)
+    {
+      result = strcmp (s1->msgid, s2->msgid);
+      if (result == 0)
+	{
+	  result = s1->plindex - s2->plindex;
+	  if (result == 0)
+	    /* We compare the category last (though this is the cheapest
+	       operation) since it is hopefully always the same (namely
+	       LC_MESSAGES).  */
+	    result = s1->category - s2->category;
+	}
+    }
+
+  return result;
+}
+#endif
+
+/* Name of the default domain used for gettext(3) prior any call to
+   textdomain(3).  The default value for this is "messages".  */
+const char _nl_default_default_domain[] = "messages";
+
+/* Value used as the default domain for gettext(3).  */
+const char *_nl_current_default_domain = _nl_default_default_domain;
+
+/* Contains the default location of the message catalogs.  */
+const char _nl_default_dirname[] = GNULOCALEDIR;
+
+/* List with bindings of specific domains created by bindtextdomain()
+   calls.  */
+struct binding *_nl_domain_bindings;
+
+/* Prototypes for local functions.  */
+static unsigned long int plural_eval (struct expression *pexp,
+				      unsigned long int n) internal_function;
+static const char *category_to_name PARAMS ((int category)) internal_function;
+static const char *guess_category_value PARAMS ((int category,
+						 const char *categoryname))
+     internal_function;
+
+
+/* For those loosing systems which don't have `alloca' we have to add
+   some additional code emulating it.  */
+#ifdef HAVE_ALLOCA
+/* Nothing has to be done.  */
+# define ADD_BLOCK(list, address) /* nothing */
+# define FREE_BLOCKS(list) /* nothing */
+#else
+struct block_list
+{
+  void *address;
+  struct block_list *next;
+};
+# define ADD_BLOCK(list, addr)						      \
+  do {									      \
+    struct block_list *newp = (struct block_list *) malloc (sizeof (*newp));  \
+    /* If we cannot get a free block we cannot add the new element to	      \
+       the list.  */							      \
+    if (newp != NULL) {							      \
+      newp->address = (addr);						      \
+      newp->next = (list);						      \
+      (list) = newp;							      \
+    }									      \
+  } while (0)
+# define FREE_BLOCKS(list)						      \
+  do {									      \
+    while (list != NULL) {						      \
+      struct block_list *old = list;					      \
+      list = list->next;						      \
+      free (old);							      \
+    }									      \
+  } while (0)
+# undef alloca
+# define alloca(size) (malloc (size))
+#endif	/* have alloca */
+
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define DCIGETTEXT __dcigettext
+#else
+# define DCIGETTEXT dcigettext__
+#endif
+
+/* Checking whether the binaries runs SUID must be done and glibc provides
+   easier methods therefore we make a difference here.  */
+#ifdef _LIBC
+# define ENABLE_SECURE __libc_enable_secure
+# define DETERMINE_SECURE
+#else
+static int enable_secure;
+# define ENABLE_SECURE (enable_secure == 1)
+# define DETERMINE_SECURE \
+  if (enable_secure == 0)						      \
+    {									      \
+      if (getuid () != geteuid () || getgid () != getegid ())		      \
+	enable_secure = 1;						      \
+      else								      \
+	enable_secure = -1;						      \
+    }
+#endif
+
+/* Look up MSGID in the DOMAINNAME message catalog for the current
+   CATEGORY locale and, if PLURAL is nonzero, search over string
+   depending on the plural form determined by N.  */
+char *
+DCIGETTEXT (domainname, msgid1, msgid2, plural, n, category)
+     const char *domainname;
+     const char *msgid1;
+     const char *msgid2;
+     int plural;
+     unsigned long int n;
+     int category;
+{
+#ifndef HAVE_ALLOCA
+  struct block_list *block_list = NULL;
+#endif
+  struct loaded_l10nfile *domain;
+  struct binding *binding;
+  const char *categoryname;
+  const char *categoryvalue;
+  char *dirname, *xdomainname;
+  char *single_locale;
+  char *retval;
+  int saved_errno;
+#if defined HAVE_TSEARCH || defined _LIBC
+  struct known_translation_t *search;
+  struct known_translation_t **foundp = NULL;
+  size_t msgid_len = strlen (msgid1) + 1;
+#endif
+  size_t domainname_len;
+
+  /* If no real MSGID is given return NULL.  */
+  if (msgid1 == NULL)
+    return NULL;
+
+#if defined HAVE_TSEARCH || defined _LIBC
+  if (plural == 0)
+    {
+      /* Try to find the translation among those which we found at
+	 some time.  */
+      search = (struct known_translation_t *) alloca (sizeof (*search)
+						      + msgid_len);
+      memcpy (search->msgid, msgid1, msgid_len);
+      search->domain = (char *) domainname;
+      search->plindex = 0;
+      search->category = category;
+
+      foundp = (struct known_translation_t **) tfind (search, &root, transcmp);
+      if (foundp != NULL && (*foundp)->counter == _nl_msg_cat_cntr)
+	return (char *) (*foundp)->translation;
+    }
+#endif
+
+  /* Preserve the `errno' value.  */
+  saved_errno = errno;
+
+  /* See whether this is a SUID binary or not.  */
+  DETERMINE_SECURE;
+
+  /* If DOMAINNAME is NULL, we are interested in the default domain.  If
+     CATEGORY is not LC_MESSAGES this might not make much sense but the
+     definition left this undefined.  */
+  if (domainname == NULL)
+    domainname = _nl_current_default_domain;
+
+  /* First find matching binding.  */
+  for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next)
+    {
+      int compare = strcmp (domainname, binding->domainname);
+      if (compare == 0)
+	/* We found it!  */
+	break;
+      if (compare < 0)
+	{
+	  /* It is not in the list.  */
+	  binding = NULL;
+	  break;
+	}
+    }
+
+  if (binding == NULL)
+    dirname = (char *) _nl_default_dirname;
+  else if (binding->dirname[0] == '/')
+    dirname = binding->dirname;
+  else
+    {
+      /* We have a relative path.  Make it absolute now.  */
+      size_t dirname_len = strlen (binding->dirname) + 1;
+      size_t path_max;
+      char *ret;
+
+      path_max = (unsigned int) PATH_MAX;
+      path_max += 2;		/* The getcwd docs say to do this.  */
+
+      dirname = (char *) alloca (path_max + dirname_len);
+      ADD_BLOCK (block_list, dirname);
+
+      __set_errno (0);
+      while ((ret = getcwd (dirname, path_max)) == NULL && errno == ERANGE)
+	{
+	  path_max += PATH_INCR;
+	  dirname = (char *) alloca (path_max + dirname_len);
+	  ADD_BLOCK (block_list, dirname);
+	  __set_errno (0);
+	}
+
+      if (ret == NULL)
+	{
+	  /* We cannot get the current working directory.  Don't signal an
+	     error but simply return the default string.  */
+	  FREE_BLOCKS (block_list);
+	  __set_errno (saved_errno);
+	  return (plural == 0
+		  ? (char *) msgid1
+		  /* Use the Germanic plural rule.  */
+		  : n == 1 ? (char *) msgid1 : (char *) msgid2);
+	}
+
+      stpcpy (stpcpy (strchr (dirname, '\0'), "/"), binding->dirname);
+    }
+
+  /* Now determine the symbolic name of CATEGORY and its value.  */
+  categoryname = category_to_name (category);
+  categoryvalue = guess_category_value (category, categoryname);
+
+  domainname_len = strlen (domainname);
+  xdomainname = (char *) alloca (strlen (categoryname)
+				 + domainname_len + 5);
+  ADD_BLOCK (block_list, xdomainname);
+
+  stpcpy (mempcpy (stpcpy (stpcpy (xdomainname, categoryname), "/"),
+		  domainname, domainname_len),
+	  ".mo");
+
+  /* Creating working area.  */
+  single_locale = (char *) alloca (strlen (categoryvalue) + 1);
+  ADD_BLOCK (block_list, single_locale);
+
+
+  /* Search for the given string.  This is a loop because we perhaps
+     got an ordered list of languages to consider for the translation.  */
+  while (1)
+    {
+      /* Make CATEGORYVALUE point to the next element of the list.  */
+      while (categoryvalue[0] != '\0' && categoryvalue[0] == ':')
+	++categoryvalue;
+      if (categoryvalue[0] == '\0')
+	{
+	  /* The whole contents of CATEGORYVALUE has been searched but
+	     no valid entry has been found.  We solve this situation
+	     by implicitly appending a "C" entry, i.e. no translation
+	     will take place.  */
+	  single_locale[0] = 'C';
+	  single_locale[1] = '\0';
+	}
+      else
+	{
+	  char *cp = single_locale;
+	  while (categoryvalue[0] != '\0' && categoryvalue[0] != ':')
+	    *cp++ = *categoryvalue++;
+	  *cp = '\0';
+
+	  /* When this is a SUID binary we must not allow accessing files
+	     outside the dedicated directories.  */
+	  if (ENABLE_SECURE
+	      && (memchr (single_locale, '/',
+			  _nl_find_language (single_locale) - single_locale)
+		  != NULL))
+	    /* Ingore this entry.  */
+	    continue;
+	}
+
+      /* If the current locale value is C (or POSIX) we don't load a
+	 domain.  Return the MSGID.  */
+      if (strcmp (single_locale, "C") == 0
+	  || strcmp (single_locale, "POSIX") == 0)
+	{
+	  FREE_BLOCKS (block_list);
+	  __set_errno (saved_errno);
+	  return (plural == 0
+		  ? (char *) msgid1
+		  /* Use the Germanic plural rule.  */
+		  : n == 1 ? (char *) msgid1 : (char *) msgid2);
+	}
+
+
+      /* Find structure describing the message catalog matching the
+	 DOMAINNAME and CATEGORY.  */
+      domain = _nl_find_domain (dirname, single_locale, xdomainname);
+
+      if (domain != NULL)
+	{
+#if defined HAVE_TSEARCH || defined _LIBC
+	  struct loaded_domain *domaindata =
+	    (struct loaded_domain *) domain->data;
+	  unsigned long int index = 0;
+
+	  if (plural != 0)
+	    {
+	      /* Try to find the translation among those which we
+		 found at some time.  */
+	      search = (struct known_translation_t *) alloca (sizeof (*search)
+							      + msgid_len);
+	      memcpy (search->msgid, msgid1, msgid_len);
+	      search->domain = (char *) domainname;
+	      search->plindex = plural_eval (domaindata->plural, n);
+	      if (search->plindex >= domaindata->nplurals)
+		/* This should never happen.  It means the plural expression
+		   and the given maximum value do not match.  */
+		search->plindex = 0;
+	      index = search->plindex;
+	      search->category = category;
+
+	      foundp = (struct known_translation_t **) tfind (search, &root,
+							      transcmp);
+	      if (foundp != NULL && (*foundp)->counter == _nl_msg_cat_cntr)
+		return (char *) (*foundp)->translation;
+	    }
+#endif
+
+	  retval = _nl_find_msg (domain, msgid1, index);
+
+	  if (retval == NULL)
+	    {
+	      int cnt;
+
+	      for (cnt = 0; domain->successor[cnt] != NULL; ++cnt)
+		{
+		  retval = _nl_find_msg (domain->successor[cnt], msgid1,
+					 index);
+
+		  if (retval != NULL)
+		    break;
+		}
+	    }
+
+	  if (retval != NULL)
+	    {
+	      FREE_BLOCKS (block_list);
+	      __set_errno (saved_errno);
+#if defined HAVE_TSEARCH || defined _LIBC
+	      if (foundp == NULL)
+		{
+		  /* Create a new entry and add it to the search tree.  */
+		  struct known_translation_t *newp;
+
+		  newp = (struct known_translation_t *)
+		    malloc (sizeof (*newp) + msgid_len
+			    + domainname_len + 1 - ZERO);
+		  if (newp != NULL)
+		    {
+		      newp->domain = mempcpy (newp->msgid, msgid1, msgid_len);
+		      memcpy (newp->domain, domainname, domainname_len + 1);
+		      newp->plindex = index;
+		      newp->category = category;
+		      newp->counter = _nl_msg_cat_cntr;
+		      newp->translation = retval;
+
+		      /* Insert the entry in the search tree.  */
+		      foundp = (struct known_translation_t **)
+			tsearch (newp, &root, transcmp);
+		      if (&newp != foundp)
+			/* The insert failed.  */
+			free (newp);
+		    }
+		}
+	      else
+		{
+		  /* We can update the existing entry.  */
+		  (*foundp)->counter = _nl_msg_cat_cntr;
+		  (*foundp)->translation = retval;
+		}
+#endif
+	      return retval;
+	    }
+	}
+    }
+  /* NOTREACHED */
+}
+
+
+char *
+internal_function
+_nl_find_msg (domain_file, msgid, index)
+     struct loaded_l10nfile *domain_file;
+     const char *msgid;
+     unsigned long int index;
+{
+  size_t act = 0;
+  size_t top, bottom;
+  struct loaded_domain *domain;
+
+  if (domain_file->decided == 0)
+    _nl_load_domain (domain_file);
+
+  if (domain_file->data == NULL)
+    return NULL;
+
+  domain = (struct loaded_domain *) domain_file->data;
+
+  /* Locate the MSGID and its translation.  */
+  if (domain->hash_size > 2 && domain->hash_tab != NULL)
+    {
+      /* Use the hashing table.  */
+      nls_uint32 len = strlen (msgid);
+      nls_uint32 hash_val = hash_string (msgid);
+      nls_uint32 idx = hash_val % domain->hash_size;
+      nls_uint32 incr = 1 + (hash_val % (domain->hash_size - 2));
+      nls_uint32 nstr = W (domain->must_swap, domain->hash_tab[idx]);
+
+      if (nstr == 0)
+	/* Hash table entry is empty.  */
+	return NULL;
+
+      if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len
+	  && strcmp (msgid,
+		     domain->data + W (domain->must_swap,
+				       domain->orig_tab[nstr - 1].offset)) == 0)
+	{
+	  /* We found an entry.  If we have to convert the string to use
+	     a different character set this is the time.  */
+	  char *result =
+	    (char *) domain->data + W (domain->must_swap,
+				       domain->trans_tab[nstr - 1].offset);
+
+	  /* Now skip some strings.  How much depends on the index passed
+	     in.  */
+	  while (index-- > 0)
+	    {
+#ifdef _LIBC
+	      result = __rawmemchr (result, '\0');
+#else
+	      result = strchr (result, '\0');
+#endif
+	      /* And skip over the NUL byte.  */
+	      ++result;
+	    }
+
+	  if (
+#ifdef _LIBC
+	      domain->conv != (__gconv_t) -1
+#else
+# if HAVE_ICONV
+	      domain->conv != (iconv_t) -1
+# endif
+#endif
+	      )
+	    {
+	      /* We are supposed to do a conversion.  First allocate an
+		 appropriate table with the same structure as the hash
+		 table in the file where we can put the pointers to the
+		 converted strings in.  */
+	      if (domain->conv_tab == NULL
+		  && ((domain->conv_tab = (char **) calloc (domain->hash_size,
+							    sizeof (char *)))
+		      == NULL))
+		/* Mark that we didn't succeed allocating a table.  */
+		domain->conv_tab = (char **) -1;
+
+	      if (domain->conv_tab == (char **) -1)
+		/* Nothing we can do, no more memory.  */
+		return NULL;
+
+	      if (domain->conv_tab[idx] == NULL)
+		{
+		  /* We haven't used this string so far, so it is not
+		     translated yet.  Do this now.  */
+#ifdef _LIBC
+		  /* For glibc we use a bit more efficient memory handling.
+		     We allocate always larger blocks which get used over
+		     time.  This is faster than many small allocations.   */
+		  __libc_lock_define_initialized (static, lock)
+		  static unsigned char *freemem;
+		  static size_t freemem_size;
+		  /* Note that we include the NUL byte.  */
+		  size_t resultlen = strlen (result) + 1;
+		  const unsigned char *inbuf = result;
+		  unsigned char *outbuf = freemem;
+		  size_t written;
+		  int res;
+
+		  __libc_lock_lock (lock);
+
+		  while ((res = __gconv (domain->conv,
+					 &inbuf, inbuf + resultlen,
+					 &outbuf, outbuf + freemem_size,
+					 &written)) == __GCONV_OK)
+		    {
+		      if (res != __GCONV_FULL_OUTPUT)
+			goto out;
+
+		      /* We must resize the buffer.  */
+		      freemem_size = MAX (2 * freemem_size, 4064);
+		      freemem = (char *) malloc (freemem_size);
+		      if (freemem == NULL)
+			goto out;
+
+		      inbuf = result;
+		      outbuf = freemem;
+		    }
+
+		  /* We have now in our buffer a converted string.  Put this
+		     in the hash table  */
+		  domain->conv_tab[idx] = freemem;
+		  freemem_size -= outbuf - freemem;
+		  freemem = outbuf;
+
+		out:
+		  __libc_lock_unlock (lock);
+#endif
+		}
+
+	      result = domain->conv_tab[idx];
+	    }
+
+	  return result;
+	}
+
+      while (1)
+	{
+	  if (idx >= domain->hash_size - incr)
+	    idx -= domain->hash_size - incr;
+	  else
+	    idx += incr;
+
+	  nstr = W (domain->must_swap, domain->hash_tab[idx]);
+	  if (nstr == 0)
+	    /* Hash table entry is empty.  */
+	    return NULL;
+
+	  if (W (domain->must_swap, domain->orig_tab[nstr - 1].length) == len
+	      && (strcmp (msgid,
+			  domain->data + W (domain->must_swap,
+					    domain->orig_tab[nstr - 1].offset))
+		  == 0))
+	    return ((char *) domain->data
+		    + W (domain->must_swap,
+			 domain->trans_tab[nstr - 1].offset));
+	}
+      /* NOTREACHED */
+    }
+
+  /* Now we try the default method:  binary search in the sorted
+     array of messages.  */
+  bottom = 0;
+  top = domain->nstrings;
+  while (bottom < top)
+    {
+      int cmp_val;
+
+      act = (bottom + top) / 2;
+      cmp_val = strcmp (msgid, (domain->data
+				+ W (domain->must_swap,
+				     domain->orig_tab[act].offset)));
+      if (cmp_val < 0)
+	top = act;
+      else if (cmp_val > 0)
+	bottom = act + 1;
+      else
+	break;
+    }
+
+  /* If an translation is found return this.  */
+  return bottom >= top ? NULL : ((char *) domain->data
+				 + W (domain->must_swap,
+				      domain->trans_tab[act].offset));
+}
+
+
+/* Function to evaluate the plural expression and return an index value.  */
+static unsigned long int
+internal_function
+plural_eval (struct expression *pexp, unsigned long int n)
+{
+  switch (pexp->operation)
+    {
+    case var:
+      return n;
+    case num:
+      return pexp->val.num;
+    case mult:
+      return (plural_eval (pexp->val.args2.left, n)
+	      * plural_eval (pexp->val.args2.right, n));
+    case divide:
+      return (plural_eval (pexp->val.args2.left, n)
+	      / plural_eval (pexp->val.args2.right, n));
+    case module:
+      return (plural_eval (pexp->val.args2.left, n)
+	      % plural_eval (pexp->val.args2.right, n));
+    case plus:
+      return (plural_eval (pexp->val.args2.left, n)
+	      + plural_eval (pexp->val.args2.right, n));
+    case minus:
+      return (plural_eval (pexp->val.args2.left, n)
+	      - plural_eval (pexp->val.args2.right, n));
+    case equal:
+      return (plural_eval (pexp->val.args2.left, n)
+	      == plural_eval (pexp->val.args2.right, n));
+    case not_equal:
+      return (plural_eval (pexp->val.args2.left, n)
+	      != plural_eval (pexp->val.args2.right, n));
+    case land:
+      return (plural_eval (pexp->val.args2.left, n)
+	      && plural_eval (pexp->val.args2.right, n));
+    case lor:
+      return (plural_eval (pexp->val.args2.left, n)
+	      || plural_eval (pexp->val.args2.right, n));
+    case qmop:
+      return (plural_eval (pexp->val.args3.bexp, n)
+	      ? plural_eval (pexp->val.args3.tbranch, n)
+	      : plural_eval (pexp->val.args3.fbranch, n));
+    }
+  /* NOTREACHED */
+  return 0;
+}
+
+
+/* Return string representation of locale CATEGORY.  */
+static const char *
+internal_function
+category_to_name (category)
+     int category;
+{
+  const char *retval;
+
+  switch (category)
+  {
+#ifdef LC_COLLATE
+  case LC_COLLATE:
+    retval = "LC_COLLATE";
+    break;
+#endif
+#ifdef LC_CTYPE
+  case LC_CTYPE:
+    retval = "LC_CTYPE";
+    break;
+#endif
+#ifdef LC_MONETARY
+  case LC_MONETARY:
+    retval = "LC_MONETARY";
+    break;
+#endif
+#ifdef LC_NUMERIC
+  case LC_NUMERIC:
+    retval = "LC_NUMERIC";
+    break;
+#endif
+#ifdef LC_TIME
+  case LC_TIME:
+    retval = "LC_TIME";
+    break;
+#endif
+#ifdef LC_MESSAGES
+  case LC_MESSAGES:
+    retval = "LC_MESSAGES";
+    break;
+#endif
+#ifdef LC_RESPONSE
+  case LC_RESPONSE:
+    retval = "LC_RESPONSE";
+    break;
+#endif
+#ifdef LC_ALL
+  case LC_ALL:
+    /* This might not make sense but is perhaps better than any other
+       value.  */
+    retval = "LC_ALL";
+    break;
+#endif
+  default:
+    /* If you have a better idea for a default value let me know.  */
+    retval = "LC_XXX";
+  }
+
+  return retval;
+}
+
+/* Guess value of current locale from value of the environment variables.  */
+static const char *
+internal_function
+guess_category_value (category, categoryname)
+     int category;
+     const char *categoryname;
+{
+  const char *retval;
+
+  /* The highest priority value is the `LANGUAGE' environment
+     variable.  This is a GNU extension.  */
+  retval = getenv ("LANGUAGE");
+  if (retval != NULL && retval[0] != '\0')
+    return retval;
+
+  /* `LANGUAGE' is not set.  So we have to proceed with the POSIX
+     methods of looking to `LC_ALL', `LC_xxx', and `LANG'.  On some
+     systems this can be done by the `setlocale' function itself.  */
+#if defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES && defined HAVE_LOCALE_NULL
+  return setlocale (category, NULL);
+#else
+  /* Setting of LC_ALL overwrites all other.  */
+  retval = getenv ("LC_ALL");
+  if (retval != NULL && retval[0] != '\0')
+    return retval;
+
+  /* Next comes the name of the desired category.  */
+  retval = getenv (categoryname);
+  if (retval != NULL && retval[0] != '\0')
+    return retval;
+
+  /* Last possibility is the LANG environment variable.  */
+  retval = getenv ("LANG");
+  if (retval != NULL && retval[0] != '\0')
+    return retval;
+
+  /* We use C as the default domain.  POSIX says this is implementation
+     defined.  */
+  return "C";
+#endif
+}
+
+/* @@ begin of epilog @@ */
+
+/* We don't want libintl.a to depend on any other library.  So we
+   avoid the non-standard function stpcpy.  In GNU C Library this
+   function is available, though.  Also allow the symbol HAVE_STPCPY
+   to be defined.  */
+#if !_LIBC && !HAVE_STPCPY
+static char *
+stpcpy (dest, src)
+     char *dest;
+     const char *src;
+{
+  while ((*dest++ = *src++) != '\0')
+    /* Do nothing. */ ;
+  return dest - 1;
+}
+#endif
+
+#if !_LIBC && !HAVE_MEMPCPY
+static void *
+mempcpy (dest, src, n)
+     void *dest;
+     const void *src;
+     size_t n;
+{
+  return (void *) ((char *) memcpy (dst, src, n) + n);
+}
+#endif
+
+
+#ifdef _LIBC
+/* If we want to free all resources we have to do some work at
+   program's end.  */
+static void __attribute__ ((unused))
+free_mem (void)
+{
+  struct binding *runp;
+
+  for (runp = _nl_domain_bindings; runp != NULL; runp = runp->next)
+    {
+      free (runp->domainname);
+      if (runp->dirname != _nl_default_dirname)
+	/* Yes, this is a pointer comparison.  */
+	free (runp->dirname);
+    }
+
+  if (_nl_current_default_domain != _nl_default_default_domain)
+    /* Yes, again a pointer comparison.  */
+    free ((char *) _nl_current_default_domain);
+
+  /* Remove the search tree with the know translations.  */
+  __tdestroy (root, free);
+}
+
+text_set_element (__libc_subfreeres, free_mem);
+#endif
diff --git a/intl/dcngettext.c b/intl/dcngettext.c
new file mode 100644
index 0000000000..ef1dc349b6
--- /dev/null
+++ b/intl/dcngettext.c
@@ -0,0 +1,64 @@
+/* Implementation of the dcngettext(3) function.
+   Copyright (C) 1995-1999, 2000 Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "gettext.h"
+#include "gettextP.h"
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+
+/* @@ end of prolog @@ */
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define DCNGETTEXT __dcngettext
+# define DCIGETTEXT __dcigettext
+#else
+# define DCNGETTEXT dcngettext__
+# define DCIGETTEXT dcigettext__
+#endif
+
+/* Look up MSGID in the DOMAINNAME message catalog for the current CATEGORY
+   locale.  */
+char *
+DCNGETTEXT (domainname, msgid1, msgid2, n, category)
+     const char *domainname;
+     const char *msgid1;
+     const char *msgid2;
+     unsigned long int n;
+     int category;
+{
+  return DCIGETTEXT (domainname, msgid1, msgid2, 1, n, category);
+}
+
+#ifdef _LIBC
+/* Alias for function name in GNU C Library.  */
+weak_alias (__dcngettext, dcngettext);
+#endif
diff --git a/intl/dngettext.c b/intl/dngettext.c
new file mode 100644
index 0000000000..2fb861c6e4
--- /dev/null
+++ b/intl/dngettext.c
@@ -0,0 +1,67 @@
+/* Implementation of the dngettext(3) function.
+   Copyright (C) 1995, 1996, 1997, 2000 Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if defined HAVE_LOCALE_H || defined _LIBC
+# include <locale.h>
+#endif
+
+#include "gettext.h"
+#include "gettextP.h"
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+
+/* @@ end of prolog @@ */
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define DNGETTEXT __dngettext
+# define DCNGETTEXT __dcngettext
+#else
+# define DNGETTEXT dngettext__
+# define DCNGETTEXT dcngettext__
+#endif
+
+/* Look up MSGID in the DOMAINNAME message catalog of the current
+   LC_MESSAGES locale and skip message according to the plural form.  */
+char *
+DNGETTEXT (domainname, msgid1, msgid2, n)
+     const char *domainname;
+     const char *msgid1;
+     const char *msgid2;
+     unsigned long int n;
+{
+  return DCNGETTEXT (domainname, msgid1, msgid2, n, LC_MESSAGES);
+}
+
+#ifdef _LIBC
+/* Alias for function name in GNU C Library.  */
+weak_alias (__dngettext, dngettext);
+#endif
diff --git a/intl/gettext.c b/intl/gettext.c
index d4687ceb24..74eab14419 100644
--- a/intl/gettext.c
+++ b/intl/gettext.c
@@ -1,5 +1,5 @@
 /* Implementation of gettext(3) function.
-   Copyright (C) 1995, 1997 Free Software Foundation, Inc.
+   Copyright (C) 1995, 1997, 2000 Free Software Foundation, Inc.
 
    This file is part of the GNU C Library.  Its master source is NOT part of
    the C library, however.
@@ -52,10 +52,10 @@
    prefix.  So we have to make a difference here.  */
 #ifdef _LIBC
 # define GETTEXT __gettext
-# define DGETTEXT __dgettext
+# define DCGETTEXT __dcgettext
 #else
 # define GETTEXT gettext__
-# define DGETTEXT dgettext__
+# define DCGETTEXT dcgettext__
 #endif
 
 /* Look up MSGID in the current default message catalog for the current
@@ -65,7 +65,7 @@ char *
 GETTEXT (msgid)
      const char *msgid;
 {
-  return DGETTEXT (NULL, msgid);
+  return DCGETTEXT (NULL, msgid, LC_MESSAGES);
 }
 
 #ifdef _LIBC
diff --git a/intl/gettextP.h b/intl/gettextP.h
index b3c8c18598..d210dc6c4e 100644
--- a/intl/gettextP.h
+++ b/intl/gettextP.h
@@ -1,5 +1,5 @@
 /* Header describing internals of gettext library
-   Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
+   Copyright (C) 1995-1999, 2000 Free Software Foundation, Inc.
    Written by Ulrich Drepper <drepper@cygnus.com>, 1995.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -64,6 +64,51 @@ SWAP (i)
 #endif
 
 
+/* This is the representation of the expressions to determine the
+   plural form.  */
+struct expression
+{
+  enum operator
+  {
+    var,			/* The variable "n".  */
+    num,			/* Decimal number.  */
+    mult,			/* Multiplication.  */
+    divide,			/* Division.  */
+    module,			/* Module operation.  */
+    plus,			/* Addition.  */
+    minus,			/* Subtraction.  */
+    equal,			/* Comparision for equality.  */
+    not_equal,			/* Comparision for inequality.  */
+    land,			/* Logical AND.  */
+    lor,			/* Logical OR.  */
+    qmop			/* Question mark operator.  */
+  } operation;
+  union
+  {
+    unsigned long int num;	/* Number value for `num'.  */
+    struct
+    {
+      struct expression *left;	/* Left expression in binary operation.  */
+      struct expression *right;	/* Right expression in binary operation.  */
+    } args2;
+    struct
+    {
+      struct expression *bexp;	/* Boolean expression in ?: operation.  */
+      struct expression *tbranch; /* True-branch in ?: operation.  */
+      struct expression *fbranch; /* False-branch in ?: operation.  */
+    } args3;
+  } val;
+};
+
+/* This is the data structure to pass information to the parser and get
+   the result in a thread-safe way.  */
+struct parse_args
+{
+  const char *cp;
+  struct expression *res;
+};
+
+
 struct loaded_domain
 {
   const char *data;
@@ -83,6 +128,9 @@ struct loaded_domain
 # endif
 #endif
   char **conv_tab;
+
+  struct expression *plural;
+  unsigned long int nplurals;
 };
 
 struct binding
@@ -103,6 +151,34 @@ void _nl_load_domain PARAMS ((struct loaded_l10nfile *__domain))
 void _nl_unload_domain PARAMS ((struct loaded_domain *__domain))
      internal_function;
 
+#ifdef _LIBC
+extern char *__ngettext PARAMS ((const char *msgid1, const char *msgid2,
+				 unsigned long int n));
+extern char *__dngettext PARAMS ((const char *domainname, const char *msgid1,
+				  const char *msgid2, unsigned long int n));
+extern char *__dcngettext PARAMS ((const char *domainname, const char *msgid1,
+				   const char *msgid2, unsigned long int n,
+				   int category));
+extern char *__dcigettext PARAMS ((const char *domainname, const char *msgid1,
+				   const char *msgid2, int plural,
+				   unsigned long int n, int category));
+#else
+extern char *ngettext__ PARAMS ((const char *msgid1, const char *msgid2,
+				 unsigned long int n));
+extern char *dngettext__ PARAMS ((const char *domainname, const char *msgid1,
+				  const char *msgid2, unsigned long int n));
+extern char *dcngettext__ PARAMS ((const char *domainname, const char *msgid1,
+				   const char *msgid2, unsigned long int n,
+				   int category));
+extern char *dcigettext__ PARAMS ((const char *domainname, const char *msgid1,
+				   const char *msgid2, int plural,
+				   unsigned long int n, int category));
+#endif
+
+extern int __gettextdebug;
+extern void __gettext_free_exp (struct expression *exp) internal_function;
+extern int __gettextparse (void *arg);
+
 /* @@ begin of epilog @@ */
 
 #endif /* gettextP.h  */
diff --git a/intl/libintl.h b/intl/libintl.h
index 10c3726b7c..5ac716b75c 100644
--- a/intl/libintl.h
+++ b/intl/libintl.h
@@ -1,6 +1,5 @@
 /* Message catalogs for internationalization.
-   Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
-   Contributed by Ulrich Drepper <drepper@cygnus.com>, 1995.
+   Copyright (C) 1995-1999, 2000 Free Software Foundation, Inc.
    This file is derived from the file libgettext.h in the GNU gettext package.
 
    This file is part of the GNU C Library.  Its master source is NOT part of
@@ -52,6 +51,23 @@ extern char *__dcgettext (__const char *__domainname,
 			  __const char *__msgid, int __category) __THROW;
 
 
+/* Similar to `gettext' but select the plural form corresponding to the
+   number N.  */
+extern char *ngettext (__const char *__msgid1, __const char *__msgid2,
+		       unsigned long int __n) __THROW;
+
+/* Similar to `dgettext' but select the plural form corresponding to the
+   number N.  */
+extern char *dngettext (__const char *__domainname, __const char *__msgid1,
+			__const char *__msgid2, unsigned long int __n) __THROW;
+
+/* Similar to `dxgettext' but select the plural form corresponding to the
+   number N.  */
+extern char *dcngettext (__const char *__domainname, __const char *__msgid1,
+			 __const char *__msgid2, unsigned long int __n,
+			 int __category) __THROW;
+
+
 /* Set the current default message catalog to DOMAINNAME.
    If DOMAINNAME is null, return the current default.
    If DOMAINNAME is "", reset to the default of "messages".  */
@@ -82,6 +98,11 @@ extern char *bindtextdomain (__const char *__domainname,
 # define dgettext(domainname, msgid)					      \
   dcgettext (domainname, msgid, LC_MESSAGES)
 
+# define ngettext(msgid, n) dngettext (NULL, msgid, n)
+
+# define dngettext(domainname, msgid, n)				      \
+  dcngettext (domainname, msgid, n, LC_MESSAGES)
+
 #endif	/* Optimizing.  */
 
 __END_DECLS
diff --git a/intl/loadinfo.h b/intl/loadinfo.h
index 09b2fdf6ac..009abcc620 100644
--- a/intl/loadinfo.h
+++ b/intl/loadinfo.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
+/* Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
 
@@ -89,7 +89,7 @@ extern char *_nl_find_language PARAMS ((const char *name));
 
 
 extern char *_nl_find_msg PARAMS ((struct loaded_l10nfile *domain_file,
-				   const char *msgid))
+				   const char *msgid, unsigned long int index))
      internal_function;
 
 #endif	/* loadinfo.h */
diff --git a/intl/loadmsgcat.c b/intl/loadmsgcat.c
index ba818ce3b1..b017d710e8 100644
--- a/intl/loadmsgcat.c
+++ b/intl/loadmsgcat.c
@@ -1,5 +1,5 @@
 /* Load needed message catalogs.
-   Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc.
+   Copyright (C) 1995-1999, 2000 Free Software Foundation, Inc.
 
    This file is part of the GNU C Library.  Its master source is NOT part of
    the C library, however.
@@ -23,6 +23,7 @@
 # include <config.h>
 #endif
 
+#include <ctype.h>
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -82,6 +83,32 @@
    cached by one of GCC's features.  */
 int _nl_msg_cat_cntr;
 
+/* These structs are the constant expression for the germanic plural
+   form determination.  */
+static const struct expression plvar =
+{
+  .operation = var,
+};
+static const struct expression plone =
+{
+  .operation = num,
+  .val =
+  {
+    .num = 1
+  }
+};
+static struct expression germanic_plural =
+{
+  .operation = not_equal,
+  .val =
+  {
+    .args2 = {
+      .left = (struct expression *) &plvar,
+      .right = (struct expression *) &plone
+    }
+  }
+};
+
 
 /* Load the message catalogs specified by FILENAME.  If it is no valid
    message catalog do nothing.  */
@@ -230,10 +257,12 @@ _nl_load_domain (domain_file)
   domain->conv = (iconv_t) -1;
 # endif
 #endif
-  nullentry = _nl_find_msg (domain_file, "");
+  nullentry = _nl_find_msg (domain_file, "", 0);
   if (nullentry != NULL)
     {
-      char *charsetstr = strstr (nullentry, "charset=");
+      const char *charsetstr = strstr (nullentry, "charset=");
+      const char *plural;
+      const char *nplurals;
 
       if (charsetstr != NULL)
 	{
@@ -270,6 +299,42 @@ _nl_load_domain (domain_file)
 # endif
 #endif
 	}
+
+      /* Also look for a plural specification.  */
+      plural = strstr (nullentry, "plural=");
+      nplurals = strstr (nullentry, "nplurals=");
+      if (plural == NULL || nplurals == NULL)
+	{
+	  /* By default we are using the Germanic form: singular form only
+	     for `one', the plural form otherwise.  Yes, this is also what
+	     English is using since English is a Germanic language.  */
+	no_plural:
+	  domain->plural = &germanic_plural;
+	  domain->nplurals = 2;
+	}
+      else
+	{
+	  /* First get the number.  */
+	  char *endp;
+	  struct parse_args args;
+
+	  nplurals += 9;
+	  while (*nplurals != '\0' && isspace (*nplurals))
+	    ++nplurals;
+	  domain->nplurals = strtoul (nplurals, &endp, 10);
+	  if (nplurals == endp)
+	    goto no_plural;
+
+	  /* Due to the restrictions bison imposes onto the interface of the
+	     scanner function we have to put the input string and the result
+	     passed up from the parser into the same structure which address
+	     is passed down to the parser.  */
+	  plural += 7;
+	  args.cp = plural;
+	  if (__gettextparse (&args) != 0)
+	    goto no_plural;
+	  domain->plural = args.res;
+	}
     }
 }
 
@@ -280,6 +345,19 @@ internal_function
 _nl_unload_domain (domain)
      struct loaded_domain *domain;
 {
+  if (domain->plural != &germanic_plural)
+    __gettext_free_exp (domain->plural);
+
+#ifdef _LIBC
+  if (domain->conv != (__gconv_t) -1)
+    __gconv_close (domain->conv);
+#else
+# if HAVE_ICONV
+  if (domain->conv != (iconv_t) -1)
+    iconv_close (domain->conv);
+# endif
+#endif
+
 #ifdef _POSIX_MAPPED_FILES
   if (domain->use_mmap)
     munmap ((caddr_t) domain->data, domain->mmap_size);
diff --git a/intl/ngettext.c b/intl/ngettext.c
new file mode 100644
index 0000000000..99e74d9938
--- /dev/null
+++ b/intl/ngettext.c
@@ -0,0 +1,78 @@
+/* Implementation of ngettext(3) function.
+   Copyright (C) 1995, 1997, 2000 Free Software Foundation, Inc.
+
+   This file is part of the GNU C Library.  Its master source is NOT part of
+   the C library, however.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef _LIBC
+# define __need_NULL
+# include <stddef.h>
+#else
+# ifdef STDC_HEADERS
+#  include <stdlib.h>		/* Just for NULL.  */
+# else
+#  ifdef HAVE_STRING_H
+#   include <string.h>
+#  else
+#   define NULL ((void *) 0)
+#  endif
+# endif
+#endif
+
+#include "gettext.h"
+#include "gettextP.h"
+#ifdef _LIBC
+# include <libintl.h>
+#else
+# include "libgettext.h"
+#endif
+
+/* @@ end of prolog @@ */
+
+/* Names for the libintl functions are a problem.  They must not clash
+   with existing names and they should follow ANSI C.  But this source
+   code is also used in GNU C Library where the names have a __
+   prefix.  So we have to make a difference here.  */
+#ifdef _LIBC
+# define NGETTEXT __ngettext
+# define DCNGETTEXT __dcngettext
+#else
+# define NGETTEXT gettext__
+# define DCNGETTEXT dcngettext__
+#endif
+
+/* Look up MSGID in the current default message catalog for the current
+   LC_MESSAGES locale.  If not found, returns MSGID itself (the default
+   text).  */
+char *
+NGETTEXT (msgid1, msgid2, n)
+     const char *msgid1;
+     const char *msgid2;
+     unsigned long int n;
+{
+  return DCNGETTEXT (NULL, msgid1, msgid2, n, LC_MESSAGES);
+}
+
+#ifdef _LIBC
+/* Alias for function name in GNU C Library.  */
+weak_alias (__ngettext, ngettext);
+#endif
diff --git a/intl/plural.c b/intl/plural.c
new file mode 100644
index 0000000000..f9165b7868
--- /dev/null
+++ b/intl/plural.c
@@ -0,0 +1,1218 @@
+
+/*  A Bison parser, made from plural.y
+    by GNU Bison version 1.28  */
+
+#define YYBISON 1  /* Identify Bison output.  */
+
+#define yyparse __gettextparse
+#define yylex __gettextlex
+#define yyerror __gettexterror
+#define yylval __gettextlval
+#define yychar __gettextchar
+#define yydebug __gettextdebug
+#define yynerrs __gettextnerrs
+#define	NUMBER	257
+
+#line 1 "plural.y"
+
+/* Expression parsing for plural form selection.
+   Copyright (C) 2000 Free Software Foundation, Inc.
+   Written by Ulrich Drepper <drepper@cygnus.com>, 2000.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include "gettext.h"
+#include "gettextP.h"
+
+#define YYLEX_PARAM	&((struct parse_args *) arg)->cp
+#define YYPARSE_PARAM	arg
+
+#line 32 "plural.y"
+typedef union {
+  unsigned long int num;
+  struct expression *exp;
+} YYSTYPE;
+#line 37 "plural.y"
+
+/* Prototypes for local functions.  */
+static struct expression *new_exp (enum operator op, ...);
+static int yylex (YYSTYPE *lval, const char **pexp);
+static void yyerror (const char *str);
+#include <stdio.h>
+
+#ifndef __cplusplus
+#ifndef __STDC__
+#define const
+#endif
+#endif
+
+
+
+#define	YYFINAL		31
+#define	YYFLAG		-32768
+#define	YYNTBASE	18
+
+#define YYTRANSLATE(x) ((unsigned)(x) <= 257 ? yytranslate[x] : 20)
+
+static const char yytranslate[] = {     0,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     7,     2,     2,     2,    12,     5,     2,    16,
+    17,    10,     8,     2,     9,     2,    11,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,    14,     2,     2,
+     6,     2,     3,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,    15,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     4,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     1,    13
+};
+
+#if YYDEBUG != 0
+static const short yyprhs[] = {     0,
+     0,     2,     8,    12,    16,    20,    24,    28,    32,    36,
+    40,    44,    46,    48
+};
+
+static const short yyrhs[] = {    19,
+     0,    19,     3,    19,    14,    19,     0,    19,     4,    19,
+     0,    19,     5,    19,     0,    19,     6,    19,     0,    19,
+     7,    19,     0,    19,     8,    19,     0,    19,     9,    19,
+     0,    19,    10,    19,     0,    19,    11,    19,     0,    19,
+    12,    19,     0,    15,     0,    13,     0,    16,    19,    17,
+     0
+};
+
+#endif
+
+#if YYDEBUG != 0
+static const short yyrline[] = { 0,
+    55,    61,    66,    71,    76,    81,    86,    91,    96,   101,
+   106,   111,   116,   122
+};
+#endif
+
+
+#if YYDEBUG != 0 || defined (YYERROR_VERBOSE)
+
+static const char * const yytname[] = {   "$","error","$undefined.","'?'","'|'",
+"'&'","'='","'!'","'+'","'-'","'*'","'/'","'%'","NUMBER","':'","'n'","'('","')'",
+"start","exp", NULL
+};
+#endif
+
+static const short yyr1[] = {     0,
+    18,    19,    19,    19,    19,    19,    19,    19,    19,    19,
+    19,    19,    19,    19
+};
+
+static const short yyr2[] = {     0,
+     1,     5,     3,     3,     3,     3,     3,     3,     3,     3,
+     3,     1,     1,     3
+};
+
+static const short yydefact[] = {     0,
+    13,    12,     0,     1,     0,     0,     0,     0,     0,     0,
+     0,     0,     0,     0,     0,    14,     0,     3,     4,     5,
+     6,     7,     8,     9,    10,    11,     0,     2,     0,     0,
+     0
+};
+
+static const short yydefgoto[] = {    29,
+     4
+};
+
+static const short yypact[] = {    58,
+-32768,-32768,    58,    37,    10,    58,    58,    58,    58,    58,
+    58,    58,    58,    58,    58,-32768,    25,    45,    52,    57,
+    57,    65,    65,-32768,-32768,-32768,    58,    37,     1,     2,
+-32768
+};
+
+static const short yypgoto[] = {-32768,
+    -3
+};
+
+
+#define	YYLAST		77
+
+
+static const short yytable[] = {     5,
+    30,    31,    17,    18,    19,    20,    21,    22,    23,    24,
+    25,    26,     6,     7,     8,     9,    10,    11,    12,    13,
+    14,    15,     0,    28,     0,     0,    16,     6,     7,     8,
+     9,    10,    11,    12,    13,    14,    15,     0,    27,     6,
+     7,     8,     9,    10,    11,    12,    13,    14,    15,     8,
+     9,    10,    11,    12,    13,    14,    15,     9,    10,    11,
+    12,    13,    14,    15,    11,    12,    13,    14,    15,     0,
+     1,     0,     2,     3,    13,    14,    15
+};
+
+static const short yycheck[] = {     3,
+     0,     0,     6,     7,     8,     9,    10,    11,    12,    13,
+    14,    15,     3,     4,     5,     6,     7,     8,     9,    10,
+    11,    12,    -1,    27,    -1,    -1,    17,     3,     4,     5,
+     6,     7,     8,     9,    10,    11,    12,    -1,    14,     3,
+     4,     5,     6,     7,     8,     9,    10,    11,    12,     5,
+     6,     7,     8,     9,    10,    11,    12,     6,     7,     8,
+     9,    10,    11,    12,     8,     9,    10,    11,    12,    -1,
+    13,    -1,    15,    16,    10,    11,    12
+};
+#define YYPURE 1
+
+/* -*-C-*-  Note some compilers choke on comments on `#line' lines.  */
+#line 3 "/usr/share/bison.simple"
+/* This file comes from bison-1.28.  */
+
+/* Skeleton output parser for bison,
+   Copyright (C) 1984, 1989, 1990 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 2, 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, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+/* As a special exception, when this file is copied by Bison into a
+   Bison output file, you may use that output file without restriction.
+   This special exception was added by the Free Software Foundation
+   in version 1.24 of Bison.  */
+
+/* This is the parser code that is written into each bison parser
+  when the %semantic_parser declaration is not specified in the grammar.
+  It was written by Richard Stallman by simplifying the hairy parser
+  used when %semantic_parser is specified.  */
+
+#ifndef YYSTACK_USE_ALLOCA
+#ifdef alloca
+#define YYSTACK_USE_ALLOCA
+#else /* alloca not defined */
+#ifdef __GNUC__
+#define YYSTACK_USE_ALLOCA
+#define alloca __builtin_alloca
+#else /* not GNU C.  */
+#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi) || (defined (__sun) && defined (__i386))
+#define YYSTACK_USE_ALLOCA
+#include <alloca.h>
+#else /* not sparc */
+/* We think this test detects Watcom and Microsoft C.  */
+/* This used to test MSDOS, but that is a bad idea
+   since that symbol is in the user namespace.  */
+#if (defined (_MSDOS) || defined (_MSDOS_)) && !defined (__TURBOC__)
+#if 0 /* No need for malloc.h, which pollutes the namespace;
+	 instead, just don't use alloca.  */
+#include <malloc.h>
+#endif
+#else /* not MSDOS, or __TURBOC__ */
+#if defined(_AIX)
+/* I don't know what this was needed for, but it pollutes the namespace.
+   So I turned it off.   rms, 2 May 1997.  */
+/* #include <malloc.h>  */
+ #pragma alloca
+#define YYSTACK_USE_ALLOCA
+#else /* not MSDOS, or __TURBOC__, or _AIX */
+#if 0
+#ifdef __hpux /* haible@ilog.fr says this works for HPUX 9.05 and up,
+		 and on HPUX 10.  Eventually we can turn this on.  */
+#define YYSTACK_USE_ALLOCA
+#define alloca __builtin_alloca
+#endif /* __hpux */
+#endif
+#endif /* not _AIX */
+#endif /* not MSDOS, or __TURBOC__ */
+#endif /* not sparc */
+#endif /* not GNU C */
+#endif /* alloca not defined */
+#endif /* YYSTACK_USE_ALLOCA not defined */
+
+#ifdef YYSTACK_USE_ALLOCA
+#define YYSTACK_ALLOC alloca
+#else
+#define YYSTACK_ALLOC malloc
+#endif
+
+/* Note: there must be only one dollar sign in this file.
+   It is replaced by the list of actions, each action
+   as one case of the switch.  */
+
+#define yyerrok		(yyerrstatus = 0)
+#define yyclearin	(yychar = YYEMPTY)
+#define YYEMPTY		-2
+#define YYEOF		0
+#define YYACCEPT	goto yyacceptlab
+#define YYABORT 	goto yyabortlab
+#define YYERROR		goto yyerrlab1
+/* Like YYERROR except do call yyerror.
+   This remains here temporarily to ease the
+   transition to the new meaning of YYERROR, for GCC.
+   Once GCC version 2 has supplanted version 1, this can go.  */
+#define YYFAIL		goto yyerrlab
+#define YYRECOVERING()  (!!yyerrstatus)
+#define YYBACKUP(token, value) \
+do								\
+  if (yychar == YYEMPTY && yylen == 1)				\
+    { yychar = (token), yylval = (value);			\
+      yychar1 = YYTRANSLATE (yychar);				\
+      YYPOPSTACK;						\
+      goto yybackup;						\
+    }								\
+  else								\
+    { yyerror ("syntax error: cannot back up"); YYERROR; }	\
+while (0)
+
+#define YYTERROR	1
+#define YYERRCODE	256
+
+#ifndef YYPURE
+#define YYLEX		yylex()
+#endif
+
+#ifdef YYPURE
+#ifdef YYLSP_NEEDED
+#ifdef YYLEX_PARAM
+#define YYLEX		yylex(&yylval, &yylloc, YYLEX_PARAM)
+#else
+#define YYLEX		yylex(&yylval, &yylloc)
+#endif
+#else /* not YYLSP_NEEDED */
+#ifdef YYLEX_PARAM
+#define YYLEX		yylex(&yylval, YYLEX_PARAM)
+#else
+#define YYLEX		yylex(&yylval)
+#endif
+#endif /* not YYLSP_NEEDED */
+#endif
+
+/* If nonreentrant, generate the variables here */
+
+#ifndef YYPURE
+
+int	yychar;			/*  the lookahead symbol		*/
+YYSTYPE	yylval;			/*  the semantic value of the		*/
+				/*  lookahead symbol			*/
+
+#ifdef YYLSP_NEEDED
+YYLTYPE yylloc;			/*  location data for the lookahead	*/
+				/*  symbol				*/
+#endif
+
+int yynerrs;			/*  number of parse errors so far       */
+#endif  /* not YYPURE */
+
+#if YYDEBUG != 0
+int yydebug;			/*  nonzero means print parse trace	*/
+/* Since this is uninitialized, it does not stop multiple parsers
+   from coexisting.  */
+#endif
+
+/*  YYINITDEPTH indicates the initial size of the parser's stacks	*/
+
+#ifndef	YYINITDEPTH
+#define YYINITDEPTH 200
+#endif
+
+/*  YYMAXDEPTH is the maximum size the stacks can grow to
+    (effective only if the built-in stack extension method is used).  */
+
+#if YYMAXDEPTH == 0
+#undef YYMAXDEPTH
+#endif
+
+#ifndef YYMAXDEPTH
+#define YYMAXDEPTH 10000
+#endif
+
+/* Define __yy_memcpy.  Note that the size argument
+   should be passed with type unsigned int, because that is what the non-GCC
+   definitions require.  With GCC, __builtin_memcpy takes an arg
+   of type size_t, but it can handle unsigned int.  */
+
+#if __GNUC__ > 1		/* GNU C and GNU C++ define this.  */
+#define __yy_memcpy(TO,FROM,COUNT)	__builtin_memcpy(TO,FROM,COUNT)
+#else				/* not GNU C or C++ */
+#ifndef __cplusplus
+
+/* This is the most reliable way to avoid incompatibilities
+   in available built-in functions on various systems.  */
+static void
+__yy_memcpy (to, from, count)
+     char *to;
+     char *from;
+     unsigned int count;
+{
+  register char *f = from;
+  register char *t = to;
+  register int i = count;
+
+  while (i-- > 0)
+    *t++ = *f++;
+}
+
+#else /* __cplusplus */
+
+/* This is the most reliable way to avoid incompatibilities
+   in available built-in functions on various systems.  */
+static void
+__yy_memcpy (char *to, char *from, unsigned int count)
+{
+  register char *t = to;
+  register char *f = from;
+  register int i = count;
+
+  while (i-- > 0)
+    *t++ = *f++;
+}
+
+#endif
+#endif
+
+#line 217 "/usr/share/bison.simple"
+
+/* The user can define YYPARSE_PARAM as the name of an argument to be passed
+   into yyparse.  The argument should have type void *.
+   It should actually point to an object.
+   Grammar actions can access the variable by casting it
+   to the proper pointer type.  */
+
+#ifdef YYPARSE_PARAM
+#ifdef __cplusplus
+#define YYPARSE_PARAM_ARG void *YYPARSE_PARAM
+#define YYPARSE_PARAM_DECL
+#else /* not __cplusplus */
+#define YYPARSE_PARAM_ARG YYPARSE_PARAM
+#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM;
+#endif /* not __cplusplus */
+#else /* not YYPARSE_PARAM */
+#define YYPARSE_PARAM_ARG
+#define YYPARSE_PARAM_DECL
+#endif /* not YYPARSE_PARAM */
+
+/* Prevent warning if -Wstrict-prototypes.  */
+#ifdef __GNUC__
+#ifdef YYPARSE_PARAM
+int yyparse (void *);
+#else
+int yyparse (void);
+#endif
+#endif
+
+int
+yyparse(YYPARSE_PARAM_ARG)
+     YYPARSE_PARAM_DECL
+{
+  register int yystate;
+  register int yyn;
+  register short *yyssp;
+  register YYSTYPE *yyvsp;
+  int yyerrstatus;	/*  number of tokens to shift before error messages enabled */
+  int yychar1 = 0;		/*  lookahead token as an internal (translated) token number */
+
+  short	yyssa[YYINITDEPTH];	/*  the state stack			*/
+  YYSTYPE yyvsa[YYINITDEPTH];	/*  the semantic value stack		*/
+
+  short *yyss = yyssa;		/*  refer to the stacks thru separate pointers */
+  YYSTYPE *yyvs = yyvsa;	/*  to allow yyoverflow to reallocate them elsewhere */
+
+#ifdef YYLSP_NEEDED
+  YYLTYPE yylsa[YYINITDEPTH];	/*  the location stack			*/
+  YYLTYPE *yyls = yylsa;
+  YYLTYPE *yylsp;
+
+#define YYPOPSTACK   (yyvsp--, yyssp--, yylsp--)
+#else
+#define YYPOPSTACK   (yyvsp--, yyssp--)
+#endif
+
+  int yystacksize = YYINITDEPTH;
+  int yyfree_stacks = 0;
+
+#ifdef YYPURE
+  int yychar;
+  YYSTYPE yylval;
+  int yynerrs;
+#ifdef YYLSP_NEEDED
+  YYLTYPE yylloc;
+#endif
+#endif
+
+  YYSTYPE yyval;		/*  the variable used to return		*/
+				/*  semantic values from the action	*/
+				/*  routines				*/
+
+  int yylen;
+
+#if YYDEBUG != 0
+  if (yydebug)
+    fprintf(stderr, "Starting parse\n");
+#endif
+
+  yystate = 0;
+  yyerrstatus = 0;
+  yynerrs = 0;
+  yychar = YYEMPTY;		/* Cause a token to be read.  */
+
+  /* Initialize stack pointers.
+     Waste one element of value and location stack
+     so that they stay on the same level as the state stack.
+     The wasted elements are never initialized.  */
+
+  yyssp = yyss - 1;
+  yyvsp = yyvs;
+#ifdef YYLSP_NEEDED
+  yylsp = yyls;
+#endif
+
+/* Push a new state, which is found in  yystate  .  */
+/* In all cases, when you get here, the value and location stacks
+   have just been pushed. so pushing a state here evens the stacks.  */
+yynewstate:
+
+  *++yyssp = yystate;
+
+  if (yyssp >= yyss + yystacksize - 1)
+    {
+      /* Give user a chance to reallocate the stack */
+      /* Use copies of these so that the &'s don't force the real ones into memory. */
+      YYSTYPE *yyvs1 = yyvs;
+      short *yyss1 = yyss;
+#ifdef YYLSP_NEEDED
+      YYLTYPE *yyls1 = yyls;
+#endif
+
+      /* Get the current used size of the three stacks, in elements.  */
+      int size = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+      /* Each stack pointer address is followed by the size of
+	 the data in use in that stack, in bytes.  */
+#ifdef YYLSP_NEEDED
+      /* This used to be a conditional around just the two extra args,
+	 but that might be undefined if yyoverflow is a macro.  */
+      yyoverflow("parser stack overflow",
+		 &yyss1, size * sizeof (*yyssp),
+		 &yyvs1, size * sizeof (*yyvsp),
+		 &yyls1, size * sizeof (*yylsp),
+		 &yystacksize);
+#else
+      yyoverflow("parser stack overflow",
+		 &yyss1, size * sizeof (*yyssp),
+		 &yyvs1, size * sizeof (*yyvsp),
+		 &yystacksize);
+#endif
+
+      yyss = yyss1; yyvs = yyvs1;
+#ifdef YYLSP_NEEDED
+      yyls = yyls1;
+#endif
+#else /* no yyoverflow */
+      /* Extend the stack our own way.  */
+      if (yystacksize >= YYMAXDEPTH)
+	{
+	  yyerror("parser stack overflow");
+	  if (yyfree_stacks)
+	    {
+	      free (yyss);
+	      free (yyvs);
+#ifdef YYLSP_NEEDED
+	      free (yyls);
+#endif
+	    }
+	  return 2;
+	}
+      yystacksize *= 2;
+      if (yystacksize > YYMAXDEPTH)
+	yystacksize = YYMAXDEPTH;
+#ifndef YYSTACK_USE_ALLOCA
+      yyfree_stacks = 1;
+#endif
+      yyss = (short *) YYSTACK_ALLOC (yystacksize * sizeof (*yyssp));
+      __yy_memcpy ((char *)yyss, (char *)yyss1,
+		   size * (unsigned int) sizeof (*yyssp));
+      yyvs = (YYSTYPE *) YYSTACK_ALLOC (yystacksize * sizeof (*yyvsp));
+      __yy_memcpy ((char *)yyvs, (char *)yyvs1,
+		   size * (unsigned int) sizeof (*yyvsp));
+#ifdef YYLSP_NEEDED
+      yyls = (YYLTYPE *) YYSTACK_ALLOC (yystacksize * sizeof (*yylsp));
+      __yy_memcpy ((char *)yyls, (char *)yyls1,
+		   size * (unsigned int) sizeof (*yylsp));
+#endif
+#endif /* no yyoverflow */
+
+      yyssp = yyss + size - 1;
+      yyvsp = yyvs + size - 1;
+#ifdef YYLSP_NEEDED
+      yylsp = yyls + size - 1;
+#endif
+
+#if YYDEBUG != 0
+      if (yydebug)
+	fprintf(stderr, "Stack size increased to %d\n", yystacksize);
+#endif
+
+      if (yyssp >= yyss + yystacksize - 1)
+	YYABORT;
+    }
+
+#if YYDEBUG != 0
+  if (yydebug)
+    fprintf(stderr, "Entering state %d\n", yystate);
+#endif
+
+  goto yybackup;
+ yybackup:
+
+/* Do appropriate processing given the current state.  */
+/* Read a lookahead token if we need one and don't already have one.  */
+/* yyresume: */
+
+  /* First try to decide what to do without reference to lookahead token.  */
+
+  yyn = yypact[yystate];
+  if (yyn == YYFLAG)
+    goto yydefault;
+
+  /* Not known => get a lookahead token if don't already have one.  */
+
+  /* yychar is either YYEMPTY or YYEOF
+     or a valid token in external form.  */
+
+  if (yychar == YYEMPTY)
+    {
+#if YYDEBUG != 0
+      if (yydebug)
+	fprintf(stderr, "Reading a token: ");
+#endif
+      yychar = YYLEX;
+    }
+
+  /* Convert token to internal form (in yychar1) for indexing tables with */
+
+  if (yychar <= 0)		/* This means end of input. */
+    {
+      yychar1 = 0;
+      yychar = YYEOF;		/* Don't call YYLEX any more */
+
+#if YYDEBUG != 0
+      if (yydebug)
+	fprintf(stderr, "Now at end of input.\n");
+#endif
+    }
+  else
+    {
+      yychar1 = YYTRANSLATE(yychar);
+
+#if YYDEBUG != 0
+      if (yydebug)
+	{
+	  fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]);
+	  /* Give the individual parser a way to print the precise meaning
+	     of a token, for further debugging info.  */
+#ifdef YYPRINT
+	  YYPRINT (stderr, yychar, yylval);
+#endif
+	  fprintf (stderr, ")\n");
+	}
+#endif
+    }
+
+  yyn += yychar1;
+  if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1)
+    goto yydefault;
+
+  yyn = yytable[yyn];
+
+  /* yyn is what to do for this token type in this state.
+     Negative => reduce, -yyn is rule number.
+     Positive => shift, yyn is new state.
+       New state is final state => don't bother to shift,
+       just return success.
+     0, or most negative number => error.  */
+
+  if (yyn < 0)
+    {
+      if (yyn == YYFLAG)
+	goto yyerrlab;
+      yyn = -yyn;
+      goto yyreduce;
+    }
+  else if (yyn == 0)
+    goto yyerrlab;
+
+  if (yyn == YYFINAL)
+    YYACCEPT;
+
+  /* Shift the lookahead token.  */
+
+#if YYDEBUG != 0
+  if (yydebug)
+    fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]);
+#endif
+
+  /* Discard the token being shifted unless it is eof.  */
+  if (yychar != YYEOF)
+    yychar = YYEMPTY;
+
+  *++yyvsp = yylval;
+#ifdef YYLSP_NEEDED
+  *++yylsp = yylloc;
+#endif
+
+  /* count tokens shifted since error; after three, turn off error status.  */
+  if (yyerrstatus) yyerrstatus--;
+
+  yystate = yyn;
+  goto yynewstate;
+
+/* Do the default action for the current state.  */
+yydefault:
+
+  yyn = yydefact[yystate];
+  if (yyn == 0)
+    goto yyerrlab;
+
+/* Do a reduction.  yyn is the number of a rule to reduce with.  */
+yyreduce:
+  yylen = yyr2[yyn];
+  if (yylen > 0)
+    yyval = yyvsp[1-yylen]; /* implement default value of the action */
+
+#if YYDEBUG != 0
+  if (yydebug)
+    {
+      int i;
+
+      fprintf (stderr, "Reducing via rule %d (line %d), ",
+	       yyn, yyrline[yyn]);
+
+      /* Print the symbols being reduced, and their result.  */
+      for (i = yyprhs[yyn]; yyrhs[i] > 0; i++)
+	fprintf (stderr, "%s ", yytname[yyrhs[i]]);
+      fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]);
+    }
+#endif
+
+
+  switch (yyn) {
+
+case 1:
+#line 56 "plural.y"
+{
+	    ((struct parse_args *) arg)->res = yyvsp[0].exp;
+	  ;
+    break;}
+case 2:
+#line 62 "plural.y"
+{
+	    if ((yyval.exp = new_exp (qmop, yyvsp[-4].exp, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 3:
+#line 67 "plural.y"
+{
+	    if ((yyval.exp = new_exp (lor, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 4:
+#line 72 "plural.y"
+{
+	    if ((yyval.exp = new_exp (land, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 5:
+#line 77 "plural.y"
+{
+	    if ((yyval.exp = new_exp (equal, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 6:
+#line 82 "plural.y"
+{
+	    if ((yyval.exp = new_exp (not_equal, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 7:
+#line 87 "plural.y"
+{
+	    if ((yyval.exp = new_exp (plus, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 8:
+#line 92 "plural.y"
+{
+	    if ((yyval.exp = new_exp (minus, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 9:
+#line 97 "plural.y"
+{
+	    if ((yyval.exp = new_exp (mult, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 10:
+#line 102 "plural.y"
+{
+	    if ((yyval.exp = new_exp (divide, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 11:
+#line 107 "plural.y"
+{
+	    if ((yyval.exp = new_exp (module, yyvsp[-2].exp, yyvsp[0].exp, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 12:
+#line 112 "plural.y"
+{
+	    if ((yyval.exp = new_exp (var, NULL)) == NULL)
+	      YYABORT
+	  ;
+    break;}
+case 13:
+#line 117 "plural.y"
+{
+	    if ((yyval.exp = new_exp (num, NULL)) == NULL)
+	      YYABORT;
+	    yyval.exp->val.num = yyvsp[0].num
+	  ;
+    break;}
+case 14:
+#line 123 "plural.y"
+{
+	    yyval.exp = yyvsp[-1].exp
+	  ;
+    break;}
+}
+   /* the action file gets copied in in place of this dollarsign */
+#line 543 "/usr/share/bison.simple"
+
+  yyvsp -= yylen;
+  yyssp -= yylen;
+#ifdef YYLSP_NEEDED
+  yylsp -= yylen;
+#endif
+
+#if YYDEBUG != 0
+  if (yydebug)
+    {
+      short *ssp1 = yyss - 1;
+      fprintf (stderr, "state stack now");
+      while (ssp1 != yyssp)
+	fprintf (stderr, " %d", *++ssp1);
+      fprintf (stderr, "\n");
+    }
+#endif
+
+  *++yyvsp = yyval;
+
+#ifdef YYLSP_NEEDED
+  yylsp++;
+  if (yylen == 0)
+    {
+      yylsp->first_line = yylloc.first_line;
+      yylsp->first_column = yylloc.first_column;
+      yylsp->last_line = (yylsp-1)->last_line;
+      yylsp->last_column = (yylsp-1)->last_column;
+      yylsp->text = 0;
+    }
+  else
+    {
+      yylsp->last_line = (yylsp+yylen-1)->last_line;
+      yylsp->last_column = (yylsp+yylen-1)->last_column;
+    }
+#endif
+
+  /* Now "shift" the result of the reduction.
+     Determine what state that goes to,
+     based on the state we popped back to
+     and the rule number reduced by.  */
+
+  yyn = yyr1[yyn];
+
+  yystate = yypgoto[yyn - YYNTBASE] + *yyssp;
+  if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+    yystate = yytable[yystate];
+  else
+    yystate = yydefgoto[yyn - YYNTBASE];
+
+  goto yynewstate;
+
+yyerrlab:   /* here on detecting error */
+
+  if (! yyerrstatus)
+    /* If not already recovering from an error, report this error.  */
+    {
+      ++yynerrs;
+
+#ifdef YYERROR_VERBOSE
+      yyn = yypact[yystate];
+
+      if (yyn > YYFLAG && yyn < YYLAST)
+	{
+	  int size = 0;
+	  char *msg;
+	  int x, count;
+
+	  count = 0;
+	  /* Start X at -yyn if nec to avoid negative indexes in yycheck.  */
+	  for (x = (yyn < 0 ? -yyn : 0);
+	       x < (sizeof(yytname) / sizeof(char *)); x++)
+	    if (yycheck[x + yyn] == x)
+	      size += strlen(yytname[x]) + 15, count++;
+	  msg = (char *) malloc(size + 15);
+	  if (msg != 0)
+	    {
+	      strcpy(msg, "parse error");
+
+	      if (count < 5)
+		{
+		  count = 0;
+		  for (x = (yyn < 0 ? -yyn : 0);
+		       x < (sizeof(yytname) / sizeof(char *)); x++)
+		    if (yycheck[x + yyn] == x)
+		      {
+			strcat(msg, count == 0 ? ", expecting `" : " or `");
+			strcat(msg, yytname[x]);
+			strcat(msg, "'");
+			count++;
+		      }
+		}
+	      yyerror(msg);
+	      free(msg);
+	    }
+	  else
+	    yyerror ("parse error; also virtual memory exceeded");
+	}
+      else
+#endif /* YYERROR_VERBOSE */
+	yyerror("parse error");
+    }
+
+  goto yyerrlab1;
+yyerrlab1:   /* here on error raised explicitly by an action */
+
+  if (yyerrstatus == 3)
+    {
+      /* if just tried and failed to reuse lookahead token after an error, discard it.  */
+
+      /* return failure if at end of input */
+      if (yychar == YYEOF)
+	YYABORT;
+
+#if YYDEBUG != 0
+      if (yydebug)
+	fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]);
+#endif
+
+      yychar = YYEMPTY;
+    }
+
+  /* Else will try to reuse lookahead token
+     after shifting the error token.  */
+
+  yyerrstatus = 3;		/* Each real token shifted decrements this */
+
+  goto yyerrhandle;
+
+yyerrdefault:  /* current state does not do anything special for the error token. */
+
+#if 0
+  /* This is wrong; only states that explicitly want error tokens
+     should shift them.  */
+  yyn = yydefact[yystate];  /* If its default is to accept any token, ok.  Otherwise pop it.*/
+  if (yyn) goto yydefault;
+#endif
+
+yyerrpop:   /* pop the current state because it cannot handle the error token */
+
+  if (yyssp == yyss) YYABORT;
+  yyvsp--;
+  yystate = *--yyssp;
+#ifdef YYLSP_NEEDED
+  yylsp--;
+#endif
+
+#if YYDEBUG != 0
+  if (yydebug)
+    {
+      short *ssp1 = yyss - 1;
+      fprintf (stderr, "Error: state stack now");
+      while (ssp1 != yyssp)
+	fprintf (stderr, " %d", *++ssp1);
+      fprintf (stderr, "\n");
+    }
+#endif
+
+yyerrhandle:
+
+  yyn = yypact[yystate];
+  if (yyn == YYFLAG)
+    goto yyerrdefault;
+
+  yyn += YYTERROR;
+  if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR)
+    goto yyerrdefault;
+
+  yyn = yytable[yyn];
+  if (yyn < 0)
+    {
+      if (yyn == YYFLAG)
+	goto yyerrpop;
+      yyn = -yyn;
+      goto yyreduce;
+    }
+  else if (yyn == 0)
+    goto yyerrpop;
+
+  if (yyn == YYFINAL)
+    YYACCEPT;
+
+#if YYDEBUG != 0
+  if (yydebug)
+    fprintf(stderr, "Shifting error token, ");
+#endif
+
+  *++yyvsp = yylval;
+#ifdef YYLSP_NEEDED
+  *++yylsp = yylloc;
+#endif
+
+  yystate = yyn;
+  goto yynewstate;
+
+ yyacceptlab:
+  /* YYACCEPT comes here.  */
+  if (yyfree_stacks)
+    {
+      free (yyss);
+      free (yyvs);
+#ifdef YYLSP_NEEDED
+      free (yyls);
+#endif
+    }
+  return 0;
+
+ yyabortlab:
+  /* YYABORT comes here.  */
+  if (yyfree_stacks)
+    {
+      free (yyss);
+      free (yyvs);
+#ifdef YYLSP_NEEDED
+      free (yyls);
+#endif
+    }
+  return 1;
+}
+#line 128 "plural.y"
+
+
+static struct expression *
+new_exp (enum operator op, ...)
+{
+  struct expression *newp = (struct expression *) malloc (sizeof (*newp));
+  va_list va;
+  struct expression *next;
+
+  va_start (va, op);
+
+  if (newp == NULL)
+    while ((next = va_arg (va, struct expression *)) != NULL)
+      __gettext_free_exp (next);
+  else
+    {
+      newp->operation = op;
+      next = va_arg (va, struct expression *);
+      if (next != NULL)
+	{
+	  newp->val.args3.bexp = next;
+	  next = va_arg (va, struct expression *);
+	  if (next != NULL)
+	    {
+	      newp->val.args3.tbranch = next;
+	      next = va_arg (va, struct expression *);
+	      if (next != NULL)
+		newp->val.args3.fbranch = next;
+	    }
+	}
+    }
+
+  va_end (va);
+
+  return newp;
+}
+
+void
+internal_function
+__gettext_free_exp (struct expression *exp)
+{
+  if (exp == NULL)
+    return;
+
+  /* Handle the recursive case.  */
+  switch (exp->operation)
+    {
+    case qmop:
+      __gettext_free_exp (exp->val.args3.fbranch);
+      /* FALLTHROUGH */
+
+    case mult:
+    case divide:
+    case module:
+    case plus:
+    case minus:
+    case equal:
+    case not_equal:
+    case land:
+    case lor:
+      __gettext_free_exp (exp->val.args2.right);
+      __gettext_free_exp (exp->val.args2.left);
+      break;
+
+    default:
+      break;
+    }
+
+  free (exp);
+}
+
+
+static int
+yylex (YYSTYPE *lval, const char **pexp)
+{
+  const char *exp = *pexp;
+  int result;
+
+  while (1)
+    {
+      if (exp[0] == '\\' && exp[1] == '\n')
+	{
+	  exp += 2;
+	  continue;
+	}
+      if (exp[0] != '\0' && exp[0] != ' ' && exp[0] != '\t')
+	break;
+
+      ++exp;
+    }
+
+  result = *exp++;
+  switch (result)
+    {
+    case '0' ... '9':
+      {
+	unsigned long int n = exp[-1] - '0';
+	while (exp[0] >= '0' && exp[0] <= '9')
+	  {
+	    n *= 10;
+	    n += exp[0] - '0';
+	    ++exp;
+	  }
+	lval->num = n;
+	result = NUMBER;
+      }
+      break;
+
+    case '=':
+    case '!':
+      if (exp[0] == '=')
+	++exp;
+      else
+	result = YYERRCODE;
+      break;
+
+    case '&':
+    case '|':
+      if (exp[0] == result)
+	++exp;
+      else
+	result = YYERRCODE;
+      break;
+
+    case 'n':
+    case '*':
+    case '/':
+    case '%':
+    case '+':
+    case '-':
+    case '?':
+    case ':':
+    case '(':
+    case ')':
+      /* Nothing, just return the character.  */
+      break;
+
+    case '\n':
+    case '\0':
+      /* Be safe and let the user call this function again.  */
+      --exp;
+      result = YYEOF;
+      break;
+
+    default:
+      result = YYERRCODE;
+#if YYDEBUG != 0
+      --exp;
+#endif
+      break;
+    }
+
+  *pexp = exp;
+
+  return result;
+}
+
+
+static void
+yyerror (const char *str)
+{
+  /* Do nothing.  We don't print error messages here.  */
+}
diff --git a/intl/plural.y b/intl/plural.y
new file mode 100644
index 0000000000..00b6fccb4f
--- /dev/null
+++ b/intl/plural.y
@@ -0,0 +1,290 @@
+%{
+/* Expression parsing for plural form selection.
+   Copyright (C) 2000 Free Software Foundation, Inc.
+   Written by Ulrich Drepper <drepper@cygnus.com>, 2000.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include "gettext.h"
+#include "gettextP.h"
+
+#define YYLEX_PARAM	&((struct parse_args *) arg)->cp
+#define YYPARSE_PARAM	arg
+%}
+%pure_parser
+%expect 10
+
+%union {
+  unsigned long int num;
+  struct expression *exp;
+}
+
+%{
+/* Prototypes for local functions.  */
+static struct expression *new_exp (enum operator op, ...);
+static int yylex (YYSTYPE *lval, const char **pexp);
+static void yyerror (const char *str);
+%}
+
+%left '?'
+%left '|'
+%left '&'
+%left '=', '!'
+%left '+', '-'
+%left '*', '/', '%'
+%token <num> NUMBER
+%type <exp> exp
+
+%%
+
+start:	  exp
+	  {
+	    ((struct parse_args *) arg)->res = $1;
+	  }
+	;
+
+exp:	  exp '?' exp ':' exp
+	  {
+	    if (($$ = new_exp (qmop, $1, $3, $5, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '|' exp
+	  {
+	    if (($$ = new_exp (lor, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '&' exp
+	  {
+	    if (($$ = new_exp (land, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '=' exp
+	  {
+	    if (($$ = new_exp (equal, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '!' exp
+	  {
+	    if (($$ = new_exp (not_equal, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '+' exp
+	  {
+	    if (($$ = new_exp (plus, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '-' exp
+	  {
+	    if (($$ = new_exp (minus, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '*' exp
+	  {
+	    if (($$ = new_exp (mult, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '/' exp
+	  {
+	    if (($$ = new_exp (divide, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| exp '%' exp
+	  {
+	    if (($$ = new_exp (module, $1, $3, NULL)) == NULL)
+	      YYABORT
+	  }
+	| 'n'
+	  {
+	    if (($$ = new_exp (var, NULL)) == NULL)
+	      YYABORT
+	  }
+	| NUMBER
+	  {
+	    if (($$ = new_exp (num, NULL)) == NULL)
+	      YYABORT;
+	    $$->val.num = $1
+	  }
+	| '(' exp ')'
+	  {
+	    $$ = $2
+	  }
+	;
+
+%%
+
+static struct expression *
+new_exp (enum operator op, ...)
+{
+  struct expression *newp = (struct expression *) malloc (sizeof (*newp));
+  va_list va;
+  struct expression *next;
+
+  va_start (va, op);
+
+  if (newp == NULL)
+    while ((next = va_arg (va, struct expression *)) != NULL)
+      __gettext_free_exp (next);
+  else
+    {
+      newp->operation = op;
+      next = va_arg (va, struct expression *);
+      if (next != NULL)
+	{
+	  newp->val.args3.bexp = next;
+	  next = va_arg (va, struct expression *);
+	  if (next != NULL)
+	    {
+	      newp->val.args3.tbranch = next;
+	      next = va_arg (va, struct expression *);
+	      if (next != NULL)
+		newp->val.args3.fbranch = next;
+	    }
+	}
+    }
+
+  va_end (va);
+
+  return newp;
+}
+
+void
+internal_function
+__gettext_free_exp (struct expression *exp)
+{
+  if (exp == NULL)
+    return;
+
+  /* Handle the recursive case.  */
+  switch (exp->operation)
+    {
+    case qmop:
+      __gettext_free_exp (exp->val.args3.fbranch);
+      /* FALLTHROUGH */
+
+    case mult:
+    case divide:
+    case module:
+    case plus:
+    case minus:
+    case equal:
+    case not_equal:
+    case land:
+    case lor:
+      __gettext_free_exp (exp->val.args2.right);
+      __gettext_free_exp (exp->val.args2.left);
+      break;
+
+    default:
+      break;
+    }
+
+  free (exp);
+}
+
+
+static int
+yylex (YYSTYPE *lval, const char **pexp)
+{
+  const char *exp = *pexp;
+  int result;
+
+  while (1)
+    {
+      if (exp[0] == '\\' && exp[1] == '\n')
+	{
+	  exp += 2;
+	  continue;
+	}
+      if (exp[0] != '\0' && exp[0] != ' ' && exp[0] != '\t')
+	break;
+
+      ++exp;
+    }
+
+  result = *exp++;
+  switch (result)
+    {
+    case '0' ... '9':
+      {
+	unsigned long int n = exp[-1] - '0';
+	while (exp[0] >= '0' && exp[0] <= '9')
+	  {
+	    n *= 10;
+	    n += exp[0] - '0';
+	    ++exp;
+	  }
+	lval->num = n;
+	result = NUMBER;
+      }
+      break;
+
+    case '=':
+    case '!':
+      if (exp[0] == '=')
+	++exp;
+      else
+	result = YYERRCODE;
+      break;
+
+    case '&':
+    case '|':
+      if (exp[0] == result)
+	++exp;
+      else
+	result = YYERRCODE;
+      break;
+
+    case 'n':
+    case '*':
+    case '/':
+    case '%':
+    case '+':
+    case '-':
+    case '?':
+    case ':':
+    case '(':
+    case ')':
+      /* Nothing, just return the character.  */
+      break;
+
+    case '\n':
+    case '\0':
+      /* Be safe and let the user call this function again.  */
+      --exp;
+      result = YYEOF;
+      break;
+
+    default:
+      result = YYERRCODE;
+#if YYDEBUG != 0
+      --exp;
+#endif
+      break;
+    }
+
+  *pexp = exp;
+
+  return result;
+}
+
+
+static void
+yyerror (const char *str)
+{
+  /* Do nothing.  We don't print error messages here.  */
+}
diff --git a/intl/po2test.sed b/intl/po2test.sed
new file mode 100644
index 0000000000..d1c2f3d086
--- /dev/null
+++ b/intl/po2test.sed
@@ -0,0 +1,70 @@
+# po2test.sed - Convert Uniforum style .po file to C code for testing.
+# Copyright (C) 2000 Free Software Foundation, Inc.
+# Ulrich Drepper <drepper@cygnus.com>, 2000.
+#
+# 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 2, 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+#
+# We copy the original message as a comment into the .msg file.  But enclose
+# them with INPUT ( ).
+#
+/^msgid/ {
+  s/msgid[ 	]*"\(.*\)"/INPUT ("\1")/
+# Clear flag from last substitution.
+  tb
+# Append the next line.
+  :b
+  N
+# Look whether second part is a continuation line.
+  s/\(.*\)")\(\n\)"\(.*\)"/\1\\\2\3")/
+# Yes, then branch.
+  ta
+  P
+  D
+# Note that `D' includes a jump to the start!!
+# We found a continuation line.  But before printing insert '\'.
+  :a
+  s/\(.*\)")\(\n.*\)/\1\\\2/
+  P
+# We cannot use the sed command `D' here
+  s/.*\n\(.*\)/\1/
+  tb
+}
+#
+# Copy the translations as well and enclose them with OUTPUT ( ).
+#
+/^msgstr/ {
+  s/msgstr[ 	]*"\(.*\)"/OUTPUT ("\1")/
+# Clear flag from last substitution.
+  tb
+# Append the next line.
+  :b
+  N
+# Look whether second part is a continuation line.
+  s/\(.*\)")\(\n\)"\(.*\)"/\1\\\2\3")/
+# Yes, then branch.
+  ta
+  P
+  D
+# Note that `D' includes a jump to the start!!
+# We found a continuation line.  But before printing insert '\'.
+  :a
+  s/\(.*\)")\(\n.*\)/\1\\\2/
+  P
+# We cannot use the sed command `D' here
+  s/.*\n\(.*\)/\1/
+  tb
+}
+d
diff --git a/intl/tst-gettext.c b/intl/tst-gettext.c
new file mode 100644
index 0000000000..9ce11903aa
--- /dev/null
+++ b/intl/tst-gettext.c
@@ -0,0 +1,322 @@
+/* Test of the gettext functions.
+   Copyright (C) 2000 Free Software Foundation, Inc.
+   Contributed by Ulrich Drepper <drepper@cygnus.com>, 2000.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <libintl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+const struct
+{
+  const char *msgid;
+  const char *msgstr;
+} msgs[] =
+{
+#define INPUT(Str) { Str,
+#define OUTPUT(Str) Str },
+#include TESTSTRS_H
+};
+
+const char *catname[] =
+{
+  [LC_MESSAGES] = "LC_MESSAGES",
+  [LC_TIME] = "LC_TIME",
+  [LC_NUMERIC] = "LC_NUMERIC"
+};
+
+
+static int positive_gettext_test (void);
+static int negative_gettext_test (void);
+static int positive_dgettext_test (const char *domain);
+static int positive_dcgettext_test (const char *domain, int category);
+static int negative_dcgettext_test (const char *domain, int category);
+
+
+int
+main (int argc, char *argv[])
+{
+  int result = 0;
+
+  /* This is the place where the .mo files are placed.  */
+  if (argc > 1)
+    {
+      bindtextdomain ("existing-domain", argv[1]);
+      bindtextdomain ("existing-time-domain", argv[1]);
+      bindtextdomain ("non-existing-domain", argv[1]);
+    }
+
+  /* The locale the catalog is created for is "existing-category".  Now
+     set the various variables in question to this value and run the
+     test.  */
+  setenv ("LANGUAGE", "existing-locale", 1);
+  setenv ("LC_ALL", "non-existing-locale", 1);
+  setenv ("LC_MESSAGES", "non-existing-locale", 1);
+  setenv ("LANG", "non-existing-locale", 1);
+  /* This is the name of the existing domain with a catalog for the
+     LC_MESSAGES category.  */
+  textdomain ("existing-domain");
+  puts ("test `gettext' with LANGUAGE set");
+  if (positive_gettext_test () != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  /* This is the name of a non-existing domain with a catalog for the
+     LC_MESSAGES category.  We leave this value set for the `dgettext'
+     and `dcgettext' tests.  */
+  textdomain ("non-existing-domain");
+  puts ("test `gettext' with LANGUAGE set");
+  if (negative_gettext_test () != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  puts ("test `dgettext' with LANGUAGE set");
+  if (positive_dgettext_test ("existing-domain") != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+
+  /* Now the same tests with LC_ALL deciding.  */
+  unsetenv ("LANGUAGE");
+  setenv ("LC_ALL", "existing-locale", 1);
+  puts ("test `gettext' with LC_ALL set");
+  /* This is the name of the existing domain with a catalog for the
+     LC_MESSAGES category.  */
+  textdomain ("existing-domain");
+  if (positive_gettext_test () != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  /* This is the name of a non-existing domain with a catalog for the
+     LC_MESSAGES category.  We leave this value set for the `dgettext'
+     and `dcgettext' tests.  */
+  textdomain ("non-existing-domain");
+  puts ("test `gettext' with LANGUAGE set");
+  if (negative_gettext_test () != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  puts ("test `dgettext' with LANGUAGE set");
+  if (positive_dgettext_test ("existing-domain") != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+
+  /* Now the same tests with LC_MESSAGES deciding.  */
+  unsetenv ("LC_ALL");
+  setenv ("LC_MESSAGES", "existing-locale", 1);
+  setenv ("LC_TIME", "existing-locale", 1);
+  setenv ("LC_NUMERIC", "non-existing-locale", 1);
+  puts ("test `gettext' with LC_ALL set");
+  /* This is the name of the existing domain with a catalog for the
+     LC_MESSAGES category.  */
+  textdomain ("existing-domain");
+  if (positive_gettext_test () != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  /* This is the name of a non-existing domain with a catalog for the
+     LC_MESSAGES category.  We leave this value set for the `dgettext'
+     and `dcgettext' tests.  */
+  textdomain ("non-existing-domain");
+  puts ("test `gettext' with LANGUAGE set");
+  if (negative_gettext_test () != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  puts ("test `dgettext' with LANGUAGE set");
+  if (positive_dgettext_test ("existing-domain") != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  puts ("test `dcgettext' with LANGUAGE set (LC_MESSAGES)");
+  if (positive_dcgettext_test ("existing-domain", LC_MESSAGES) != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  /* Try a different category.  For this we also switch the domain.  */
+  puts ("test `dcgettext' with LANGUAGE set (LC_TIME)");
+  if (positive_dcgettext_test ("existing-time-domain", LC_TIME) != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  /* This time use a category for which there is no catalog.  */
+  puts ("test `dcgettext' with LANGUAGE set (LC_NUMERIC)");
+  if (negative_dcgettext_test ("existing-domain", LC_NUMERIC) != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+
+  /* Now the same tests with LANG deciding.  */
+  unsetenv ("LC_MESSAGES");
+  setenv ("LANG", "existing-locale", 1);
+  /* This is the name of the existing domain with a catalog for the
+     LC_MESSAGES category.  */
+  textdomain ("existing-domain");
+  puts ("test `gettext' with LC_ALL set");
+  if (positive_gettext_test () != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  /* This is the name of a non-existing domain with a catalog for the
+     LC_MESSAGES category.  We leave this value set for the `dgettext'
+     and `dcgettext' tests.  */
+  textdomain ("non-existing-domain");
+  puts ("test `gettext' with LANGUAGE set");
+  if (negative_gettext_test () != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+  puts ("test `dgettext' with LANGUAGE set");
+  if (positive_dgettext_test ("existing-domain") != 0)
+    {
+      puts ("FAILED");
+      result = 1;
+    }
+
+  return result;
+}
+
+
+static int
+positive_gettext_test (void)
+{
+  size_t cnt;
+  int result = 0;
+
+  for (cnt = 0; cnt < sizeof (msgs) / sizeof (msgs[0]); ++cnt)
+    {
+      const char *found = gettext (msgs[cnt].msgid);
+
+      if (found == NULL || strcmp (found, msgs[cnt].msgstr) != 0)
+	{
+	  /* Oops, shouldn't happen.  */
+	  printf ("  gettext (\"%s\") failed, returned \"%s\"\n",
+		  msgs[cnt].msgid, found);
+	  result = 1;
+	}
+    }
+
+  return result;
+}
+
+
+static int
+negative_gettext_test (void)
+{
+  size_t cnt;
+  int result = 0;
+
+  for (cnt = 0; cnt < sizeof (msgs) / sizeof (msgs[0]); ++cnt)
+    {
+      const char *found = gettext (msgs[cnt].msgid);
+
+      if (found != msgs[cnt].msgid)
+	{
+	  /* Oops, shouldn't happen.  */
+	  printf ("  gettext (\"%s\") failed\n", msgs[cnt].msgid);
+	  result = 1;
+	}
+    }
+
+  return result;
+}
+
+
+static int
+positive_dgettext_test (const char *domain)
+{
+  size_t cnt;
+  int result = 0;
+
+  for (cnt = 0; cnt < sizeof (msgs) / sizeof (msgs[0]); ++cnt)
+    {
+      const char *found = dgettext (domain, msgs[cnt].msgid);
+
+      if (found == NULL || strcmp (found, msgs[cnt].msgstr) != 0)
+	{
+	  /* Oops, shouldn't happen.  */
+	  printf ("  dgettext (\"%s\", \"%s\") failed, returned \"%s\"\n",
+		  domain, msgs[cnt].msgid, found);
+	  result = 1;
+	}
+    }
+
+  return result;
+}
+
+
+static int
+positive_dcgettext_test (const char *domain, int category)
+{
+  size_t cnt;
+  int result = 0;
+
+  for (cnt = 0; cnt < sizeof (msgs) / sizeof (msgs[0]); ++cnt)
+    {
+      const char *found = dcgettext (domain, msgs[cnt].msgid, category);
+
+      if (found == NULL || strcmp (found, msgs[cnt].msgstr) != 0)
+	{
+	  /* Oops, shouldn't happen.  */
+	  printf ("  dcgettext (\"%s\", \"%s\", %s) failed, returned \"%s\"\n",
+		  domain, msgs[cnt].msgid, catname[category], found);
+	  result = 1;
+	}
+    }
+
+  return result;
+}
+
+
+static int
+negative_dcgettext_test (const char *domain, int category)
+{
+  size_t cnt;
+  int result = 0;
+
+  for (cnt = 0; cnt < sizeof (msgs) / sizeof (msgs[0]); ++cnt)
+    {
+      const char *found = dcgettext (domain, msgs[cnt].msgid, category);
+
+      if (found != msgs[cnt].msgid)
+	{
+	  /* Oops, shouldn't happen.  */
+	  printf ("  dcgettext (\"%s\", \"%s\", %s) failed\n",
+		  domain, msgs[cnt].msgid, catname[category]);
+	  result = 1;
+	}
+    }
+
+  return result;
+}
diff --git a/intl/tst-gettext.sh b/intl/tst-gettext.sh
new file mode 100755
index 0000000000..db3653ad73
--- /dev/null
+++ b/intl/tst-gettext.sh
@@ -0,0 +1,41 @@
+#! /bin/sh
+# Test of gettext functions.
+# Copyright (C) 2000 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 Library General Public License as
+# published by the Free Software Foundation; either version 2 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
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with the GNU C Library; see the file COPYING.LIB.  If
+# not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+common_objpfx=$1
+objpfx=$2
+
+# Generate the test data.
+test -d ${objpfx}domaindir || mkdir ${objpfx}domaindir
+# Create the locale directories.
+test -d ${objpfx}domaindir/existing-locale || mkdir ${objpfx}domaindir/existing-locale
+test -d ${objpfx}domaindir/existing-locale/LC_MESSAGES || mkdir ${objpfx}domaindir/existing-locale/LC_MESSAGES
+test -d ${objpfx}domaindir/existing-locale/LC_TIME || mkdir ${objpfx}domaindir/existing-locale/LC_TIME
+
+# Populate them.
+msgfmt -o ${objpfx}domaindir/existing-locale/LC_MESSAGES/existing-domain.mo \
+       ../po/de.po
+msgfmt -o ${objpfx}domaindir/existing-locale/LC_TIME/existing-time-domain.mo \
+       ../po/de.po
+
+# Now run the test.
+${common_objpfx}elf/ld.so --library-path $common_objpfx \
+${objpfx}tst-gettext > ${objpfx}tst-gettext.out ${objpfx}domaindir
+
+exit $?