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