]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Make translations of strings with <inttypes.h> macros work with musl libc.
authorBruno Haible <bruno@clisp.org>
Thu, 15 Jun 2023 07:47:15 +0000 (09:47 +0200)
committerBruno Haible <bruno@clisp.org>
Thu, 15 Jun 2023 14:15:45 +0000 (16:15 +0200)
* gettext-tools/src/write-mo.h (no_redundancy): New declaration.
* gettext-tools/src/write-mo.c (SIZEOF): New macro.
(no_redundancy): New variable.
(struct sysdep_instantiation_rule): New type.
(useful_instantiation_rules): New variable.
(concat_prefix_cs, get_sysdep_segment_value): New functions.
(write_table): Add a second pass, that instantiates system dependent string
pairs.
* gettext-tools/src/msgfmt.c (long_options): Add --no-redundancy.
(main): Handle the --no-redundancy option.
(usage): Document the --no-redundancy option.
* gettext-tools/doc/msgfmt.texi: Document the --no-redundancy option.
* gettext-tools/tests/msgfmt-12: Test the msgfmt result with and without
--no-redundancy.
* gettext-tools/tests/msgunfmt-2: Pass option --no-redundancy to msgfmt.
* NEWS: Mention the change.
* gettext-tools/tests/format-c-3: Include the redundant instantiations in the
expected result of msgunfmt.
* gettext-tools/tests/format-c-4: Likewise.

NEWS
gettext-tools/doc/msgfmt.texi
gettext-tools/src/msgfmt.c
gettext-tools/src/write-mo.c
gettext-tools/src/write-mo.h
gettext-tools/tests/format-c-3
gettext-tools/tests/format-c-4
gettext-tools/tests/msgfmt-12
gettext-tools/tests/msgunfmt-2

diff --git a/NEWS b/NEWS
index d5bc0ba43c2f5d04fba6634c8a94956d998c92f4..d59be3eca75836ce8cdffc13284343355e4347d0 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -41,6 +41,11 @@ Version 0.21.2 - June 2023
     To this effect, the msgfmt program now converts the messages to UTF-8
     encoding before storing them in a MO file.  You can prevent this by
     using the msgfmt --no-convert option.
+  - On systems with musl libc, the *gettext() functions in libc now work
+    with MO files generated from PO files with ISO C 99 <inttypes.h> format
+    string directive macros.  To this effect, the msgfmt program pre-expands
+    strings with such macros.  You can prevent this by using the msgfmt
+    --no-redundancy option.
 
 * xgettext:
   - The xgettext option '--sorted-output' is now deprecated.
index 5c637ca46abddeb6733b84373ab86f0e1d4f1352..aa4d9055d3cc1384ce8ca04f75a1f02a8b3fb0e4 100644 (file)
@@ -382,6 +382,18 @@ converted to UTF-8 encoding before being stored in a MO file; this helps
 avoiding conversions at run time, since nowadays most locales use the
 UTF-8 encoding.
 
+@item --no-redundancy
+@opindex --no-redundancy@r{, @code{msgfmt} option}
+Don't pre-expand ISO C 99 <inttypes.h> format string directive macros.
+By default, messages that are marked as @code{c-format} and contain
+ISO C 99 <inttypes.h> format string directive macros are pre-expanded
+for selected platforms, and these redundant expansions are stored in the
+MO file.  These redundant expansions make the translations of these
+messages work with the @code{gettext} implementation in the @code{libc}
+of that platform, without requiring GNU @code{gettext}'s @code{libintl}.
+The platforms that benefit from this pre-expansion are those with the
+musl libc.
+
 @item -a @var{number}
 @itemx --alignment=@var{number}
 @opindex -a@r{, @code{msgfmt} option}
index d93ba525025a9da47d13ede738f4d83831f346ea..56a1e497f8d2f8341c74c157d1078677bea2c9af 100644 (file)
@@ -197,6 +197,7 @@ static const struct option long_options[] =
   { "locale", required_argument, NULL, 'l' },
   { "no-convert", no_argument, NULL, CHAR_MAX + 17 },
   { "no-hash", no_argument, NULL, CHAR_MAX + 6 },
+  { "no-redundancy", no_argument, NULL, CHAR_MAX + 18 },
   { "output-file", required_argument, NULL, 'o' },
   { "properties-input", no_argument, NULL, 'P' },
   { "qt", no_argument, NULL, CHAR_MAX + 9 },
@@ -426,6 +427,9 @@ main (int argc, char *argv[])
       case CHAR_MAX + 17: /* --no-convert */
         no_convert_to_utf8 = true;
         break;
+      case CHAR_MAX + 18: /* --no-redundancy */
+        no_redundancy = true;
+        break;
       default:
         usage (EXIT_FAILURE);
         break;
@@ -1051,6 +1055,9 @@ Output details:\n"));
       printf (_("\
       --no-convert            don't convert the messages to UTF-8 encoding\n"));
       printf (_("\
+      --no-redundancy         don't pre-expand ISO C 99 <inttypes.h>\n\
+                                format string directive macros\n"));
+      printf (_("\
   -a, --alignment=NUMBER      align strings to NUMBER bytes (default: %d)\n"), DEFAULT_OUTPUT_ALIGNMENT);
       printf (_("\
       --endianness=BYTEORDER  write out 32-bit numbers in the given byte order\n\
index d94f1863c6468dd5c72a129ab28a3695e969b304..1809549a8c435ac069deddc2af3e7898fb37a5b4 100644 (file)
 # endif /* GNU CC2  */
 #endif /* roundup  */
 
+#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
+
 
 /* True if no conversion to UTF-8 is desired.  */
 bool no_convert_to_utf8;
 
+/* True if the redundant storage of instantiations of system-dependent strings
+   shall be avoided.  */
+bool no_redundancy;
+
 /* Alignment of strings in resulting .mo file.  */
 size_t alignment;
 
@@ -147,12 +153,225 @@ struct pre_sysdep_message
   size_t id_plural_len;
 };
 
+
+/* Instantiating system dependent strings.
+   This is a technique to make messages with system dependent strings work with
+   musl libc's gettext() implementation, even though this implementation does
+   not process the system dependent strings.  Namely, we store the actual
+   runtime expansion of the string for this platform — we call this an
+   "instantiation" of the string — in the table of static string pairs.
+   This is redundant, but allows the same MO files to be used on musl libc
+   (without GNU libintl) as on other platforms (with GNU libc or with GNU
+   libintl).
+
+   A survey of the PO files on translationproject.org shows that
+     * Less than 9% of the messages of any PO file are system dependent strings.
+       Therefore the increase of the size of the MO file is small.
+     * None of these PO files uses the 'I' format string flag.
+
+   There are few possible <inttypes.h> flavours.  Each such flavour gives rise
+   to an instantation rule.  We ran this test program on various platforms:
+   =============================================================================
+   #include <inttypes.h>
+   #include <stdio.h>
+   #include <string.h>
+   int main ()
+   {
+     printf ("%s\n", PRIuMAX);
+     printf ("%s\n", PRIdMAX);
+     printf ("%s  %s  %s  %s\n", PRIu8, PRIu16, PRIu32, PRIu64);
+     printf ("%s  %s  %s  %s\n", PRId8, PRId16, PRId32, PRId64);
+     printf ("%s  %s  %s  %s\n", PRIuLEAST8, PRIuLEAST16, PRIuLEAST32, PRIuLEAST64);
+     printf ("%s  %s  %s  %s\n", PRIdLEAST8, PRIdLEAST16, PRIdLEAST32, PRIdLEAST64);
+     printf ("%s  %s  %s  %s\n", PRIuFAST8, PRIuFAST16, PRIuFAST32, PRIuFAST64);
+     printf ("%s  %s  %s  %s\n", PRIdFAST8, PRIdFAST16, PRIdFAST32, PRIdFAST64);
+     printf ("%s\n", PRIuPTR);
+     printf ("%s\n", PRIdPTR);
+     printf ("Summary:\n");
+     printf ("  MAX 8               LEAST8          FAST8           PTR\n");
+     printf ("  |   |   |   |   |   |   |   |   |   |   |   |   |   |\n");
+     printf ("| %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s %-3.*s |\n",
+             (int) strlen (PRIuMAX) - 1, PRIuMAX,
+             (int) strlen (PRIu8) - 1, PRIu8,
+             (int) strlen (PRIu16) - 1, PRIu16,
+             (int) strlen (PRIu32) - 1, PRIu32,
+             (int) strlen (PRIu64) - 1, PRIu64,
+             (int) strlen (PRIuLEAST8) - 1, PRIuLEAST8,
+             (int) strlen (PRIuLEAST16) - 1, PRIuLEAST16,
+             (int) strlen (PRIuLEAST32) - 1, PRIuLEAST32,
+             (int) strlen (PRIuLEAST64) - 1, PRIuLEAST64,
+             (int) strlen (PRIuFAST8) - 1, PRIuFAST8,
+             (int) strlen (PRIuFAST16) - 1, PRIuFAST16,
+             (int) strlen (PRIuFAST32) - 1, PRIuFAST32,
+             (int) strlen (PRIuFAST64) - 1, PRIuFAST64,
+             (int) strlen (PRIuPTR) - 1, PRIuPTR);
+     return 0;
+   }
+   =============================================================================
+   and found the following table.
+
+   <inttypes.h>   MAX 8               LEAST8          FAST8           PTR
+     flavour      |   |   |   |   |   |   |   |   |   |   |   |   |   |
+   ------------ -----------------------------------------------------------
+        0       | ll              ll              ll              ll      |
+        1       | l               l               l               l   l   |
+        2       | ll              ll              ll              ll  l   |
+        3       | ll  hh  h       ll  hh  h       ll  hh  h       ll  l   |
+        4       | ll  hh  h       ll  hh  h       ll  hh          ll      |
+        5       | ll  hh  h       ll  hh  h       ll  hh          ll  ll  |
+        6       | l               l               l       l   l   l   l   |
+        7       | l   hh  h       l   hh  h       l   hh  h       l   l   |
+        8       | l   hh  h       l   hh  h       l   hh          l   l   |
+        9       | l   hh  h       l   hh  h       l   hh  l   l   l   l   |
+       10       | j   hh  h       ll  hh  h       ll  hh  h       ll  l   |
+       11       | j               ll              ll              ll      |
+       12       | j               l               l               l   l   |
+       13       | j               ll              ll              ll  l   |
+       14       | I64             I64             I64             I64     |
+       15       | I64             I64             I64             I64 I64 |
+
+   Which <inttypes.h> flavour for which platforms?
+
+   <inttypes.h>
+     flavour     Platforms
+   ------------  ---------------------------------------------------------------
+        0        glibc 32-bit, musl 32-bit, NetBSD 32-bit
+        1        musl 64-bit, NetBSD 64-bit, Haiku 64-bit
+        2        Haiku 32-bit
+        3        AIX 32-bit
+        4        Solaris 32-bit, Cygwin 32-bit, MSVC 32-bit
+        5        MSVC 64-bit
+        6        glibc 64-bit
+        7        AIX 64-bit
+        8        Solaris 64-bit
+        9        Cygwin 64-bit
+       10        macOS 32-bit and 64-bit
+       11        FreeBSD 32-bit, Android 32-bit
+       12        FreeBSD 64-bit
+       13        OpenBSD 32-bit and 64-bit
+       14        mingw 32-bit
+       15        mingw 64-bit
+ */
+struct sysdep_instantiation_rule
+{
+  const char *prefix_for_MAX;
+  const char *prefix_for_8;      /* also for LEAST8 and FAST8 */
+  const char *prefix_for_16;     /* also for LEAST16 */
+  const char *prefix_for_64;     /* also for LEAST64 and FAST64 */
+  const char *prefix_for_FAST16;
+  const char *prefix_for_FAST32;
+  const char *prefix_for_PTR;
+};
+const struct sysdep_instantiation_rule useful_instantiation_rules[] =
+{
+  /*  0 */ { "ll",  "",   "",  "ll",  "",  "",  ""    },
+  /*  1 */ { "l",   "",   "",  "l",   "",  "",  "l"   },
+#if 0 /* These instantiation rules are not useful.  They would just be bloat.  */
+  /*  2 */ { "ll",  "",   "",  "ll",  "",  "",  "l"   },
+  /*  3 */ { "ll",  "hh", "h", "ll",  "h", "",  "l"   },
+  /*  4 */ { "ll",  "hh", "h", "ll",  "",  "",  ""    },
+  /*  5 */ { "ll",  "hh", "h", "ll",  "",  "",  "ll"  },
+  /*  6 */ { "l",   "",   "",  "l",   "l", "l", "l"   },
+  /*  7 */ { "l",   "hh", "h", "l",   "h", "",  "l"   },
+  /*  8 */ { "l",   "hh", "h", "l",   "",  "",  "l"   },
+  /*  9 */ { "l",   "hh", "h", "l",   "l", "l", "l"   },
+  /* 10 */ { "j",   "hh", "h", "ll",  "h", "",  "l"   },
+  /* 11 */ { "j",   "",   "",  "ll",  "",  "",  ""    },
+  /* 12 */ { "j",   "",   "",  "l",   "",  "",  "l"   },
+  /* 13 */ { "j",   "",   "",  "ll",  "",  "",  "l"   },
+  /* 14 */ { "I64", "",   "",  "I64", "",  "",  ""    },
+  /* 15 */ { "I64", "",   "",  "I64", "",  "",  "I64" },
+#endif
+};
+
+/* Concatenate a prefix and a conversion specifier.  */
+static const char *
+concat_prefix_cs (const char *prefix, char conversion)
+{
+  char *result = XNMALLOC (strlen (prefix) + 2, char);
+  {
+    char *p = result;
+    p = stpcpy (p, prefix);
+    *p++ = conversion;
+    *p = '\0';
+  }
+  return result;
+}
+
+/* Expand a system dependent string segment for a specific instantation.
+   Return NULL if unsupported.  */
+static const char *
+get_sysdep_segment_value (struct pre_sysdep_segment segment,
+                          const struct sysdep_instantiation_rule *instrule)
+{
+  const char *name = segment.pointer;
+  size_t len = segment.length;
+
+  /* Test for an ISO C 99 section 7.8.1 format string directive.
+     Syntax:
+     P R I { d | i | o | u | x | X }
+     { { | LEAST | FAST } { 8 | 16 | 32 | 64 } | MAX | PTR }  */
+  if (len >= 3 && name[0] == 'P' && name[1] == 'R' && name[2] == 'I')
+    {
+      if (len >= 4
+          && (name[3] == 'd' || name[3] == 'i' || name[3] == 'o'
+              || name[3] == 'u' || name[3] == 'x' || name[3] == 'X'))
+        {
+          if (len == 5 && name[4] == '8')
+            return concat_prefix_cs (instrule->prefix_for_8, name[3]);
+          if (len == 6 && name[4] == '1' && name[5] == '6')
+            return concat_prefix_cs (instrule->prefix_for_16, name[3]);
+          if (len == 6 && name[4] == '3' && name[5] == '2')
+            return concat_prefix_cs ("", name[3]);
+          if (len == 6 && name[4] == '6' && name[5] == '4')
+            return concat_prefix_cs (instrule->prefix_for_64, name[3]);
+          if (len >= 9 && name[4] == 'L' && name[5] == 'E' && name[6] == 'A'
+              && name[7] == 'S' && name[8] == 'T')
+            {
+              if (len == 10 && name[9] == '8')
+                return concat_prefix_cs (instrule->prefix_for_8, name[3]);
+              if (len == 11 && name[9] == '1' && name[10] == '6')
+                return concat_prefix_cs (instrule->prefix_for_16, name[3]);
+              if (len == 11 && name[9] == '3' && name[10] == '2')
+                return concat_prefix_cs ("", name[3]);
+              if (len == 11 && name[9] == '6' && name[10] == '4')
+                return concat_prefix_cs (instrule->prefix_for_64, name[3]);
+            }
+          if (len >= 8 && name[4] == 'F' && name[5] == 'A' && name[6] == 'S'
+              && name[7] == 'T')
+            {
+              if (len == 9 && name[8] == '8')
+                return concat_prefix_cs (instrule->prefix_for_8, name[3]);
+              if (len == 10 && name[8] == '1' && name[9] == '6')
+                return concat_prefix_cs (instrule->prefix_for_FAST16, name[3]);
+              if (len == 10 && name[8] == '3' && name[9] == '2')
+                return concat_prefix_cs (instrule->prefix_for_FAST32, name[3]);
+              if (len == 10 && name[8] == '6' && name[9] == '4')
+                return concat_prefix_cs (instrule->prefix_for_64, name[3]);
+            }
+          if (len == 7 && name[4] == 'M' && name[5] == 'A' && name[6] == 'X')
+            return concat_prefix_cs (instrule->prefix_for_MAX, name[3]);
+          if (len == 7 && name[4] == 'P' && name[5] == 'T' && name[6] == 'R')
+            return concat_prefix_cs (instrule->prefix_for_PTR, name[3]);
+        }
+    }
+  /* Note: We cannot support the 'I' format directive flag here.  Because
+       - If we expand the 'I' to "I", the expansion will not work on non-glibc
+         systems (whose *printf() functions don't understand this flag).
+       - If we expand the 'I' to "", the expansion will override the expansion
+         produced at run time (see loadmsgcat.c) and will not produce the
+         locale-specific outdigits as expected.  */
+  return NULL;
+}
+
+
 /* Write the message list to the given open file.  */
 static void
 write_table (FILE *output_file, message_list_ty *mlp)
 {
   char **msgctid_arr;
   size_t nstrings;
+  size_t msg_arr_allocated;
   struct pre_message *msg_arr;
   size_t n_sysdep_strings;
   struct pre_sysdep_message *sysdep_msg_arr;
@@ -177,7 +396,8 @@ write_table (FILE *output_file, message_list_ty *mlp)
      strings.  */
   msgctid_arr = XNMALLOC (mlp->nitems, char *);
   nstrings = 0;
-  msg_arr = XNMALLOC (mlp->nitems, struct pre_message);
+  msg_arr_allocated = mlp->nitems;
+  msg_arr = XNMALLOC (msg_arr_allocated, struct pre_message);
   n_sysdep_strings = 0;
   sysdep_msg_arr = XNMALLOC (mlp->nitems, struct pre_sysdep_message);
   n_sysdep_segments = 0;
@@ -379,6 +599,132 @@ write_table (FILE *output_file, message_list_ty *mlp)
       }
   }
 
+  /* Second pass: Instantiate the system dependent string pairs and add them to
+     the table of static string pairs.  */
+  if (!no_redundancy && n_sysdep_strings > 0)
+    {
+      /* Create a temporary hash table of msg_arr[*].str[M_ID], to guarantee
+         fast lookups.  */
+      hash_table static_msgids;
+
+      hash_init (&static_msgids, 10);
+      {
+        size_t i;
+
+        for (i = 0; i < nstrings; i++)
+          hash_insert_entry (&static_msgids,
+                             msg_arr[i].str[M_ID].pointer,
+                             msg_arr[i].str[M_ID].length,
+                             NULL);
+      }
+
+      size_t ss;
+
+      for (ss = 0; ss < n_sysdep_strings; ss++)
+        {
+          size_t u;
+
+          for (u = 0; u < SIZEOF (useful_instantiation_rules); u++)
+            {
+              const struct sysdep_instantiation_rule *instrule =
+                &useful_instantiation_rules[u];
+              bool supported = true;
+              struct pre_string expansion[2];
+              size_t m;
+
+              for (m = 0; m < 2; m++)
+                {
+                  struct pre_sysdep_string *pre = sysdep_msg_arr[ss].str[m];
+                  unsigned int segmentcount = pre->segmentcount;
+                  size_t expansion_length;
+                  char *expansion_pointer;
+                  unsigned int i;
+
+                  /* Compute the length of the expansion.  */
+                  expansion_length = 0;
+                  i = 0;
+                  do
+                    {
+                      expansion_length += pre->segments[i].segsize;
+
+                      size_t r = pre->segments[i].sysdepref;
+                      if (r == SEGMENTS_END)
+                        break;
+                      const char *segment_expansion =
+                        get_sysdep_segment_value (sysdep_segments[r], instrule);
+                      if (segment_expansion == NULL)
+                        {
+                          supported = false;
+                          break;
+                        }
+                      expansion_length += strlen (segment_expansion);
+                    }
+                  while (i++ < segmentcount);
+                  if (!supported)
+                    break;
+
+                  /* Compute the expansion.  */
+                  expansion_pointer = (char *) xmalloc (expansion_length);
+                  {
+                    char *p = expansion_pointer;
+
+                    i = 0;
+                    do
+                      {
+                        memcpy (p, pre->segments[i].segptr, pre->segments[i].segsize);
+                        p += pre->segments[i].segsize;
+
+                        size_t r = pre->segments[i].sysdepref;
+                        if (r == SEGMENTS_END)
+                          break;
+                        const char *segment_expansion =
+                          get_sysdep_segment_value (sysdep_segments[r], instrule);
+                        if (segment_expansion == NULL)
+                          /* Should already have set supported = false above.  */
+                          abort ();
+                        memcpy (p, segment_expansion, strlen (segment_expansion));
+                        p += strlen (segment_expansion);
+                      }
+                    while (i++ < segmentcount);
+                    if (p != expansion_pointer + expansion_length)
+                      /* The two loops are not in sync.  */
+                      abort ();
+                  }
+
+                  expansion[m].length = expansion_length;
+                  expansion[m].pointer = expansion_pointer;
+                }
+
+              if (supported)
+                {
+                  /* Don't overwrite existing static string pairs.  */
+                  if (hash_insert_entry (&static_msgids,
+                                         expansion[M_ID].pointer,
+                                         expansion[M_ID].length,
+                                         NULL)
+                      != NULL)
+                    {
+                      if (nstrings == msg_arr_allocated)
+                        {
+                          msg_arr_allocated = 2 * msg_arr_allocated + 1;
+                          msg_arr =
+                            (struct pre_message *)
+                            xreallocarray (msg_arr, msg_arr_allocated,
+                                           sizeof (struct pre_message));
+                        }
+                      msg_arr[nstrings].str[M_ID] = expansion[M_ID];
+                      msg_arr[nstrings].str[M_STR] = expansion[M_STR];
+                      msg_arr[nstrings].id_plural = sysdep_msg_arr[ss].id_plural;
+                      msg_arr[nstrings].id_plural_len = sysdep_msg_arr[ss].id_plural_len;
+                      nstrings++;
+                    }
+                }
+            }
+        }
+
+      hash_destroy (&static_msgids);
+    }
+
   /* Sort the table according to original string.  */
   if (nstrings > 0)
     qsort (msg_arr, nstrings, sizeof (struct pre_message), compare_id);
@@ -419,7 +765,7 @@ write_table (FILE *output_file, message_list_ty *mlp)
                 Sorting and Searching, 1973, Addison Wesley]  */
   if (!omit_hash_table)
     {
-      hash_tab_size = next_prime ((mlp->nitems * 4) / 3);
+      hash_tab_size = next_prime ((nstrings * 4) / 3);
       /* Ensure M > 2.  */
       if (hash_tab_size <= 2)
         hash_tab_size = 3;
@@ -427,8 +773,7 @@ write_table (FILE *output_file, message_list_ty *mlp)
   else
     hash_tab_size = 0;
 
-
-  /* Second pass: Fill the structure describing the header.  At the same time,
+  /* Third pass: Fill the structure describing the header.  At the same time,
      compute the sizes and offsets of the non-string parts of the file.  */
 
   /* Magic number.  */
@@ -497,7 +842,7 @@ write_table (FILE *output_file, message_list_ty *mlp)
   end_offset = offset;
 
 
-  /* Third pass: Write the non-string parts of the file.  At the same time,
+  /* Fourth pass: Write the non-string parts of the file.  At the same time,
      compute the offsets of each string, including the proper alignment.  */
 
   /* Write the header out.  */
@@ -734,7 +1079,7 @@ write_table (FILE *output_file, message_list_ty *mlp)
   free (orig_tab);
 
 
-  /* Fourth pass: Write the strings.  */
+  /* Fifth pass: Write the strings.  */
 
   offset = end_offset;
 
index 6e0e72b8dc2ff7f46ded43e47757c80a246769ea..7312062c86696d68c73aa3c3f11492d1705b7785 100644 (file)
 /* True if no conversion to UTF-8 is desired.  */
 extern bool no_convert_to_utf8;
 
+/* True if the redundant storage of instantiations of system-dependent strings
+   shall be avoided.  */
+extern bool no_redundancy;
+
 /* Alignment of strings in resulting .mo file.  */
 extern size_t alignment;
 
index ce60c578aa804321d8557ccae57204a55b01ec0e..415e3bf22fe074e8b87b8c7bb1f31113c1393ed3 100755 (executable)
@@ -39,8 +39,20 @@ ${MSGFMT} -o fc3-dir/fr/LC_MESSAGES/fc3.mo fc3-de.po
 ${MSGUNFMT} -o fc3-de.po.tmp fc3-dir/fr/LC_MESSAGES/fc3.mo || Exit 1
 LC_ALL=C tr -d '\r' < fc3-de.po.tmp > fc3-de.po.un || Exit 1
 
+cat <<EOF > fc3-de.po.red
+msgid "father of %d children"
+msgstr "Vater von %d Kindern"
+
+msgid "the president"
+msgstr "der Vorsitzende"
+
+#, c-format
+msgid "father of %<PRId8> children"
+msgstr "Vater von %<PRId8> Kindern"
+EOF
+
 : ${DIFF=diff}
-${DIFF} fc3-de.po fc3-de.po.un || Exit 1
+${DIFF} fc3-de.po.red fc3-de.po.un || Exit 1
 
 if test -z "$USE_SYSTEM_LIBINTL"; then
   # In the tests/ dir: Rely on a fake setlocale, so that we can exercise
index 37f871fa5769acff3ed6b8b558eea1694309454c..7092ee0c9e3c887478c91f68117fe2758505c7c8 100755 (executable)
@@ -43,8 +43,24 @@ ${MSGFMT} -o fc4-dir/fr/LC_MESSAGES/fc4.mo fc4-de.po
 ${MSGUNFMT} -o fc4-de.po.tmp fc4-dir/fr/LC_MESSAGES/fc4.mo || Exit 1
 LC_ALL=C tr -d '\r' < fc4-de.po.tmp > fc4-de.po.un || Exit 1
 
+cat <<EOF > fc4-de.po.red
+msgid "father of %d child"
+msgid_plural "father of %<PRId8> children"
+msgstr[0] "Vater eines Kindes"
+msgstr[1] "Vater von %d Kindern"
+
+msgid "the president"
+msgstr "der Vorsitzende"
+
+#, c-format
+msgid "father of %<PRId8> child"
+msgid_plural "father of %<PRId8> children"
+msgstr[0] "Vater eines Kindes"
+msgstr[1] "Vater von %<PRId8> Kindern"
+EOF
+
 : ${DIFF=diff}
-${DIFF} fc4-de.po fc4-de.po.un || Exit 1
+${DIFF} fc4-de.po.red fc4-de.po.un || Exit 1
 
 if test -z "$USE_SYSTEM_LIBINTL"; then
   # In the tests/ dir: Rely on a fake setlocale, so that we can exercise
index 95beb09060c9ec31d5badeba6f43da99aeed455e..f9bdd733e99abfdad997754a6c39ee42631309b6 100755 (executable)
@@ -19,6 +19,36 @@ msgid "<PRIXFAST16> errors"
 msgstr "<PRIXFAST16> Fehler"
 EOF
 
+# First, without redundancy.
+
+: ${MSGFMT=msgfmt}
+${MSGFMT} --no-convert --no-redundancy -o mf-12.mo mf-12.po || Exit 1
+
+: ${MSGUNFMT=msgunfmt}
+${MSGUNFMT} -o mf-12.tmp mf-12.mo || Exit 1
+LC_ALL=C tr -d '\r' < mf-12.tmp > mf-12.out || Exit 1
+
+cat <<\EOF > mf-12.ok
+msgid ""
+msgstr "Content-Type: text/plain; charset=ISO-8859-1\n"
+
+msgid "<PRIXFAST16> errors"
+msgstr "<PRIXFAST16> Fehler"
+
+#, c-format
+msgid "File size is: %<PRId64>"
+msgstr "Dateigröße ist: %<PRId64>"
+
+#, c-format
+msgid "File age is %10<PRIdMAX> microseconds"
+msgstr "Datei ist %10<PRIdMAX> Mikrosekunden alt."
+EOF
+
+: ${DIFF=diff}
+${DIFF} mf-12.ok mf-12.out || Exit 1
+
+# Now, with redundancy.
+
 : ${MSGFMT=msgfmt}
 ${MSGFMT} --no-convert -o mf-12.mo mf-12.po || Exit 1
 
@@ -33,6 +63,18 @@ msgstr "Content-Type: text/plain; charset=ISO-8859-1\n"
 msgid "<PRIXFAST16> errors"
 msgstr "<PRIXFAST16> Fehler"
 
+msgid "File age is %10ld microseconds"
+msgstr "Datei ist %10ld Mikrosekunden alt."
+
+msgid "File age is %10lld microseconds"
+msgstr "Datei ist %10lld Mikrosekunden alt."
+
+msgid "File size is: %ld"
+msgstr "Dateigröße ist: %ld"
+
+msgid "File size is: %lld"
+msgstr "Dateigröße ist: %lld"
+
 #, c-format
 msgid "File size is: %<PRId64>"
 msgstr "Dateigröße ist: %<PRId64>"
@@ -43,7 +85,6 @@ msgstr "Datei ist %10<PRIdMAX> Mikrosekunden alt."
 EOF
 
 : ${DIFF=diff}
-${DIFF} mf-12.ok mf-12.out
-result=$?
+${DIFF} mf-12.ok mf-12.out || Exit 1
 
-exit $result
+Exit 0
index 496e9314b447164d024996a027638c3bdcc8b1f1..68abd91fc272a2073879b2ca0e3a2a6889cdb1d9 100755 (executable)
@@ -13,7 +13,7 @@ msgstr "schneide bei %<PRIuMAX> Bytes in Ausgabedatei %s ab"
 EOF
 
 : ${MSGFMT=msgfmt}
-${MSGFMT} -o mu-2.mo mu-2.in || Exit 1
+${MSGFMT} --no-redundancy -o mu-2.mo mu-2.in || Exit 1
 
 : ${MSGUNFMT=msgunfmt}
 ${MSGUNFMT} -o mu-2.tmp mu-2.mo || Exit 1