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