1 /* Initializes a new PO file.
2    Copyright (C) 2001-2020 Free Software Foundation, Inc.
3    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22 #include <alloca.h>
23 
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <getopt.h>
27 #include <limits.h>
28 #include <locale.h>
29 #include <stdint.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 #include <sys/types.h>
35 
36 #if HAVE_PWD_H
37 # include <pwd.h>
38 #endif
39 
40 #include <unistd.h>
41 
42 #if HAVE_DIRENT_H
43 # include <dirent.h>
44 #endif
45 
46 #if HAVE_DIRENT_H
47 # define HAVE_DIR 1
48 #else
49 # define HAVE_DIR 0
50 #endif
51 
52 #include <textstyle.h>
53 
54 /* Get BINDIR.  */
55 #include "configmake.h"
56 
57 #include "noreturn.h"
58 #include "closeout.h"
59 #include "error.h"
60 #include "error-progname.h"
61 #include "progname.h"
62 #include "relocatable.h"
63 #include "basename-lgpl.h"
64 #include "c-strstr.h"
65 #include "c-strcase.h"
66 #include "message.h"
67 #include "read-catalog.h"
68 #include "read-po.h"
69 #include "read-properties.h"
70 #include "read-stringtable.h"
71 #include "write-catalog.h"
72 #include "write-po.h"
73 #include "write-properties.h"
74 #include "write-stringtable.h"
75 #include "po-charset.h"
76 #include "localcharset.h"
77 #include "localename.h"
78 #include "po-time.h"
79 #include "plural-table.h"
80 #include "lang-table.h"
81 #include "xalloc.h"
82 #include "xmalloca.h"
83 #include "concat-filename.h"
84 #include "xerror.h"
85 #include "xvasprintf.h"
86 #include "msgl-english.h"
87 #include "plural-count.h"
88 #include "spawn-pipe.h"
89 #include "wait-process.h"
90 #include "xsetenv.h"
91 #include "str-list.h"
92 #include "propername.h"
93 #include "gettext.h"
94 
95 #define _(str) gettext (str)
96 #define N_(str) (str)
97 
98 /* Get F_OK.  It is lacking from <fcntl.h> on Woe32.  */
99 #ifndef F_OK
100 # define F_OK 0
101 #endif
102 
103 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
104 
105 extern const char * _nl_expand_alias (const char *name);
106 
107 /* Locale name.  */
108 static const char *locale;
109 
110 /* Language (ISO-639 code) and optional territory (ISO-3166 code).  */
111 static const char *catalogname;
112 
113 /* Language (ISO-639 code).  */
114 static const char *language;
115 
116 /* If true, the user is not considered to be the translator.  */
117 static bool no_translator;
118 
119 /* Long options.  */
120 static const struct option long_options[] =
121 {
122   { "color", optional_argument, NULL, CHAR_MAX + 5 },
123   { "help", no_argument, NULL, 'h' },
124   { "input", required_argument, NULL, 'i' },
125   { "locale", required_argument, NULL, 'l' },
126   { "no-translator", no_argument, NULL, CHAR_MAX + 1 },
127   { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
128   { "output-file", required_argument, NULL, 'o' },
129   { "properties-input", no_argument, NULL, 'P' },
130   { "properties-output", no_argument, NULL, 'p' },
131   { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 },
132   { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 },
133   { "style", required_argument, NULL, CHAR_MAX + 6 },
134   { "version", no_argument, NULL, 'V' },
135   { "width", required_argument, NULL, 'w' },
136   { NULL, 0, NULL, 0 }
137 };
138 
139 /* Forward declaration of local functions.  */
140 _GL_NORETURN_FUNC static void usage (int status);
141 static const char *find_pot (void);
142 static const char *catalogname_for_locale (const char *locale);
143 static const char *language_of_locale (const char *locale);
144 static char *get_field (const char *header, const char *field);
145 static msgdomain_list_ty *fill_header (msgdomain_list_ty *mdlp);
146 static msgdomain_list_ty *update_msgstr_plurals (msgdomain_list_ty *mdlp);
147 
148 
149 int
main(int argc,char ** argv)150 main (int argc, char **argv)
151 {
152   int opt;
153   bool do_help;
154   bool do_version;
155   char *output_file;
156   const char *input_file;
157   msgdomain_list_ty *result;
158   catalog_input_format_ty input_syntax = &input_format_po;
159   catalog_output_format_ty output_syntax = &output_format_po;
160 
161   /* Set program name for messages.  */
162   set_program_name (argv[0]);
163   error_print_progname = maybe_print_progname;
164 
165   /* Set locale via LC_ALL.  */
166   setlocale (LC_ALL, "");
167 
168   /* Set the text message domain.  */
169   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
170   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
171   textdomain (PACKAGE);
172 
173   /* Ensure that write errors on stdout are detected.  */
174   atexit (close_stdout);
175 
176   /* Set default values for variables.  */
177   do_help = false;
178   do_version = false;
179   output_file = NULL;
180   input_file = NULL;
181   locale = NULL;
182 
183   while ((opt = getopt_long (argc, argv, "hi:l:o:pPVw:", long_options, NULL))
184          != EOF)
185     switch (opt)
186       {
187       case '\0':                /* Long option.  */
188         break;
189 
190       case 'h':
191         do_help = true;
192         break;
193 
194       case 'i':
195         if (input_file != NULL)
196           {
197             error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
198             usage (EXIT_FAILURE);
199           }
200         input_file = optarg;
201         break;
202 
203       case 'l':
204         locale = optarg;
205         break;
206 
207       case 'o':
208         output_file = optarg;
209         break;
210 
211       case 'p':
212         output_syntax = &output_format_properties;
213         break;
214 
215       case 'P':
216         input_syntax = &input_format_properties;
217         break;
218 
219       case 'V':
220         do_version = true;
221         break;
222 
223       case 'w':
224         {
225           int value;
226           char *endp;
227           value = strtol (optarg, &endp, 10);
228           if (endp != optarg)
229             message_page_width_set (value);
230         }
231         break;
232 
233       case CHAR_MAX + 1:
234         no_translator = true;
235         break;
236 
237       case CHAR_MAX + 2: /* --no-wrap */
238         message_page_width_ignore ();
239         break;
240 
241       case CHAR_MAX + 3: /* --stringtable-input */
242         input_syntax = &input_format_stringtable;
243         break;
244 
245       case CHAR_MAX + 4: /* --stringtable-output */
246         output_syntax = &output_format_stringtable;
247         break;
248 
249       case CHAR_MAX + 5: /* --color */
250         if (handle_color_option (optarg) || color_test_mode)
251           usage (EXIT_FAILURE);
252         break;
253 
254       case CHAR_MAX + 6: /* --style */
255         handle_style_option (optarg);
256         break;
257 
258       default:
259         usage (EXIT_FAILURE);
260         break;
261       }
262 
263   /* Version information is requested.  */
264   if (do_version)
265     {
266       printf ("%s (GNU %s) %s\n", last_component (program_name),
267               PACKAGE, VERSION);
268       /* xgettext: no-wrap */
269       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
270 License GPLv3+: GNU GPL version 3 or later <%s>\n\
271 This is free software: you are free to change and redistribute it.\n\
272 There is NO WARRANTY, to the extent permitted by law.\n\
273 "),
274               "2001-2020", "https://gnu.org/licenses/gpl.html");
275       printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
276       exit (EXIT_SUCCESS);
277     }
278 
279   /* Help is requested.  */
280   if (do_help)
281     usage (EXIT_SUCCESS);
282 
283   /* Test for extraneous arguments.  */
284   if (optind != argc)
285     error (EXIT_FAILURE, 0, _("too many arguments"));
286 
287   /* Search for the input file.  */
288   if (input_file == NULL)
289     input_file = find_pot ();
290 
291   /* Determine target locale.  */
292   if (locale == NULL)
293     {
294       locale = gl_locale_name (LC_MESSAGES, "LC_MESSAGES");
295       if (strcmp (locale, "C") == 0)
296         {
297           const char *doc_url =
298             "https://www.gnu.org/software/gettext/manual/html_node/Setting-the-POSIX-Locale.html";
299           multiline_error (xstrdup (""),
300                            xasprintf (_("\
301 You are in a language indifferent environment.  Please set\n\
302 your LANG environment variable, as described in\n\
303 <%s>.\n\
304 This is necessary so you can test your translations.\n"),
305                                       doc_url));
306           exit (EXIT_FAILURE);
307         }
308     }
309   {
310     const char *alias = _nl_expand_alias (locale);
311     if (alias != NULL)
312       locale = alias;
313   }
314   catalogname = catalogname_for_locale (locale);
315   language = language_of_locale (locale);
316 
317   /* Default output file name is CATALOGNAME.po.  */
318   if (output_file == NULL)
319     {
320       output_file = xasprintf ("%s.po", catalogname);
321 
322       /* But don't overwrite existing PO files.  */
323       if (access (output_file, F_OK) == 0)
324         {
325           multiline_error (xstrdup (""),
326                            xasprintf (_("\
327 Output file %s already exists.\n\
328 Please specify the locale through the --locale option or\n\
329 the output .po file through the --output-file option.\n"),
330                                       output_file));
331           exit (EXIT_FAILURE);
332         }
333     }
334 
335   /* Read input file.  */
336   result = read_catalog_file (input_file, input_syntax);
337 
338 #if defined _WIN32 || defined __CYGWIN__
339   /* The function fill_header invokes, directly or indirectly, some programs
340      that are installed in ${libdir}/gettext:
341        - hostname, invoked indirectly through 'user-email'.
342        - urlget, invoked indirectly through 'team-address'.
343        - cldr-plurals, invoked directly.
344      These programs depend on libintl.  In installations with shared libraries,
345      we need to guarantee that the programs find the DLL, which is installed
346      in ${bindir}, not in ${libdir}/gettext.  The preferred way to do so is to
347      extend $PATH, so that it contains ${bindir}.  */
348   {
349     const char *orig_path;
350     size_t orig_path_len;
351     char separator;
352     const char *bindir;
353     size_t bindir_len;
354     char *augmented_path;
355 
356     orig_path = getenv ("PATH");
357     if (orig_path == NULL)
358       orig_path = "";
359     orig_path_len = strlen (orig_path);
360 
361     #if defined __CYGWIN__
362     separator = ':';
363     #else /* native Windows */
364     separator = ';';
365     #endif
366 
367     bindir = BINDIR;
368     bindir_len = strlen (bindir);
369 
370     /* Concatenate bindir, separator, orig_path.  */
371     augmented_path = XNMALLOC (bindir_len + 1 + orig_path_len + 1, char);
372     memcpy (augmented_path, bindir, bindir_len);
373     augmented_path[bindir_len] = separator;
374     memcpy (augmented_path + bindir_len + 1, orig_path, orig_path_len + 1);
375 
376     xsetenv ("PATH", augmented_path, 1);
377   }
378 #endif
379 
380   /* Fill the header entry.  */
381   result = fill_header (result);
382 
383   /* Initialize translations.  */
384   if (strcmp (language, "en") == 0)
385     result = msgdomain_list_english (result);
386   else
387     result = update_msgstr_plurals (result);
388 
389   /* Write the modified message list out.  */
390   msgdomain_list_print (result, output_file, output_syntax, true, false);
391 
392   if (!no_translator)
393     fprintf (stderr, "\n");
394   fprintf (stderr, _("Created %s.\n"), output_file);
395 
396   exit (EXIT_SUCCESS);
397 }
398 
399 
400 /* Display usage information and exit.  */
401 static void
usage(int status)402 usage (int status)
403 {
404   if (status != EXIT_SUCCESS)
405     fprintf (stderr, _("Try '%s --help' for more information.\n"),
406              program_name);
407   else
408     {
409       printf (_("\
410 Usage: %s [OPTION]\n\
411 "), program_name);
412       printf ("\n");
413       /* xgettext: no-wrap */
414       printf (_("\
415 Creates a new PO file, initializing the meta information with values from the\n\
416 user's environment.\n\
417 "));
418       printf ("\n");
419       printf (_("\
420 Mandatory arguments to long options are mandatory for short options too.\n"));
421       printf ("\n");
422       printf (_("\
423 Input file location:\n"));
424       printf (_("\
425   -i, --input=INPUTFILE       input POT file\n"));
426       printf (_("\
427 If no input file is given, the current directory is searched for the POT file.\n\
428 If it is -, standard input is read.\n"));
429       printf ("\n");
430       printf (_("\
431 Output file location:\n"));
432       printf (_("\
433   -o, --output-file=FILE      write output to specified PO file\n"));
434       printf (_("\
435 If no output file is given, it depends on the --locale option or the user's\n\
436 locale setting.  If it is -, the results are written to standard output.\n"));
437       printf ("\n");
438       printf (_("\
439 Input file syntax:\n"));
440       printf (_("\
441   -P, --properties-input      input file is in Java .properties syntax\n"));
442       printf (_("\
443       --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
444       printf ("\n");
445       printf (_("\
446 Output details:\n"));
447       printf (_("\
448   -l, --locale=LL_CC[.ENCODING]  set target locale\n"));
449       printf (_("\
450       --no-translator         assume the PO file is automatically generated\n"));
451       printf (_("\
452       --color                 use colors and other text attributes always\n\
453       --color=WHEN            use colors and other text attributes if WHEN.\n\
454                               WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
455       printf (_("\
456       --style=STYLEFILE       specify CSS style rule file for --color\n"));
457       printf (_("\
458   -p, --properties-output     write out a Java .properties file\n"));
459       printf (_("\
460       --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
461       printf (_("\
462   -w, --width=NUMBER          set output page width\n"));
463       printf (_("\
464       --no-wrap               do not break long message lines, longer than\n\
465                               the output page width, into several lines\n"));
466       printf ("\n");
467       printf (_("\
468 Informative output:\n"));
469       printf (_("\
470   -h, --help                  display this help and exit\n"));
471       printf (_("\
472   -V, --version               output version information and exit\n"));
473       printf ("\n");
474       /* TRANSLATORS: The first placeholder is the web address of the Savannah
475          project of this package.  The second placeholder is the bug-reporting
476          email address for this package.  Please add _another line_ saying
477          "Report translation bugs to <...>\n" with the address for translation
478          bugs (typically your translation team's web or email address).  */
479       printf(_("\
480 Report bugs in the bug tracker at <%s>\n\
481 or by email to <%s>.\n"),
482              "https://savannah.gnu.org/projects/gettext",
483              "bug-gettext@gnu.org");
484     }
485 
486   exit (status);
487 }
488 
489 
490 /* Search for the POT file and return its name.  */
491 static const char *
find_pot()492 find_pot ()
493 {
494 #if HAVE_DIR
495   DIR *dirp;
496   char *found = NULL;
497 
498   dirp = opendir (".");
499   if (dirp != NULL)
500     {
501       for (;;)
502         {
503           struct dirent *dp;
504 
505           errno = 0;
506           dp = readdir (dirp);
507           if (dp != NULL)
508             {
509               const char *name = dp->d_name;
510               size_t namlen = strlen (name);
511 
512               if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0)
513                 {
514                   if (found == NULL)
515                     found = xstrdup (name);
516                   else
517                     {
518                       multiline_error (xstrdup (""),
519                                        xstrdup (_("\
520 Found more than one .pot file.\n\
521 Please specify the input .pot file through the --input option.\n")));
522                       usage (EXIT_FAILURE);
523                     }
524                 }
525             }
526           else if (errno != 0)
527             error (EXIT_FAILURE, errno, _("error reading current directory"));
528           else
529             break;
530         }
531       if (closedir (dirp))
532         error (EXIT_FAILURE, errno, _("error reading current directory"));
533 
534       if (found != NULL)
535         return found;
536     }
537 #endif
538 
539   multiline_error (xstrdup (""),
540                    xstrdup (_("\
541 Found no .pot file in the current directory.\n\
542 Please specify the input .pot file through the --input option.\n")));
543   usage (EXIT_FAILURE);
544   /* NOTREACHED */
545   return NULL;
546 }
547 
548 
549 /* Return the gettext catalog name corresponding to a locale.  If the locale
550    consists of a language and a territory, and the language is mainly spoken
551    in that territory, the territory is removed from the locale name.
552    For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de",
553    because the resulting catalog can be used as a default for all "de_XX",
554    such as "de_AT".  */
555 static const char *
catalogname_for_locale(const char * locale)556 catalogname_for_locale (const char *locale)
557 {
558   static const char *locales_with_principal_territory[] = {
559                 /* Language     Main territory */
560     "ace_ID",   /* Achinese     Indonesia */
561     "af_ZA",    /* Afrikaans    South Africa */
562     "ak_GH",    /* Akan         Ghana */
563     "am_ET",    /* Amharic      Ethiopia */
564     "an_ES",    /* Aragonese    Spain */
565     "ang_GB",   /* Old English  Britain */
566     "arn_CL",   /* Mapudungun   Chile */
567     "as_IN",    /* Assamese     India */
568     "ast_ES",   /* Asturian     Spain */
569     "av_RU",    /* Avaric       Russia */
570     "awa_IN",   /* Awadhi       India */
571     "az_AZ",    /* Azerbaijani  Azerbaijan */
572     "ban_ID",   /* Balinese     Indonesia */
573     "be_BY",    /* Belarusian   Belarus */
574     "bej_SD",   /* Beja         Sudan */
575     "bem_ZM",   /* Bemba        Zambia */
576     "bg_BG",    /* Bulgarian    Bulgaria */
577     "bho_IN",   /* Bhojpuri     India */
578     "bi_VU",    /* Bislama      Vanuatu */
579     "bik_PH",   /* Bikol        Philippines */
580     "bin_NG",   /* Bini         Nigeria */
581     "bm_ML",    /* Bambara      Mali */
582     "bn_IN",    /* Bengali      India */
583     "bo_CN",    /* Tibetan      China */
584     "br_FR",    /* Breton       France */
585     "bs_BA",    /* Bosnian      Bosnia */
586     "bug_ID",   /* Buginese     Indonesia */
587     "ca_ES",    /* Catalan      Spain */
588     "ce_RU",    /* Chechen      Russia */
589     "ceb_PH",   /* Cebuano      Philippines */
590     "co_FR",    /* Corsican     France */
591     "cr_CA",    /* Cree         Canada */
592     /* Don't put "crh_UZ" or "crh_UA" here.  That would be asking for fruitless
593        political discussion.  */
594     "cs_CZ",    /* Czech        Czech Republic */
595     "csb_PL",   /* Kashubian    Poland */
596     "cy_GB",    /* Welsh        Britain */
597     "da_DK",    /* Danish       Denmark */
598     "de_DE",    /* German       Germany */
599     "din_SD",   /* Dinka        Sudan */
600     "doi_IN",   /* Dogri        India */
601     "dsb_DE",   /* Lower Sorbian        Germany */
602     "dv_MV",    /* Divehi       Maldives */
603     "dz_BT",    /* Dzongkha     Bhutan */
604     "ee_GH",    /* Éwé          Ghana */
605     "el_GR",    /* Greek        Greece */
606     /* Don't put "en_GB" or "en_US" here.  That would be asking for fruitless
607        political discussion.  */
608     "es_ES",    /* Spanish      Spain */
609     "et_EE",    /* Estonian     Estonia */
610     "fa_IR",    /* Persian      Iran */
611     "fi_FI",    /* Finnish      Finland */
612     "fil_PH",   /* Filipino     Philippines */
613     "fj_FJ",    /* Fijian       Fiji */
614     "fo_FO",    /* Faroese      Faeroe Islands */
615     "fon_BJ",   /* Fon          Benin */
616     "fr_FR",    /* French       France */
617     "fur_IT",   /* Friulian     Italy */
618     "fy_NL",    /* Western Frisian      Netherlands */
619     "ga_IE",    /* Irish        Ireland */
620     "gd_GB",    /* Scottish Gaelic      Britain */
621     "gon_IN",   /* Gondi        India */
622     "gsw_CH",   /* Swiss German Switzerland */
623     "gu_IN",    /* Gujarati     India */
624     "he_IL",    /* Hebrew       Israel */
625     "hi_IN",    /* Hindi        India */
626     "hil_PH",   /* Hiligaynon   Philippines */
627     "hr_HR",    /* Croatian     Croatia */
628     "hsb_DE",   /* Upper Sorbian        Germany */
629     "ht_HT",    /* Haitian      Haiti */
630     "hu_HU",    /* Hungarian    Hungary */
631     "hy_AM",    /* Armenian     Armenia */
632     "id_ID",    /* Indonesian   Indonesia */
633     "ig_NG",    /* Igbo         Nigeria */
634     "ii_CN",    /* Sichuan Yi   China */
635     "ilo_PH",   /* Iloko        Philippines */
636     "is_IS",    /* Icelandic    Iceland */
637     "it_IT",    /* Italian      Italy */
638     "ja_JP",    /* Japanese     Japan */
639     "jab_NG",   /* Hyam         Nigeria */
640     "jv_ID",    /* Javanese     Indonesia */
641     "ka_GE",    /* Georgian     Georgia */
642     "kab_DZ",   /* Kabyle       Algeria */
643     "kaj_NG",   /* Jju          Nigeria */
644     "kam_KE",   /* Kamba        Kenya */
645     "kmb_AO",   /* Kimbundu     Angola */
646     "kcg_NG",   /* Tyap         Nigeria */
647     "kdm_NG",   /* Kagoma       Nigeria */
648     "kg_CD",    /* Kongo        Democratic Republic of Congo */
649     "kk_KZ",    /* Kazakh       Kazakhstan */
650     "kl_GL",    /* Kalaallisut  Greenland */
651     "km_KH",    /* Central Khmer        Cambodia */
652     "kn_IN",    /* Kannada      India */
653     "ko_KR",    /* Korean       Korea (South) */
654     "kok_IN",   /* Konkani      India */
655     "kr_NG",    /* Kanuri       Nigeria */
656     "kru_IN",   /* Kurukh       India */
657     "ky_KG",    /* Kyrgyz       Kyrgyzstan */
658     "lg_UG",    /* Ganda        Uganda */
659     "li_BE",    /* Limburgish   Belgium */
660     "lo_LA",    /* Laotian      Laos */
661     "lt_LT",    /* Lithuanian   Lithuania */
662     "lu_CD",    /* Luba-Katanga Democratic Republic of Congo */
663     "lua_CD",   /* Luba-Lulua   Democratic Republic of Congo */
664     "luo_KE",   /* Luo          Kenya */
665     "lv_LV",    /* Latvian      Latvia */
666     "mad_ID",   /* Madurese     Indonesia */
667     "mag_IN",   /* Magahi       India */
668     "mai_IN",   /* Maithili     India */
669     "mak_ID",   /* Makasar      Indonesia */
670     "man_ML",   /* Mandingo     Mali */
671     "men_SL",   /* Mende        Sierra Leone */
672     "mfe_MU",   /* Mauritian Creole     Mauritius */
673     "mg_MG",    /* Malagasy     Madagascar */
674     "mi_NZ",    /* Maori        New Zealand */
675     "min_ID",   /* Minangkabau  Indonesia */
676     "mk_MK",    /* Macedonian   North Macedonia */
677     "ml_IN",    /* Malayalam    India */
678     "mn_MN",    /* Mongolian    Mongolia */
679     "mni_IN",   /* Manipuri     India */
680     "mos_BF",   /* Mossi        Burkina Faso */
681     "mr_IN",    /* Marathi      India */
682     "ms_MY",    /* Malay        Malaysia */
683     "mt_MT",    /* Maltese      Malta */
684     "mwr_IN",   /* Marwari      India */
685     "my_MM",    /* Burmese      Myanmar */
686     "na_NR",    /* Nauru        Nauru */
687     "nah_MX",   /* Nahuatl      Mexico */
688     "nap_IT",   /* Neapolitan   Italy */
689     "nb_NO",    /* Norwegian Bokmål    Norway */
690     "nds_DE",   /* Low Saxon    Germany */
691     "ne_NP",    /* Nepali       Nepal */
692     "nl_NL",    /* Dutch        Netherlands */
693     "nn_NO",    /* Norwegian Nynorsk    Norway */
694     "no_NO",    /* Norwegian    Norway */
695     "nr_ZA",    /* South Ndebele        South Africa */
696     "nso_ZA",   /* Northern Sotho       South Africa */
697     "ny_MW",    /* Chichewa     Malawi */
698     "nym_TZ",   /* Nyamwezi     Tanzania */
699     "nyn_UG",   /* Nyankole     Uganda */
700     "oc_FR",    /* Occitan      France */
701     "oj_CA",    /* Ojibwa       Canada */
702     "or_IN",    /* Oriya        India */
703     "pa_IN",    /* Punjabi      India */
704     "pag_PH",   /* Pangasinan   Philippines */
705     "pam_PH",   /* Pampanga     Philippines */
706     "pap_AN",   /* Papiamento   Netherlands Antilles - this line can be removed in 2018 */
707     "pbb_CO",   /* Páez                Colombia */
708     "pl_PL",    /* Polish       Poland */
709     "ps_AF",    /* Pashto       Afghanistan */
710     "pt_PT",    /* Portuguese   Portugal */
711     "raj_IN",   /* Rajasthani   India */
712     "rm_CH",    /* Romansh      Switzerland */
713     "rn_BI",    /* Kirundi      Burundi */
714     "ro_RO",    /* Romanian     Romania */
715     "ru_RU",    /* Russian      Russia */
716     "rw_RW",    /* Kinyarwanda  Rwanda */
717     "sa_IN",    /* Sanskrit     India */
718     "sah_RU",   /* Yakut        Russia */
719     "sas_ID",   /* Sasak        Indonesia */
720     "sat_IN",   /* Santali      India */
721     "sc_IT",    /* Sardinian    Italy */
722     "scn_IT",   /* Sicilian     Italy */
723     "sg_CF",    /* Sango        Central African Republic */
724     "shn_MM",   /* Shan         Myanmar */
725     "si_LK",    /* Sinhala      Sri Lanka */
726     "sid_ET",   /* Sidamo       Ethiopia */
727     "sk_SK",    /* Slovak       Slovakia */
728     "sl_SI",    /* Slovenian    Slovenia */
729     "smn_FI",   /* Inari Sami   Finland */
730     "sms_FI",   /* Skolt Sami   Finland */
731     "so_SO",    /* Somali       Somalia */
732     "sq_AL",    /* Albanian     Albania */
733     "sr_RS",    /* Serbian      Serbia */
734     "srr_SN",   /* Serer        Senegal */
735     "suk_TZ",   /* Sukuma       Tanzania */
736     "sus_GN",   /* Susu         Guinea */
737     "sv_SE",    /* Swedish      Sweden */
738     "te_IN",    /* Telugu       India */
739     "tem_SL",   /* Timne        Sierra Leone */
740     "tet_ID",   /* Tetum        Indonesia */
741     "tg_TJ",    /* Tajik        Tajikistan */
742     "th_TH",    /* Thai         Thailand */
743     "tiv_NG",   /* Tiv          Nigeria */
744     "tk_TM",    /* Turkmen      Turkmenistan */
745     "tl_PH",    /* Tagalog      Philippines */
746     "to_TO",    /* Tonga        Tonga */
747     "tpi_PG",   /* Tok Pisin    Papua New Guinea */
748     "tr_TR",    /* Turkish      Turkey */
749     "tum_MW",   /* Tumbuka      Malawi */
750     "ug_CN",    /* Uighur       China */
751     "uk_UA",    /* Ukrainian    Ukraine */
752     "umb_AO",   /* Umbundu      Angola */
753     "ur_PK",    /* Urdu         Pakistan */
754     "uz_UZ",    /* Uzbek        Uzbekistan */
755     "ve_ZA",    /* Venda        South Africa */
756     "vi_VN",    /* Vietnamese   Vietnam */
757     "wa_BE",    /* Walloon      Belgium */
758     "wal_ET",   /* Walamo       Ethiopia */
759     "war_PH",   /* Waray        Philippines */
760     "wen_DE",   /* Sorbian      Germany */
761     "yao_MW",   /* Yao          Malawi */
762     "zap_MX"    /* Zapotec      Mexico */
763   };
764   const char *dot;
765   size_t i;
766 
767   /* Remove the ".codeset" part from the locale.  */
768   dot = strchr (locale, '.');
769   if (dot != NULL)
770     {
771       const char *codeset_end;
772       char *shorter_locale;
773 
774       codeset_end = strpbrk (dot + 1, "_@");
775       if (codeset_end == NULL)
776         codeset_end = dot + strlen (dot);
777 
778       shorter_locale = XNMALLOC (strlen (locale), char);
779       memcpy (shorter_locale, locale, dot - locale);
780       strcpy (shorter_locale + (dot - locale), codeset_end);
781       locale = shorter_locale;
782     }
783 
784   /* If the territory is the language's principal territory, drop it.  */
785   for (i = 0; i < SIZEOF (locales_with_principal_territory); i++)
786     if (strcmp (locale, locales_with_principal_territory[i]) == 0)
787       {
788         const char *language_end;
789         size_t len;
790         char *shorter_locale;
791 
792         language_end = strchr (locale, '_');
793         if (language_end == NULL)
794           abort ();
795 
796         len = language_end - locale;
797         shorter_locale = XNMALLOC (len + 1, char);
798         memcpy (shorter_locale, locale, len);
799         shorter_locale[len] = '\0';
800         locale = shorter_locale;
801         break;
802       }
803 
804   return locale;
805 }
806 
807 
808 /* Return the language of a locale.  */
809 static const char *
language_of_locale(const char * locale)810 language_of_locale (const char *locale)
811 {
812   const char *language_end;
813 
814   language_end = strpbrk (locale, "_.@");
815   if (language_end != NULL)
816     {
817       size_t len;
818       char *result;
819 
820       len = language_end - locale;
821       result = XNMALLOC (len + 1, char);
822       memcpy (result, locale, len);
823       result[len] = '\0';
824 
825       return result;
826     }
827   else
828     return locale;
829 }
830 
831 
832 /* Return the most likely desired charset for the PO file, as a portable
833    charset name.  */
834 static const char *
canonical_locale_charset()835 canonical_locale_charset ()
836 {
837   const char *tmp;
838   char *old_LC_ALL;
839   const char *charset;
840 
841   /* Save LC_ALL environment variable.  */
842 
843   tmp = getenv ("LC_ALL");
844   old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
845 
846   xsetenv ("LC_ALL", locale, 1);
847 
848   if (setlocale (LC_ALL, "") == NULL)
849     /* Nonexistent locale.  Use anything.  */
850     charset = "";
851   else
852     /* Get the locale's charset.  */
853     charset = locale_charset ();
854 
855   /* Restore LC_ALL environment variable.  */
856 
857   if (old_LC_ALL != NULL)
858     xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
859   else
860     unsetenv ("LC_ALL");
861 
862   setlocale (LC_ALL, "");
863 
864   /* Canonicalize it.  */
865   charset = po_charset_canonicalize (charset);
866   if (charset == NULL)
867     charset = po_charset_ascii;
868 
869   return charset;
870 }
871 
872 
873 /* Return the English name of the language.  */
874 static const char *
englishname_of_language()875 englishname_of_language ()
876 {
877   size_t i;
878 
879   for (i = 0; i < language_table_size; i++)
880     if (strcmp (language_table[i].code, language) == 0)
881       return language_table[i].english;
882 
883   return xasprintf ("Language %s", language);
884 }
885 
886 
887 /* Construct the value for the PACKAGE name.  */
888 static const char *
project_id(const char * header)889 project_id (const char *header)
890 {
891   const char *old_field;
892 
893   /* Return the first part of the Project-Id-Version field if present, assuming
894      it was already filled in by xgettext.  */
895   old_field = get_field (header, "Project-Id-Version");
896   if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0)
897     {
898       /* Remove the last word from old_field.  */
899       const char *last_space;
900 
901       last_space = strrchr (old_field, ' ');
902       if (last_space != NULL)
903         {
904           while (last_space > old_field && last_space[-1] == ' ')
905             last_space--;
906           if (last_space > old_field)
907             {
908               size_t package_len = last_space - old_field;
909               char *package = XNMALLOC (package_len + 1, char);
910               memcpy (package, old_field, package_len);
911               package[package_len] = '\0';
912 
913               return package;
914             }
915         }
916       /* It contains no version, just a package name.  */
917       return old_field;
918     }
919 
920   /* On native Windows, a Bourne shell is generally not available.
921      Avoid error messages such as
922      "msginit.exe: subprocess ... failed: No such file or directory"  */
923 #if !(defined _WIN32 && ! defined __CYGWIN__)
924   {
925     const char *gettextlibdir;
926     char *prog;
927     char *argv[3];
928     pid_t child;
929     int fd[1];
930     FILE *fp;
931     char *line;
932     size_t linesize;
933     size_t linelen;
934     int exitstatus;
935 
936     gettextlibdir = getenv ("GETTEXTLIBDIR_SRCDIR");
937     if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
938       gettextlibdir = relocate (LIBDIR "/gettext");
939 
940     prog = xconcatenated_filename (gettextlibdir, "project-id", NULL);
941 
942     /* Call the project-id shell script.  */
943     argv[0] = BOURNE_SHELL;
944     argv[1] = prog;
945     argv[2] = NULL;
946     child = create_pipe_in (prog, BOURNE_SHELL, argv, DEV_NULL, false, true,
947                             false, fd);
948     if (child == -1)
949       goto failed;
950 
951     /* Retrieve its result.  */
952     fp = fdopen (fd[0], "r");
953     if (fp == NULL)
954       {
955         error (0, errno, _("fdopen() failed"));
956         goto failed;
957       }
958 
959     line = NULL; linesize = 0;
960     linelen = getline (&line, &linesize, fp);
961     if (linelen == (size_t)(-1))
962       {
963         error (0, 0, _("%s subprocess I/O error"), prog);
964         fclose (fp);
965         goto failed;
966       }
967     if (linelen > 0 && line[linelen - 1] == '\n')
968       line[linelen - 1] = '\0';
969 
970     fclose (fp);
971 
972     /* Remove zombie process from process list, and retrieve exit status.  */
973     exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
974     if (exitstatus != 0)
975       {
976         error (0, 0, _("%s subprocess failed with exit code %d"),
977                prog, exitstatus);
978         goto failed;
979       }
980 
981     return line;
982   }
983 
984 failed:
985 #endif
986   return "PACKAGE";
987 }
988 
989 
990 /* Construct the value for the Project-Id-Version field.  */
991 static const char *
project_id_version(const char * header)992 project_id_version (const char *header)
993 {
994   const char *old_field;
995 
996   /* Return the old value if present, assuming it was already filled in by
997      xgettext.  */
998   old_field = get_field (header, "Project-Id-Version");
999   if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0)
1000     return old_field;
1001 
1002   /* On native Windows, a Bourne shell is generally not available.
1003      Avoid error messages such as
1004      "msginit.exe: subprocess ... failed: No such file or directory"  */
1005 #if !(defined _WIN32 && ! defined __CYGWIN__)
1006   {
1007     const char *gettextlibdir;
1008     char *prog;
1009     char *argv[4];
1010     pid_t child;
1011     int fd[1];
1012     FILE *fp;
1013     char *line;
1014     size_t linesize;
1015     size_t linelen;
1016     int exitstatus;
1017 
1018     gettextlibdir = getenv ("GETTEXTLIBDIR_SRCDIR");
1019     if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
1020       gettextlibdir = relocate (LIBDIR "/gettext");
1021 
1022     prog = xconcatenated_filename (gettextlibdir, "project-id", NULL);
1023 
1024     /* Call the project-id shell script.  */
1025     argv[0] = BOURNE_SHELL;
1026     argv[1] = prog;
1027     argv[2] = "yes";
1028     argv[3] = NULL;
1029     child = create_pipe_in (prog, BOURNE_SHELL, argv, DEV_NULL, false, true,
1030                             false, fd);
1031     if (child == -1)
1032       goto failed;
1033 
1034     /* Retrieve its result.  */
1035     fp = fdopen (fd[0], "r");
1036     if (fp == NULL)
1037       {
1038         error (0, errno, _("fdopen() failed"));
1039         goto failed;
1040       }
1041 
1042     line = NULL; linesize = 0;
1043     linelen = getline (&line, &linesize, fp);
1044     if (linelen == (size_t)(-1))
1045       {
1046         error (0, 0, _("%s subprocess I/O error"), prog);
1047         fclose (fp);
1048         goto failed;
1049       }
1050     if (linelen > 0 && line[linelen - 1] == '\n')
1051       line[linelen - 1] = '\0';
1052 
1053     fclose (fp);
1054 
1055     /* Remove zombie process from process list, and retrieve exit status.  */
1056     exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
1057     if (exitstatus != 0)
1058       {
1059         error (0, 0, _("%s subprocess failed with exit code %d"),
1060                prog, exitstatus);
1061         goto failed;
1062       }
1063 
1064     return line;
1065   }
1066 
1067 failed:
1068 #endif
1069   return "PACKAGE VERSION";
1070 }
1071 
1072 
1073 /* Construct the value for the PO-Revision-Date field.  */
1074 static const char *
po_revision_date(const char * header)1075 po_revision_date (const char *header)
1076 {
1077   if (no_translator)
1078     /* Because the PO file is automatically generated, we use the
1079        POT-Creation-Date, not the current time.  */
1080     return get_field (header, "POT-Creation-Date");
1081   else
1082     {
1083       /* Assume the translator will modify the PO file now.  */
1084       time_t now;
1085 
1086       time (&now);
1087       return po_strftime (&now);
1088     }
1089 }
1090 
1091 
1092 #if HAVE_PWD_H  /* Only Unix, not native Windows.  */
1093 
1094 /* Returns the struct passwd entry for the current user.  */
1095 static struct passwd *
get_user_pwd()1096 get_user_pwd ()
1097 {
1098   const char *username;
1099   struct passwd *userpasswd;
1100 
1101   /* 1. attempt: getpwnam(getenv("USER"))  */
1102   username = getenv ("USER");
1103   if (username != NULL)
1104     {
1105       errno = 0;
1106       userpasswd = getpwnam (username);
1107       if (userpasswd != NULL)
1108         return userpasswd;
1109       if (errno != 0)
1110         error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username);
1111     }
1112 
1113   /* 2. attempt: getpwnam(getlogin())  */
1114   username = getlogin ();
1115   if (username != NULL)
1116     {
1117       errno = 0;
1118       userpasswd = getpwnam (username);
1119       if (userpasswd != NULL)
1120         return userpasswd;
1121       if (errno != 0)
1122         error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username);
1123     }
1124 
1125   /* 3. attempt: getpwuid(getuid())  */
1126   errno = 0;
1127   userpasswd = getpwuid (getuid ());
1128   if (userpasswd != NULL)
1129     return userpasswd;
1130   if (errno != 0)
1131     error (EXIT_FAILURE, errno, "getpwuid(%ju)", (uintmax_t) getuid ());
1132 
1133   return NULL;
1134 }
1135 
1136 #endif
1137 
1138 
1139 /* Return the user's full name.  */
1140 static const char *
get_user_fullname()1141 get_user_fullname ()
1142 {
1143 #if HAVE_PWD_H
1144   struct passwd *pwd;
1145 
1146   pwd = get_user_pwd ();
1147   if (pwd != NULL)
1148     {
1149       const char *fullname;
1150       const char *fullname_end;
1151       char *result;
1152 
1153       /* Return the pw_gecos field, up to the first comma (if any).  */
1154       fullname = pwd->pw_gecos;
1155       fullname_end = strchr (fullname, ',');
1156       if (fullname_end == NULL)
1157         fullname_end = fullname + strlen (fullname);
1158 
1159       result = XNMALLOC (fullname_end - fullname + 1, char);
1160       memcpy (result, fullname, fullname_end - fullname);
1161       result[fullname_end - fullname] = '\0';
1162 
1163       return result;
1164     }
1165 #endif
1166 
1167   return NULL;
1168 }
1169 
1170 
1171 /* Return the user's email address.  */
1172 static const char *
get_user_email()1173 get_user_email ()
1174 {
1175   /* On native Windows, a Bourne shell is generally not available.
1176      Avoid error messages such as
1177      "msginit.exe: subprocess ... failed: No such file or directory"  */
1178 #if !(defined _WIN32 && ! defined __CYGWIN__)
1179   {
1180     const char *prog = relocate (LIBDIR "/gettext/user-email");
1181     char *argv[4];
1182     pid_t child;
1183     int fd[1];
1184     FILE *fp;
1185     char *line;
1186     size_t linesize;
1187     size_t linelen;
1188     int exitstatus;
1189 
1190     /* Ask the user for his email address.  */
1191     argv[0] = BOURNE_SHELL;
1192     argv[1] = (char *) prog;
1193     argv[2] = (char *) _("\
1194 The new message catalog should contain your email address, so that users can\n\
1195 give you feedback about the translations, and so that maintainers can contact\n\
1196 you in case of unexpected technical problems.\n");
1197     argv[3] = NULL;
1198     child = create_pipe_in (prog, BOURNE_SHELL, argv, DEV_NULL, false, true,
1199                             false, fd);
1200     if (child == -1)
1201       goto failed;
1202 
1203     /* Retrieve his answer.  */
1204     fp = fdopen (fd[0], "r");
1205     if (fp == NULL)
1206       {
1207         error (0, errno, _("fdopen() failed"));
1208         goto failed;
1209       }
1210 
1211     line = NULL; linesize = 0;
1212     linelen = getline (&line, &linesize, fp);
1213     if (linelen == (size_t)(-1))
1214       {
1215         error (0, 0, _("%s subprocess I/O error"), prog);
1216         fclose (fp);
1217         goto failed;
1218       }
1219     if (linelen > 0 && line[linelen - 1] == '\n')
1220       line[linelen - 1] = '\0';
1221 
1222     fclose (fp);
1223 
1224     /* Remove zombie process from process list, and retrieve exit status.  */
1225     exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
1226     if (exitstatus != 0)
1227       {
1228         error (0, 0, _("%s subprocess failed with exit code %d"),
1229                prog, exitstatus);
1230         goto failed;
1231       }
1232 
1233     return line;
1234   }
1235 
1236 failed:
1237 #endif
1238   return "EMAIL@ADDRESS";
1239 }
1240 
1241 
1242 /* Construct the value for the Last-Translator field.  */
1243 static const char *
last_translator()1244 last_translator ()
1245 {
1246   if (no_translator)
1247     return "Automatically generated";
1248   else
1249     {
1250       const char *fullname = get_user_fullname ();
1251       const char *email = get_user_email ();
1252 
1253       if (fullname != NULL)
1254         return xasprintf ("%s <%s>", fullname, email);
1255       else
1256         return xasprintf ("<%s>", email);
1257     }
1258 }
1259 
1260 
1261 /* Return the name of the language used by the language team, in English.  */
1262 static const char *
language_team_englishname()1263 language_team_englishname ()
1264 {
1265   size_t i;
1266 
1267   /* Search for a name depending on the catalogname.  */
1268   for (i = 0; i < language_variant_table_size; i++)
1269     if (strcmp (language_variant_table[i].code, catalogname) == 0)
1270       return language_variant_table[i].english;
1271 
1272   /* Search for a name depending on the language only.  */
1273   return englishname_of_language ();
1274 }
1275 
1276 
1277 /* Return the language team's mailing list address or homepage URL.  */
1278 static const char *
language_team_address()1279 language_team_address ()
1280 {
1281   /* On native Windows, a Bourne shell is generally not available.
1282      Avoid error messages such as
1283      "msginit.exe: subprocess ... failed: No such file or directory"  */
1284 #if !(defined _WIN32 && ! defined __CYGWIN__)
1285   {
1286     const char *prog = relocate (PROJECTSDIR "/team-address");
1287     char *argv[7];
1288     pid_t child;
1289     int fd[1];
1290     FILE *fp;
1291     char *line;
1292     size_t linesize;
1293     size_t linelen;
1294     int exitstatus;
1295 
1296     /* Call the team-address shell script.  */
1297     argv[0] = BOURNE_SHELL;
1298     argv[1] = (char *) prog;
1299     argv[2] = (char *) relocate (PROJECTSDIR);
1300     argv[3] = (char *) relocate (LIBDIR "/gettext");
1301     argv[4] = (char *) catalogname;
1302     argv[5] = (char *) language;
1303     argv[6] = NULL;
1304     child = create_pipe_in (prog, BOURNE_SHELL, argv, DEV_NULL, false, true,
1305                             false, fd);
1306     if (child == -1)
1307       goto failed;
1308 
1309     /* Retrieve its result.  */
1310     fp = fdopen (fd[0], "r");
1311     if (fp == NULL)
1312       {
1313         error (0, errno, _("fdopen() failed"));
1314         goto failed;
1315       }
1316 
1317     line = NULL; linesize = 0;
1318     linelen = getline (&line, &linesize, fp);
1319     if (linelen == (size_t)(-1))
1320       line = "";
1321     else if (linelen > 0 && line[linelen - 1] == '\n')
1322       line[linelen - 1] = '\0';
1323 
1324     fclose (fp);
1325 
1326     /* Remove zombie process from process list, and retrieve exit status.  */
1327     exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
1328     if (exitstatus != 0)
1329       {
1330         error (0, 0, _("%s subprocess failed with exit code %d"),
1331                prog, exitstatus);
1332         goto failed;
1333       }
1334 
1335     return line;
1336   }
1337 
1338 failed:
1339 #endif
1340   return "";
1341 }
1342 
1343 
1344 /* Construct the value for the Language-Team field.  */
1345 static const char *
language_team()1346 language_team ()
1347 {
1348   if (no_translator)
1349     return "none";
1350   else
1351     {
1352       const char *englishname = language_team_englishname ();
1353       const char *address = language_team_address ();
1354 
1355       if (address != NULL && address[0] != '\0')
1356         return xasprintf ("%s %s", englishname, address);
1357       else
1358         return englishname;
1359     }
1360 }
1361 
1362 
1363 /* Construct the value for the Language field.  */
1364 static const char *
language_value()1365 language_value ()
1366 {
1367   return catalogname;
1368 }
1369 
1370 
1371 /* Construct the value for the MIME-Version field.  */
1372 static const char *
mime_version()1373 mime_version ()
1374 {
1375   return "1.0";
1376 }
1377 
1378 
1379 /* Construct the value for the Content-Type field.  */
1380 static const char *
content_type(const char * header)1381 content_type (const char *header)
1382 {
1383   bool was_utf8;
1384   const char *old_field;
1385 
1386   /* If the POT file contains charset=UTF-8, it means that the POT file
1387      contains non-ASCII characters, and we keep the UTF-8 encoding.
1388      Otherwise, when the POT file is plain ASCII, we use the locale's
1389      encoding.  */
1390   was_utf8 = false;
1391   old_field = get_field (header, "Content-Type");
1392   if (old_field != NULL)
1393     {
1394       const char *charsetstr = c_strstr (old_field, "charset=");
1395 
1396       if (charsetstr != NULL)
1397         {
1398           charsetstr += strlen ("charset=");
1399           was_utf8 = (c_strcasecmp (charsetstr, "UTF-8") == 0);
1400         }
1401     }
1402   return xasprintf ("text/plain; charset=%s",
1403                     was_utf8 ? "UTF-8" : canonical_locale_charset ());
1404 }
1405 
1406 
1407 /* Construct the value for the Content-Transfer-Encoding field.  */
1408 static const char *
content_transfer_encoding()1409 content_transfer_encoding ()
1410 {
1411   return "8bit";
1412 }
1413 
1414 
1415 /* Construct the value for the Plural-Forms field.  */
1416 static const char *
plural_forms()1417 plural_forms ()
1418 {
1419   const char *gettextcldrdir;
1420   char *prog = NULL;
1421   size_t i;
1422 
1423   /* Search for a formula depending on the catalogname.  */
1424   for (i = 0; i < plural_table_size; i++)
1425     if (strcmp (plural_table[i].lang, catalogname) == 0)
1426       return plural_table[i].value;
1427 
1428   /* Search for a formula depending on the language only.  */
1429   for (i = 0; i < plural_table_size; i++)
1430     if (strcmp (plural_table[i].lang, language) == 0)
1431       return plural_table[i].value;
1432 
1433   gettextcldrdir = getenv ("GETTEXTCLDRDIR");
1434   if (gettextcldrdir != NULL && gettextcldrdir[0] != '\0')
1435     {
1436       const char *gettextlibdir;
1437       char *dirs[3], *last_dir;
1438       char *argv[4];
1439       pid_t child;
1440       int fd[1];
1441       FILE *fp;
1442       char *line;
1443       size_t linesize;
1444       size_t linelen;
1445       int exitstatus;
1446 
1447       gettextlibdir = getenv ("GETTEXTLIBDIR_BUILDDIR");
1448       if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
1449         gettextlibdir = relocate (LIBDIR "/gettext");
1450 
1451       prog = xconcatenated_filename (gettextlibdir, "cldr-plurals", EXEEXT);
1452 
1453       last_dir = xstrdup (gettextcldrdir);
1454       dirs[0] = "common";
1455       dirs[1] = "supplemental";
1456       dirs[2] = "plurals.xml";
1457       for (i = 0; i < SIZEOF (dirs); i++)
1458         {
1459           char *dir = xconcatenated_filename (last_dir, dirs[i], NULL);
1460           free (last_dir);
1461           last_dir = dir;
1462         }
1463 
1464       /* Call the cldr-plurals command.
1465          argv[0] must be prog, not just the base name "cldr-plurals",
1466          because on Cygwin in a build with --enable-shared, the libtool
1467          wrapper of cldr-plurals.exe apparently needs this.  */
1468       argv[0] = prog;
1469       argv[1] = (char *) language;
1470       argv[2] = last_dir;
1471       argv[3] = NULL;
1472       child = create_pipe_in (prog, prog, argv, DEV_NULL,
1473                               false, true, false,
1474                               fd);
1475       free (last_dir);
1476       if (child == -1)
1477         goto failed;
1478 
1479       /* Retrieve its result.  */
1480       fp = fdopen (fd[0], "r");
1481       if (fp == NULL)
1482         {
1483           error (0, errno, _("fdopen() failed"));
1484           goto failed;
1485         }
1486 
1487       line = NULL; linesize = 0;
1488       linelen = getline (&line, &linesize, fp);
1489       if (linelen == (size_t)(-1))
1490         {
1491           error (0, 0, _("%s subprocess I/O error"), prog);
1492           fclose (fp);
1493           goto failed;
1494         }
1495       if (linelen > 0 && line[linelen - 1] == '\n')
1496         {
1497           line[linelen - 1] = '\0';
1498 #if defined _WIN32 && ! defined __CYGWIN__
1499           if (linelen > 1 && line[linelen - 2] == '\r')
1500             line[linelen - 2] = '\0';
1501 #endif
1502         }
1503 
1504       fclose (fp);
1505 
1506       /* Remove zombie process from process list, and retrieve exit status.  */
1507       exitstatus = wait_subprocess (child, prog, false, false, true, false,
1508                                     NULL);
1509       if (exitstatus != 0)
1510         {
1511           error (0, 0, _("%s subprocess failed with exit code %d"),
1512                  prog, exitstatus);
1513           goto failed;
1514         }
1515 
1516       return line;
1517     }
1518 
1519  failed:
1520   free (prog);
1521   return NULL;
1522 }
1523 
1524 
1525 static struct
1526 {
1527   const char *name;
1528   const char * (*getter0) (void);
1529   const char * (*getter1) (const char *header);
1530 }
1531 fields[] =
1532   {
1533     { "Project-Id-Version", NULL, project_id_version },
1534     { "PO-Revision-Date", NULL, po_revision_date },
1535     { "Last-Translator", last_translator, NULL },
1536     { "Language-Team", language_team, NULL },
1537     { "Language", language_value, NULL },
1538     { "MIME-Version", mime_version, NULL },
1539     { "Content-Type", NULL, content_type },
1540     { "Content-Transfer-Encoding", content_transfer_encoding, NULL },
1541     { "Plural-Forms", plural_forms, NULL }
1542   };
1543 
1544 #define NFIELDS SIZEOF (fields)
1545 #define FIELD_LAST_TRANSLATOR 2
1546 
1547 
1548 /* Retrieve a freshly allocated copy of a field's value.  */
1549 static char *
get_field(const char * header,const char * field)1550 get_field (const char *header, const char *field)
1551 {
1552   size_t len = strlen (field);
1553   const char *line;
1554 
1555   for (line = header;;)
1556     {
1557       if (strncmp (line, field, len) == 0 && line[len] == ':')
1558         {
1559           const char *value_start;
1560           const char *value_end;
1561           char *value;
1562 
1563           value_start = line + len + 1;
1564           if (*value_start == ' ')
1565             value_start++;
1566           value_end = strchr (value_start, '\n');
1567           if (value_end == NULL)
1568             value_end = value_start + strlen (value_start);
1569 
1570           value = XNMALLOC (value_end - value_start + 1, char);
1571           memcpy (value, value_start, value_end - value_start);
1572           value[value_end - value_start] = '\0';
1573 
1574           return value;
1575         }
1576 
1577       line = strchr (line, '\n');
1578       if (line != NULL)
1579         line++;
1580       else
1581         break;
1582     }
1583 
1584   return NULL;
1585 }
1586 
1587 /* Add a field with value to a header, and return the new header.  */
1588 static char *
put_field(const char * old_header,const char * field,const char * value)1589 put_field (const char *old_header, const char *field, const char *value)
1590 {
1591   size_t len = strlen (field);
1592   const char *line;
1593   char *new_header;
1594   char *p;
1595 
1596   for (line = old_header;;)
1597     {
1598       if (strncmp (line, field, len) == 0 && line[len] == ':')
1599         {
1600           const char *value_start;
1601           const char *value_end;
1602 
1603           value_start = line + len + 1;
1604           if (*value_start == ' ')
1605             value_start++;
1606           value_end = strchr (value_start, '\n');
1607           if (value_end == NULL)
1608             value_end = value_start + strlen (value_start);
1609 
1610           new_header = XNMALLOC (strlen (old_header)
1611                                  - (value_end - value_start)
1612                                  + strlen (value)
1613                                  + (*value_end != '\n' ? 1 : 0)
1614                                  + 1,
1615                                  char);
1616           p = new_header;
1617           memcpy (p, old_header, value_start - old_header);
1618           p += value_start - old_header;
1619           memcpy (p, value, strlen (value));
1620           p += strlen (value);
1621           if (*value_end != '\n')
1622             *p++ = '\n';
1623           strcpy (p, value_end);
1624 
1625           return new_header;
1626         }
1627 
1628       line = strchr (line, '\n');
1629       if (line != NULL)
1630         line++;
1631       else
1632         break;
1633     }
1634 
1635   new_header = XNMALLOC (strlen (old_header) + 1
1636                          + len + 2 + strlen (value) + 1
1637                          + 1,
1638                          char);
1639   p = new_header;
1640   memcpy (p, old_header, strlen (old_header));
1641   p += strlen (old_header);
1642   if (p > new_header && p[-1] != '\n')
1643     *p++ = '\n';
1644   memcpy (p, field, len);
1645   p += len;
1646   *p++ = ':';
1647   *p++ = ' ';
1648   memcpy (p, value, strlen (value));
1649   p += strlen (value);
1650   *p++ = '\n';
1651   *p = '\0';
1652 
1653   return new_header;
1654 }
1655 
1656 
1657 /* Return the title format string.  */
1658 static const char *
get_title()1659 get_title ()
1660 {
1661   /* This is tricky.  We want the translation in the given locale specified by
1662      the command line, not the current locale.  But we want it in the encoding
1663      that we put into the header entry, not the encoding of that locale.
1664      We could avoid the use of OUTPUT_CHARSET by using a separate message
1665      catalog and bind_textdomain_codeset(), but that doesn't seem worth the
1666      trouble for one single message.  */
1667   const char *encoding;
1668   const char *tmp;
1669   char *old_LC_ALL;
1670   char *old_LANGUAGE;
1671   char *old_OUTPUT_CHARSET;
1672   const char *msgid;
1673   const char *english;
1674   const char *result;
1675 
1676   encoding = canonical_locale_charset ();
1677 
1678   /* First, the English title.  */
1679   english = xasprintf ("%s translations for %%s package",
1680                        englishname_of_language ());
1681 
1682   /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables.  */
1683 
1684   tmp = getenv ("LC_ALL");
1685   old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
1686 
1687   tmp = getenv ("LANGUAGE");
1688   old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL);
1689 
1690   tmp = getenv ("OUTPUT_CHARSET");
1691   old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL);
1692 
1693   xsetenv ("LC_ALL", locale, 1);
1694   unsetenv ("LANGUAGE");
1695   xsetenv ("OUTPUT_CHARSET", encoding, 1);
1696 
1697   if (setlocale (LC_ALL, "") == NULL)
1698     /* Nonexistent locale.  Use the English title.  */
1699     result = english;
1700   else
1701     {
1702       /* Fetch the translation.  */
1703       /* TRANSLATORS: "English" needs to be replaced by your language.
1704          For example in it.po write "Traduzioni italiani ...",
1705          *not* "Traduzioni inglesi ...".  */
1706       msgid = N_("English translations for %s package");
1707       result = gettext (msgid);
1708       if (result != msgid && strcmp (result, msgid) != 0)
1709         /* Use the English and the foreign title.  */
1710         result = xasprintf ("%s\n%s", english, result);
1711       else
1712         /* No translation found.  Use the English title.  */
1713         result = english;
1714     }
1715 
1716   /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables.  */
1717 
1718   if (old_LC_ALL != NULL)
1719     xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
1720   else
1721     unsetenv ("LC_ALL");
1722 
1723   if (old_LANGUAGE != NULL)
1724     xsetenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE);
1725   else
1726     unsetenv ("LANGUAGE");
1727 
1728   if (old_OUTPUT_CHARSET != NULL)
1729     xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET);
1730   else
1731     unsetenv ("OUTPUT_CHARSET");
1732 
1733   setlocale (LC_ALL, "");
1734 
1735   return result;
1736 }
1737 
1738 
1739 /* Perform a set of substitutions in a string and return the resulting
1740    string.  When subst[j][0] found, it is replaced with subst[j][1].
1741    subst[j][0] must not be the empty string.  */
1742 static const char *
subst_string(const char * str,unsigned int nsubst,const char * (* subst)[2])1743 subst_string (const char *str,
1744               unsigned int nsubst, const char *(*subst)[2])
1745 {
1746   if (nsubst > 0)
1747     {
1748       char *malloced = NULL;
1749       size_t *substlen;
1750       size_t i;
1751       unsigned int j;
1752 
1753       substlen = (size_t *) xmalloca (nsubst * sizeof (size_t));
1754       for (j = 0; j < nsubst; j++)
1755         {
1756           substlen[j] = strlen (subst[j][0]);
1757           if (substlen[j] == 0)
1758             abort ();
1759         }
1760 
1761       for (i = 0;;)
1762         {
1763           if (str[i] == '\0')
1764             break;
1765           for (j = 0; j < nsubst; j++)
1766             if (*(str + i) == *subst[j][0]
1767                 && strncmp (str + i, subst[j][0], substlen[j]) == 0)
1768               {
1769                 size_t replacement_len = strlen (subst[j][1]);
1770                 size_t new_len = strlen (str) - substlen[j] + replacement_len;
1771                 char *new_str = XNMALLOC (new_len + 1, char);
1772                 memcpy (new_str, str, i);
1773                 memcpy (new_str + i, subst[j][1], replacement_len);
1774                 strcpy (new_str + i + replacement_len, str + i + substlen[j]);
1775                 if (malloced != NULL)
1776                   free (malloced);
1777                 str = new_str;
1778                 malloced = new_str;
1779                 i += replacement_len;
1780                 break;
1781               }
1782           if (j == nsubst)
1783             i++;
1784         }
1785 
1786       freea (substlen);
1787     }
1788 
1789   return str;
1790 }
1791 
1792 /* Perform a set of substitutions on each string of a string list.
1793    When subst[j][0] found, it is replaced with subst[j][1].  subst[j][0]
1794    must not be the empty string.  */
1795 static void
subst_string_list(string_list_ty * slp,unsigned int nsubst,const char * (* subst)[2])1796 subst_string_list (string_list_ty *slp,
1797                    unsigned int nsubst, const char *(*subst)[2])
1798 {
1799   size_t j;
1800 
1801   for (j = 0; j < slp->nitems; j++)
1802     slp->item[j] = subst_string (slp->item[j], nsubst, subst);
1803 }
1804 
1805 
1806 /* Fill the templates in all fields of the header entry.  */
1807 static msgdomain_list_ty *
fill_header(msgdomain_list_ty * mdlp)1808 fill_header (msgdomain_list_ty *mdlp)
1809 {
1810   /* Cache the strings filled in, for use when there are multiple domains
1811      and a header entry for each domain.  */
1812   const char *field_value[NFIELDS];
1813   size_t k, j, i;
1814 
1815   for (i = 0; i < NFIELDS; i++)
1816     field_value[i] = NULL;
1817 
1818   for (k = 0; k < mdlp->nitems; k++)
1819     {
1820       message_list_ty *mlp = mdlp->item[k]->messages;
1821 
1822       if (mlp->nitems > 0)
1823         {
1824           message_ty *header_mp = NULL;
1825           char *header;
1826 
1827           /* Search the header entry.  */
1828           for (j = 0; j < mlp->nitems; j++)
1829             if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1830               {
1831                 header_mp = mlp->item[j];
1832                 break;
1833               }
1834 
1835           /* If it wasn't found, provide one.  */
1836           if (header_mp == NULL)
1837             {
1838               static lex_pos_ty pos = { __FILE__, __LINE__ };
1839 
1840               header_mp = message_alloc (NULL, "", NULL, "", 1, &pos);
1841               message_list_prepend (mlp, header_mp);
1842             }
1843 
1844           header = xstrdup (header_mp->msgstr);
1845 
1846           /* Fill in the fields.  */
1847           for (i = 0; i < NFIELDS; i++)
1848             {
1849               if (field_value[i] == NULL)
1850                 field_value[i] =
1851                   (fields[i].getter1 != NULL
1852                    ? fields[i].getter1 (header)
1853                    : fields[i].getter0 ());
1854 
1855               if (field_value[i] != NULL)
1856                 {
1857                   char *old_header = header;
1858                   header = put_field (header, fields[i].name, field_value[i]);
1859                   free (old_header);
1860                 }
1861             }
1862 
1863           /* Replace the old translation in the header entry.  */
1864           header_mp->msgstr = header;
1865           header_mp->msgstr_len = strlen (header) + 1;
1866 
1867           /* Update the comments in the header entry.  */
1868           if (header_mp->comment != NULL)
1869             {
1870               const char *subst[4][2];
1871               const char *id;
1872               time_t now;
1873 
1874               id = project_id (header);
1875               subst[0][0] = "SOME DESCRIPTIVE TITLE";
1876               subst[0][1] = xasprintf (get_title (), id, id);
1877               subst[1][0] = "PACKAGE";
1878               subst[1][1] = id;
1879               subst[2][0] = "FIRST AUTHOR <EMAIL@ADDRESS>";
1880               subst[2][1] = field_value[FIELD_LAST_TRANSLATOR];
1881               subst[3][0] = "YEAR";
1882               subst[3][1] =
1883                 xasprintf ("%d",
1884                            (time (&now), (localtime (&now))->tm_year + 1900));
1885               subst_string_list (header_mp->comment, SIZEOF (subst), subst);
1886             }
1887 
1888           /* Finally remove the fuzzy attribute.  */
1889           header_mp->is_fuzzy = false;
1890         }
1891     }
1892 
1893   return mdlp;
1894 }
1895 
1896 
1897 /* Update the msgstr plural entries according to the nplurals count.  */
1898 static msgdomain_list_ty *
update_msgstr_plurals(msgdomain_list_ty * mdlp)1899 update_msgstr_plurals (msgdomain_list_ty *mdlp)
1900 {
1901   size_t k;
1902 
1903   for (k = 0; k < mdlp->nitems; k++)
1904     {
1905       message_list_ty *mlp = mdlp->item[k]->messages;
1906       message_ty *header_entry;
1907       unsigned long int nplurals;
1908       char *untranslated_plural_msgstr;
1909       size_t j;
1910 
1911       header_entry = message_list_search (mlp, NULL, "");
1912       nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL);
1913       untranslated_plural_msgstr = XNMALLOC (nplurals, char);
1914       memset (untranslated_plural_msgstr, '\0', nplurals);
1915 
1916       for (j = 0; j < mlp->nitems; j++)
1917         {
1918           message_ty *mp = mlp->item[j];
1919           bool is_untranslated;
1920           const char *p;
1921           const char *pend;
1922 
1923           if (mp->msgid_plural != NULL)
1924             {
1925               /* Test if mp is untranslated.  (It most likely is.)  */
1926               is_untranslated = true;
1927               for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++)
1928                 if (*p != '\0')
1929                   {
1930                     is_untranslated = false;
1931                     break;
1932                   }
1933               if (is_untranslated)
1934                 {
1935                   /* Change mp->msgstr_len consecutive empty strings into
1936                      nplurals consecutive empty strings.  */
1937                   if (nplurals > mp->msgstr_len)
1938                     mp->msgstr = untranslated_plural_msgstr;
1939                   mp->msgstr_len = nplurals;
1940                 }
1941             }
1942         }
1943     }
1944   return mdlp;
1945 }
1946