No empty .Rs/.Re
[netbsd-mini2440.git] / gnu / dist / gettext / gettext-tools / src / msginit.c
blob1d97035511382c1133e0130687d1abbb4f49d3ad
1 /* Initializes a new PO file.
2 Copyright (C) 2001-2005 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
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.
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.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 #include <alloca.h>
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>
36 #if HAVE_PWD_H
37 # include <pwd.h>
38 #endif
40 #if HAVE_UNISTD_H
41 # include <unistd.h>
42 #endif
44 #if HAVE_DIRENT_H
45 # include <dirent.h>
46 #else
47 # define dirent direct
48 # if HAVE_SYS_NDIR_H
49 # include <sys/ndir.h>
50 # endif
51 # if HAVE_SYS_DIR_H
52 # include <sys/dir.h>
53 # endif
54 # if HAVE_NDIR_H
55 # include <ndir.h>
56 # endif
57 #endif
59 #if CLOSEDIR_VOID
60 /* Fake a return value. */
61 # define CLOSEDIR(d) (closedir (d), 0)
62 #else
63 # define CLOSEDIR(d) closedir (d)
64 #endif
66 #if HAVE_DIRENT_H || HAVE_NDIR_H || HAVE_SYS_DIR_H || HAVE_SYS_NDIR_H
67 # define HAVE_DIR 1
68 #else
69 # define HAVE_DIR 0
70 #endif
72 #include "closeout.h"
73 #include "error.h"
74 #include "error-progname.h"
75 #include "progname.h"
76 #include "relocatable.h"
77 #include "basename.h"
78 #include "strpbrk.h"
79 #include "strstr.h"
80 #include "c-strcase.h"
81 #include "message.h"
82 #include "read-po.h"
83 #include "write-po.h"
84 #include "po-charset.h"
85 #include "localcharset.h"
86 #include "po-time.h"
87 #include "plural-table.h"
88 #include "xalloc.h"
89 #include "xallocsa.h"
90 #include "exit.h"
91 #include "pathname.h"
92 #include "xerror.h"
93 #include "msgl-english.h"
94 #include "plural-count.h"
95 #include "pipe.h"
96 #include "wait-process.h"
97 #include "getline.h"
98 #include "xsetenv.h"
99 #include "str-list.h"
100 #include "gettext.h"
102 #define _(str) gettext (str)
103 #define N_(str) (str)
105 /* Get F_OK. It is lacking from <fcntl.h> on Woe32. */
106 #ifndef F_OK
107 # define F_OK 0
108 #endif
110 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
112 extern const char * _nl_locale_name (int category, const char *categoryname);
113 extern const char * _nl_expand_alias (const char *name);
115 /* Locale name. */
116 static const char *locale;
118 /* Language (ISO-639 code) and optional territory (ISO-3166 code). */
119 static const char *catalogname;
121 /* Language (ISO-639 code). */
122 static const char *language;
124 /* If true, the user is not considered to be the translator. */
125 static bool no_translator;
127 /* Long options. */
128 static const struct option long_options[] =
130 { "help", no_argument, NULL, 'h' },
131 { "input", required_argument, NULL, 'i' },
132 { "locale", required_argument, NULL, 'l' },
133 { "no-translator", no_argument, NULL, CHAR_MAX + 1 },
134 { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
135 { "output-file", required_argument, NULL, 'o' },
136 { "properties-input", no_argument, NULL, 'P' },
137 { "properties-output", no_argument, NULL, 'p' },
138 { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 },
139 { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 },
140 { "version", no_argument, NULL, 'V' },
141 { "width", required_argument, NULL, 'w' },
142 { NULL, 0, NULL, 0 }
145 /* Forward declaration of local functions. */
146 static void usage (int status)
147 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
148 __attribute__ ((noreturn))
149 #endif
151 static const char *find_pot (void);
152 static const char *catalogname_for_locale (const char *locale);
153 static const char *language_of_locale (const char *locale);
154 static char *get_field (const char *header, const char *field);
155 static msgdomain_list_ty *fill_header (msgdomain_list_ty *mdlp);
156 static msgdomain_list_ty *update_msgstr_plurals (msgdomain_list_ty *mdlp);
160 main (int argc, char **argv)
162 int opt;
163 bool do_help;
164 bool do_version;
165 char *output_file;
166 const char *input_file;
167 msgdomain_list_ty *result;
169 /* Set program name for messages. */
170 set_program_name (argv[0]);
171 error_print_progname = maybe_print_progname;
173 #ifdef HAVE_SETLOCALE
174 /* Set locale via LC_ALL. */
175 setlocale (LC_ALL, "");
176 #endif
178 /* Set the text message domain. */
179 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
180 textdomain (PACKAGE);
182 /* Ensure that write errors on stdout are detected. */
183 atexit (close_stdout);
185 /* Set default values for variables. */
186 do_help = false;
187 do_version = false;
188 output_file = NULL;
189 input_file = NULL;
190 locale = NULL;
192 while ((opt = getopt_long (argc, argv, "hi:l:o:pPVw:", long_options, NULL))
193 != EOF)
194 switch (opt)
196 case '\0': /* Long option. */
197 break;
199 case 'h':
200 do_help = true;
201 break;
203 case 'i':
204 if (input_file != NULL)
206 error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
207 usage (EXIT_FAILURE);
209 input_file = optarg;
210 break;
212 case 'l':
213 locale = optarg;
214 break;
216 case 'o':
217 output_file = optarg;
218 break;
220 case 'p':
221 message_print_syntax_properties ();
222 break;
224 case 'P':
225 input_syntax = syntax_properties;
226 break;
228 case 'V':
229 do_version = true;
230 break;
232 case 'w':
234 int value;
235 char *endp;
236 value = strtol (optarg, &endp, 10);
237 if (endp != optarg)
238 message_page_width_set (value);
240 break;
242 case CHAR_MAX + 1:
243 no_translator = true;
244 break;
246 case CHAR_MAX + 2: /* --no-wrap */
247 message_page_width_ignore ();
248 break;
250 case CHAR_MAX + 3: /* --stringtable-input */
251 input_syntax = syntax_stringtable;
252 break;
254 case CHAR_MAX + 4: /* --stringtable-output */
255 message_print_syntax_stringtable ();
256 break;
258 default:
259 usage (EXIT_FAILURE);
260 break;
263 /* Version information is requested. */
264 if (do_version)
266 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
267 /* xgettext: no-wrap */
268 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
269 This is free software; see the source for copying conditions. There is NO\n\
270 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
272 "2001-2005");
273 printf (_("Written by %s.\n"), "Bruno Haible");
274 exit (EXIT_SUCCESS);
277 /* Help is requested. */
278 if (do_help)
279 usage (EXIT_SUCCESS);
281 /* Test for extraneous arguments. */
282 if (optind != argc)
283 error (EXIT_FAILURE, 0, _("too many arguments"));
285 /* Search for the input file. */
286 if (input_file == NULL)
287 input_file = find_pot ();
289 /* Determine target locale. */
290 if (locale == NULL)
292 locale = _nl_locale_name (LC_MESSAGES, "LC_MESSAGES");
293 if (strcmp (locale, "C") == 0)
295 multiline_error (xstrdup (""),
296 xstrdup (_("\
297 You are in a language indifferent environment. Please set\n\
298 your LANG environment variable, as described in the ABOUT-NLS\n\
299 file. This is necessary so you can test your translations.\n")));
300 exit (EXIT_FAILURE);
304 const char *alias = _nl_expand_alias (locale);
305 if (alias != NULL)
306 locale = alias;
308 catalogname = catalogname_for_locale (locale);
309 language = language_of_locale (locale);
311 /* Default output file name is CATALOGNAME.po. */
312 if (output_file == NULL)
314 size_t cnlen = strlen (catalogname);
316 output_file = (char *) xmalloc (cnlen + 3 + 1);
317 memcpy (output_file, catalogname, cnlen);
318 memcpy (output_file + cnlen, ".po", 3 + 1);
320 /* But don't overwrite existing PO files. */
321 if (access (output_file, F_OK) == 0)
323 multiline_error (xstrdup (""),
324 xasprintf (_("\
325 Output file %s already exists.\n\
326 Please specify the locale through the --locale option or\n\
327 the output .po file through the --output-file option.\n"),
328 output_file));
329 exit (EXIT_FAILURE);
333 /* Read input file. */
334 result = read_po_file (input_file);
336 /* Fill the header entry. */
337 result = fill_header (result);
339 /* Initialize translations. */
340 if (strcmp (language, "en") == 0)
341 result = msgdomain_list_english (result);
342 else
343 result = update_msgstr_plurals (result);
345 /* Write the modified message list out. */
346 msgdomain_list_print (result, output_file, true, false);
348 if (!no_translator)
349 fprintf (stderr, "\n");
350 fprintf (stderr, _("Created %s.\n"), output_file);
352 exit (EXIT_SUCCESS);
356 /* Display usage information and exit. */
357 static void
358 usage (int status)
360 if (status != EXIT_SUCCESS)
361 fprintf (stderr, _("Try `%s --help' for more information.\n"),
362 program_name);
363 else
365 printf (_("\
366 Usage: %s [OPTION]\n\
367 "), program_name);
368 printf ("\n");
369 /* xgettext: no-wrap */
370 printf (_("\
371 Creates a new PO file, initializing the meta information with values from the\n\
372 user's environment.\n\
373 "));
374 printf ("\n");
375 printf (_("\
376 Mandatory arguments to long options are mandatory for short options too.\n"));
377 printf ("\n");
378 printf (_("\
379 Input file location:\n"));
380 printf (_("\
381 -i, --input=INPUTFILE input POT file\n"));
382 printf (_("\
383 If no input file is given, the current directory is searched for the POT file.\n\
384 If it is -, standard input is read.\n"));
385 printf ("\n");
386 printf (_("\
387 Output file location:\n"));
388 printf (_("\
389 -o, --output-file=FILE write output to specified PO file\n"));
390 printf (_("\
391 If no output file is given, it depends on the --locale option or the user's\n\
392 locale setting. If it is -, the results are written to standard output.\n"));
393 printf ("\n");
394 printf (_("\
395 Input file syntax:\n"));
396 printf (_("\
397 -P, --properties-input input file is in Java .properties syntax\n"));
398 printf (_("\
399 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
400 printf ("\n");
401 printf (_("\
402 Output details:\n"));
403 printf (_("\
404 -l, --locale=LL_CC set target locale\n"));
405 printf (_("\
406 --no-translator assume the PO file is automatically generated\n"));
407 printf (_("\
408 -p, --properties-output write out a Java .properties file\n"));
409 printf (_("\
410 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
411 printf (_("\
412 -w, --width=NUMBER set output page width\n"));
413 printf (_("\
414 --no-wrap do not break long message lines, longer than\n\
415 the output page width, into several lines\n"));
416 printf ("\n");
417 printf (_("\
418 Informative output:\n"));
419 printf (_("\
420 -h, --help display this help and exit\n"));
421 printf (_("\
422 -V, --version output version information and exit\n"));
423 printf ("\n");
424 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
425 stdout);
428 exit (status);
432 /* Search for the POT file and return its name. */
433 static const char *
434 find_pot ()
436 #if HAVE_DIR
437 DIR *dirp;
438 char *found = NULL;
440 dirp = opendir (".");
441 if (dirp != NULL)
443 for (;;)
445 struct dirent *dp;
447 errno = 0;
448 dp = readdir (dirp);
449 if (dp != NULL)
451 const char *name = dp->d_name;
452 size_t namlen = strlen (name);
454 if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0)
456 if (found == NULL)
457 found = xstrdup (name);
458 else
460 multiline_error (xstrdup (""),
461 xstrdup (_("\
462 Found more than one .pot file.\n\
463 Please specify the input .pot file through the --input option.\n")));
464 usage (EXIT_FAILURE);
468 else if (errno != 0)
469 error (EXIT_FAILURE, errno, _("error reading current directory"));
470 else
471 break;
473 if (CLOSEDIR (dirp))
474 error (EXIT_FAILURE, errno, _("error reading current directory"));
476 if (found != NULL)
477 return found;
479 #endif
481 multiline_error (xstrdup (""),
482 xstrdup (_("\
483 Found no .pot file in the current directory.\n\
484 Please specify the input .pot file through the --input option.\n")));
485 usage (EXIT_FAILURE);
486 /* NOTREACHED */
487 return NULL;
491 /* Return the gettext catalog name corresponding to a locale. If the locale
492 consists of a language and a territory, and the language is mainly spoken
493 in that territory, the territory is removed from the locale name.
494 For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de",
495 because the resulting catalog can be used as a default for all "de_XX",
496 such as "de_AT". */
497 static const char *
498 catalogname_for_locale (const char *locale)
500 static const char *locales_with_principal_territory[] = {
501 /* Language Main territory */
502 "af_ZA", /* Afrikaans South Africa */
503 "ak_GH", /* Akan Ghana */
504 "am_ET", /* Amharic Ethiopia */
505 "an_ES", /* Aragonese Spain */
506 "as_IN", /* Assamese India */
507 "av_RU", /* Avaric Russia */
508 "az_AZ", /* Azerbaijani Azerbaijan */
509 "be_BY", /* Belarusian Belarus */
510 "bg_BG", /* Bulgarian Bulgaria */
511 "bm_ML", /* Bambara Mali */
512 "bn_IN", /* Bengali India */
513 "bo_CN", /* Tibetan China */
514 "br_FR", /* Breton France */
515 "bs_BA", /* Bosnian Bosnia */
516 "ca_ES", /* Catalan Spain */
517 "ce_RU", /* Chechen Russia */
518 "co_FR", /* Corsican France */
519 "cr_CA", /* Cree Canada */
520 "cs_CZ", /* Czech Czech Republic */
521 "cy_GB", /* Welsh Britain */
522 "da_DK", /* Danish Denmark */
523 "de_DE", /* German Germany */
524 "dv_MV", /* Divehi Maldives */
525 "dz_BT", /* Dzongkha Bhutan */
526 "ee_GH", /* Éwé Ghana */
527 "el_GR", /* Greek Greece */
528 /* Don't put "en_GB" or "en_US" here. That would be asking for fruitless
529 political discussion. */
530 "es_ES", /* Spanish Spain */
531 "et_EE", /* Estonian Estonia */
532 "fa_IR", /* Persian Iran */
533 "fi_FI", /* Finnish Finland */
534 "fj_FJ", /* Fijian Fiji */
535 "fo_FO", /* Faroese Faeroe Islands */
536 "fr_FR", /* French France */
537 "ga_IE", /* Irish Ireland */
538 "gd_GB", /* Scots Britain */
539 "gu_IN", /* Gujarati India */
540 "he_IL", /* Hebrew Israel */
541 "hi_IN", /* Hindi India */
542 "hr_HR", /* Croatian Croatia */
543 "ht_HT", /* Haitian Haiti */
544 "hu_HU", /* Hungarian Hungary */
545 "hy_AM", /* Armenian Armenia */
546 "id_ID", /* Indonesian Indonesia */
547 "ig_NG", /* Igbo Nigeria */
548 "ii_CN", /* Sichuan Yi China */
549 "is_IS", /* Icelandic Iceland */
550 "it_IT", /* Italian Italy */
551 "ja_JP", /* Japanese Japan */
552 "jv_ID", /* Javanese Indonesia */
553 "ka_GE", /* Georgian Georgia */
554 "kg_CD", /* Kongo Democratic Republic of Congo */
555 "kk_KZ", /* Kazakh Kazakhstan */
556 "kl_GL", /* Kalaallisut Greenland */
557 "km_KH", /* Khmer Cambodia */
558 "kn_IN", /* Kannada India */
559 "ko_KR", /* Korean Korea (South) */
560 "kok_IN", /* Konkani India */
561 "kr_NG", /* Kanuri Nigeria */
562 "lg_UG", /* Ganda Uganda */
563 "li_BE", /* Limburgish Belgium */
564 "lo_LA", /* Laotian Laos */
565 "lt_LT", /* Lithuanian Lithuania */
566 "lu_CD", /* Luba-Katanga Democratic Republic of Congo */
567 "lv_LV", /* Latvian Latvia */
568 "mg_MG", /* Malagasy Madagascar */
569 "mk_MK", /* Macedonian Macedonia */
570 "ml_IN", /* Malayalam India */
571 "mn_MN", /* Mongolian Mongolia */
572 "mr_IN", /* Marathi India */
573 "ms_MY", /* Malay Malaysia */
574 "mt_MT", /* Maltese Malta */
575 "my_MM", /* Burmese Myanmar */
576 "mni_IN", /* Manipuri India */
577 "na_NR", /* Nauru Nauru */
578 "nb_NO", /* Norwegian Bokmål Norway */
579 "ne_NP", /* Nepali Nepal */
580 "nl_NL", /* Dutch Netherlands */
581 "nn_NO", /* Norwegian Nynorsk Norway */
582 "no_NO", /* Norwegian Norway */
583 "oc_FR", /* Occitan France */
584 "oj_CA", /* Ojibwa Canada */
585 "or_IN", /* Oriya India */
586 "pa_IN", /* Punjabi India */
587 "pl_PL", /* Polish Poland */
588 "ps_AF", /* Pashto Afghanistan */
589 "pt_PT", /* Portuguese Portugal */
590 "rm_CH", /* Rhaeto-Roman Switzerland */
591 "rn_BI", /* Kirundi Burundi */
592 "ro_RO", /* Romanian Romania */
593 "ru_RU", /* Russian Russia */
594 "sa_IN", /* Sanskrit India */
595 "sc_IT", /* Sardinian Italy */
596 "sg_CF", /* Sango Central African Rep. */
597 "si_LK", /* Sinhalese Sri Lanka */
598 "sk_SK", /* Slovak Slovakia */
599 "sl_SI", /* Slovenian Slovenia */
600 "so_SO", /* Somali Somalia */
601 "sq_AL", /* Albanian Albania */
602 "sr_CS", /* Serbian Serbia & Montenegro */
603 "sr_YU", /* Serbian Yugoslavia */
604 "sv_SE", /* Swedish Sweden */
605 "te_IN", /* Telugu India */
606 "tg_TJ", /* Tajik Tajikistan */
607 "th_TH", /* Thai Thailand */
608 "tk_TM", /* Turkmen Turkmenistan */
609 "tl_PH", /* Tagalog Philippines */
610 "to_TO", /* Tonga Tonga */
611 "tr_TR", /* Turkish Turkey */
612 "uk_UA", /* Ukrainian Ukraine */
613 "ur_PK", /* Urdu Pakistan */
614 "uz_UZ", /* Uzbek Uzbekistan */
615 "ve_ZA", /* Venda South Africa */
616 "vi_VN", /* Vietnamese Vietnam */
617 "wa_BE", /* Walloon Belgium */
618 "wen_DE" /* Sorbian Germany */
620 const char *dot;
621 size_t i;
623 /* Remove the ".codeset" part from the locale. */
624 dot = strchr (locale, '.');
625 if (dot != NULL)
627 const char *codeset_end;
628 char *shorter_locale;
630 codeset_end = strpbrk (dot + 1, "_@+,");
631 if (codeset_end == NULL)
632 codeset_end = dot + strlen (dot);
634 shorter_locale = (char *) xmalloc (strlen (locale));
635 memcpy (shorter_locale, locale, dot - locale);
636 strcpy (shorter_locale + (dot - locale), codeset_end);
637 locale = shorter_locale;
640 /* If the territory is the language's principal territory, drop it. */
641 for (i = 0; i < SIZEOF (locales_with_principal_territory); i++)
642 if (strcmp (locale, locales_with_principal_territory[i]) == 0)
644 const char *language_end;
645 size_t len;
646 char *shorter_locale;
648 language_end = strchr (locale, '_');
649 if (language_end == NULL)
650 abort ();
652 len = language_end - locale;
653 shorter_locale = (char *) xmalloc (len + 1);
654 memcpy (shorter_locale, locale, len);
655 shorter_locale[len] = '\0';
656 locale = shorter_locale;
657 break;
660 return locale;
664 /* Return the language of a locale. */
665 static const char *
666 language_of_locale (const char *locale)
668 const char *language_end;
670 language_end = strpbrk (locale, "_.@+,");
671 if (language_end != NULL)
673 size_t len;
674 char *result;
676 len = language_end - locale;
677 result = (char *) xmalloc (len + 1);
678 memcpy (result, locale, len);
679 result[len] = '\0';
681 return result;
683 else
684 return locale;
688 /* Return the most likely desired charset for the PO file, as a portable
689 charset name. */
690 static const char *
691 canonical_locale_charset ()
693 const char *tmp;
694 char *old_LC_ALL;
695 const char *charset;
697 /* Save LC_ALL environment variable. */
699 tmp = getenv ("LC_ALL");
700 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
702 xsetenv ("LC_ALL", locale, 1);
704 #ifdef HAVE_SETLOCALE
705 if (setlocale (LC_ALL, "") == NULL)
706 /* Nonexistent locale. Use anything. */
707 charset = "";
708 else
709 #endif
710 /* Get the locale's charset. */
711 charset = locale_charset ();
713 /* Restore LC_ALL environment variable. */
715 if (old_LC_ALL != NULL)
716 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
717 else
718 unsetenv ("LC_ALL");
720 #ifdef HAVE_SETLOCALE
721 setlocale (LC_ALL, "");
722 #endif
724 /* Canonicalize it. */
725 charset = po_charset_canonicalize (charset);
726 if (charset == NULL)
727 charset = po_charset_ascii;
729 return charset;
733 /* Return the English name of the language. */
734 static const char *
735 englishname_of_language ()
737 /* Derived from ISO 639. */
738 static struct { const char *code; const char *english; } table[] = {
739 { "aa", "Afar" },
740 { "ab", "Abkhazian" },
741 { "ae", "Avestan" },
742 { "af", "Afrikaans" },
743 { "ak", "Akan" },
744 { "am", "Amharic" },
745 { "an", "Aragonese" },
746 { "ar", "Arabic" },
747 { "as", "Assamese" },
748 { "av", "Avaric" },
749 { "ay", "Aymara" },
750 { "az", "Azerbaijani" },
751 { "ba", "Bashkir" },
752 { "be", "Belarusian" },
753 { "bg", "Bulgarian" },
754 { "bh", "Bihari" },
755 { "bi", "Bislama" },
756 { "bm", "Bambara" },
757 { "bn", "Bengali" },
758 { "bo", "Tibetan" },
759 { "br", "Breton" },
760 { "bs", "Bosnian" },
761 { "ca", "Catalan" },
762 { "ce", "Chechen" },
763 { "ch", "Chamorro" },
764 { "co", "Corsican" },
765 { "cr", "Cree" },
766 { "cs", "Czech" },
767 { "cu", "Church Slavic" },
768 { "cv", "Chuvash" },
769 { "cy", "Welsh" },
770 { "da", "Danish" },
771 { "de", "German" },
772 { "dv", "Divehi" },
773 { "dz", "Dzongkha" },
774 { "ee", "Ewe" },
775 { "el", "Greek" },
776 { "en", "English" },
777 { "eo", "Esperanto" },
778 { "es", "Spanish" },
779 { "et", "Estonian" },
780 { "eu", "Basque" },
781 { "fa", "Persian" },
782 { "ff", "Fulah" },
783 { "fi", "Finnish" },
784 { "fj", "Fijian" },
785 { "fo", "Faroese" },
786 { "fr", "French" },
787 { "fy", "Frisian" },
788 { "ga", "Irish" },
789 { "gd", "Scots" },
790 { "gl", "Galician" },
791 { "gn", "Guarani" },
792 { "gu", "Gujarati" },
793 { "gv", "Manx" },
794 { "ha", "Hausa" },
795 { "he", "Hebrew" },
796 { "hi", "Hindi" },
797 { "ho", "Hiri Motu" },
798 { "hr", "Croatian" },
799 { "ht", "Haitian" },
800 { "hu", "Hungarian" },
801 { "hy", "Armenian" },
802 { "hz", "Herero" },
803 { "ia", "Interlingua" },
804 { "id", "Indonesian" },
805 { "ie", "Interlingue" },
806 { "ig", "Igbo" },
807 { "ii", "Sichuan Yi" },
808 { "ik", "Inupiak" },
809 { "is", "Icelandic" },
810 { "it", "Italian" },
811 { "iu", "Inuktitut" },
812 { "ja", "Japanese" },
813 { "jw", "Javanese" },
814 { "ka", "Georgian" },
815 { "kg", "Kongo" },
816 { "ki", "Kikuyu" },
817 { "kj", "Kuanyama" },
818 { "kk", "Kazakh" },
819 { "kl", "Kalaallisut" },
820 { "km", "Khmer" },
821 { "kn", "Kannada" },
822 { "ko", "Korean" },
823 { "kr", "Kanuri" },
824 { "ks", "Kashmiri" },
825 { "ku", "Kurdish" },
826 { "kv", "Komi" },
827 { "kw", "Cornish" },
828 { "ky", "Kirghiz" },
829 { "kok", "Konkani" },
830 { "la", "Latin" },
831 { "lb", "Letzeburgesch" },
832 { "lg", "Ganda" },
833 { "li", "Limburgish" },
834 { "ln", "Lingala" },
835 { "lo", "Laotian" },
836 { "lt", "Lithuanian" },
837 { "lu", "Luba-Katanga" },
838 { "lv", "Latvian" },
839 { "mg", "Malagasy" },
840 { "mh", "Marshall" },
841 { "mi", "Maori" },
842 { "mk", "Macedonian" },
843 { "ml", "Malayalam" },
844 { "mn", "Mongolian" },
845 { "mo", "Moldavian" },
846 { "mr", "Marathi" },
847 { "ms", "Malay" },
848 { "mt", "Maltese" },
849 { "my", "Burmese" },
850 { "mni", "Manipuri" },
851 { "na", "Nauru" },
852 { "nb", "Norwegian Bokmal" },
853 { "nd", "North Ndebele" },
854 { "ne", "Nepali" },
855 { "ng", "Ndonga" },
856 { "nl", "Dutch" },
857 { "nn", "Norwegian Nynorsk" },
858 { "no", "Norwegian" },
859 { "nr", "South Ndebele" },
860 { "nv", "Navajo" },
861 { "ny", "Nyanja" },
862 { "oc", "Occitan" },
863 { "oj", "Ojibwa" },
864 { "om", "(Afan) Oromo" },
865 { "or", "Oriya" },
866 { "os", "Ossetian" },
867 { "pa", "Punjabi" },
868 { "pi", "Pali" },
869 { "pl", "Polish" },
870 { "ps", "Pashto" },
871 { "pt", "Portuguese" },
872 { "qu", "Quechua" },
873 { "rm", "Rhaeto-Roman" },
874 { "rn", "Kirundi" },
875 { "ro", "Romanian" },
876 { "ru", "Russian" },
877 { "rw", "Kinyarwanda" },
878 { "sa", "Sanskrit" },
879 { "sc", "Sardinian" },
880 { "sd", "Sindhi" },
881 { "se", "Northern Sami" },
882 { "sg", "Sango" },
883 { "si", "Sinhalese" },
884 { "sk", "Slovak" },
885 { "sl", "Slovenian" },
886 { "sm", "Samoan" },
887 { "sn", "Shona" },
888 { "so", "Somali" },
889 { "sq", "Albanian" },
890 { "sr", "Serbian" },
891 { "ss", "Siswati" },
892 { "st", "Sesotho" },
893 { "su", "Sundanese" },
894 { "sv", "Swedish" },
895 { "sw", "Swahili" },
896 { "ta", "Tamil" },
897 { "te", "Telugu" },
898 { "tg", "Tajik" },
899 { "th", "Thai" },
900 { "ti", "Tigrinya" },
901 { "tk", "Turkmen" },
902 { "tl", "Tagalog" },
903 { "tn", "Setswana" },
904 { "to", "Tonga" },
905 { "tr", "Turkish" },
906 { "ts", "Tsonga" },
907 { "tt", "Tatar" },
908 { "tw", "Twi" },
909 { "ty", "Tahitian" },
910 { "ug", "Uighur" },
911 { "uk", "Ukrainian" },
912 { "ur", "Urdu" },
913 { "uz", "Uzbek" },
914 { "ve", "Venda" },
915 { "vi", "Vietnamese" },
916 { "vo", "Volapuk" },
917 { "wo", "Wolof" },
918 { "wen", "Sorbian" },
919 { "xh", "Xhosa" },
920 { "yi", "Yiddish" },
921 { "yo", "Yoruba" },
922 { "za", "Zhuang" },
923 { "zh", "Chinese" },
924 { "zu", "Zulu" }
926 size_t i;
928 for (i = 0; i < SIZEOF (table); i ++)
929 if (strcmp (table[i].code, language) == 0)
930 return table[i].english;
932 return xasprintf ("Language %s", language);
936 /* Construct the value for the PACKAGE name. */
937 static const char *
938 project_id ()
940 const char *gettextlibdir;
941 char *prog;
942 char *argv[3];
943 pid_t child;
944 int fd[1];
945 FILE *fp;
946 char *line;
947 size_t linesize;
948 size_t linelen;
949 int exitstatus;
951 gettextlibdir = getenv ("GETTEXTLIBDIR");
952 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
953 gettextlibdir = relocate (LIBDIR "/gettext");
955 prog = concatenated_pathname (gettextlibdir, "project-id", NULL);
957 /* Call the project-id shell script. */
958 argv[0] = "/bin/sh";
959 argv[1] = prog;
960 argv[2] = NULL;
961 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false,
962 fd);
963 if (child == -1)
964 goto failed;
966 /* Retrieve its result. */
967 fp = fdopen (fd[0], "r");
968 if (fp == NULL)
970 error (0, errno, _("fdopen() failed"));
971 goto failed;
974 line = NULL; linesize = 0;
975 linelen = getline (&line, &linesize, fp);
976 if (linelen == (size_t)(-1))
978 error (0, 0, _("%s subprocess I/O error"), prog);
979 goto failed;
981 if (linelen > 0 && line[linelen - 1] == '\n')
982 line[linelen - 1] = '\0';
984 fclose (fp);
986 /* Remove zombie process from process list, and retrieve exit status. */
987 exitstatus = wait_subprocess (child, prog, false, false, true, false);
988 if (exitstatus != 0)
990 error (0, 0, _("%s subprocess failed with exit code %d"),
991 prog, exitstatus);
992 goto failed;
995 return line;
997 failed:
998 return "PACKAGE";
1002 /* Construct the value for the Project-Id-Version field. */
1003 static const char *
1004 project_id_version ()
1006 const char *gettextlibdir;
1007 char *prog;
1008 char *argv[4];
1009 pid_t child;
1010 int fd[1];
1011 FILE *fp;
1012 char *line;
1013 size_t linesize;
1014 size_t linelen;
1015 int exitstatus;
1017 gettextlibdir = getenv ("GETTEXTLIBDIR");
1018 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
1019 gettextlibdir = relocate (LIBDIR "/gettext");
1021 prog = concatenated_pathname (gettextlibdir, "project-id", NULL);
1023 /* Call the project-id shell script. */
1024 argv[0] = "/bin/sh";
1025 argv[1] = prog;
1026 argv[2] = "yes";
1027 argv[3] = NULL;
1028 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false,
1029 fd);
1030 if (child == -1)
1031 goto failed;
1033 /* Retrieve its result. */
1034 fp = fdopen (fd[0], "r");
1035 if (fp == NULL)
1037 error (0, errno, _("fdopen() failed"));
1038 goto failed;
1041 line = NULL; linesize = 0;
1042 linelen = getline (&line, &linesize, fp);
1043 if (linelen == (size_t)(-1))
1045 error (0, 0, _("%s subprocess I/O error"), prog);
1046 goto failed;
1048 if (linelen > 0 && line[linelen - 1] == '\n')
1049 line[linelen - 1] = '\0';
1051 fclose (fp);
1053 /* Remove zombie process from process list, and retrieve exit status. */
1054 exitstatus = wait_subprocess (child, prog, false, false, true, false);
1055 if (exitstatus != 0)
1057 error (0, 0, _("%s subprocess failed with exit code %d"),
1058 prog, exitstatus);
1059 goto failed;
1062 return line;
1064 failed:
1065 return "PACKAGE VERSION";
1069 /* Construct the value for the PO-Revision-Date field. */
1070 static const char *
1071 po_revision_date (const char *header)
1073 if (no_translator)
1074 /* Because the PO file is automatically generated, we use the
1075 POT-Creation-Date, not the current time. */
1076 return get_field (header, "POT-Creation-Date");
1077 else
1079 /* Assume the translator will modify the PO file now. */
1080 time_t now;
1082 time (&now);
1083 return po_strftime (&now);
1088 /* Returns the struct passwd entry for the current user. */
1089 static struct passwd *
1090 get_user_pwd ()
1092 #if HAVE_PWD_H /* Only Unix, not native Woe32. */
1093 const char *username;
1094 struct passwd *userpasswd;
1096 /* 1. attempt: getpwnam(getenv("USER")) */
1097 username = getenv ("USER");
1098 if (username != NULL)
1100 errno = 0;
1101 userpasswd = getpwnam (username);
1102 if (userpasswd != NULL)
1103 return userpasswd;
1104 if (errno != 0)
1105 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username);
1108 /* 2. attempt: getpwnam(getlogin()) */
1109 username = getlogin ();
1110 if (username != NULL)
1112 errno = 0;
1113 userpasswd = getpwnam (username);
1114 if (userpasswd != NULL)
1115 return userpasswd;
1116 if (errno != 0)
1117 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username);
1120 /* 3. attempt: getpwuid(getuid()) */
1121 errno = 0;
1122 userpasswd = getpwuid (getuid ());
1123 if (userpasswd != NULL)
1124 return userpasswd;
1125 if (errno != 0)
1126 error (EXIT_FAILURE, errno, "getpwuid(\"%d\")", getuid ());
1127 #endif
1129 return NULL;
1133 /* Return the user's full name. */
1134 static const char *
1135 get_user_fullname ()
1137 struct passwd *pwd;
1138 const char *fullname;
1139 const char *fullname_end;
1140 char *result;
1142 pwd = get_user_pwd ();
1143 #if HAVE_PWD_H
1144 if (pwd != NULL)
1146 /* Return the pw_gecos field, upto the first comma (if any). */
1147 fullname = pwd->pw_gecos;
1148 fullname_end = strchr (fullname, ',');
1149 if (fullname_end == NULL)
1150 fullname_end = fullname + strlen (fullname);
1152 result = (char *) xmalloc (fullname_end - fullname + 1);
1153 memcpy (result, fullname, fullname_end - fullname);
1154 result[fullname_end - fullname] = '\0';
1156 return result;
1158 #endif
1160 return NULL;
1164 /* Return the user's email address. */
1165 static const char *
1166 get_user_email ()
1168 const char *prog = relocate (LIBDIR "/gettext/user-email");
1169 char *argv[4];
1170 pid_t child;
1171 int fd[1];
1172 FILE *fp;
1173 char *line;
1174 size_t linesize;
1175 size_t linelen;
1176 int exitstatus;
1178 /* Ask the user for his email address. */
1179 argv[0] = "/bin/sh";
1180 argv[1] = (char *) prog;
1181 argv[2] = (char *) _("\
1182 The new message catalog should contain your email address, so that users can\n\
1183 give you feedback about the translations, and so that maintainers can contact\n\
1184 you in case of unexpected technical problems.\n");
1185 argv[3] = NULL;
1186 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false,
1187 fd);
1188 if (child == -1)
1189 goto failed;
1191 /* Retrieve his answer. */
1192 fp = fdopen (fd[0], "r");
1193 if (fp == NULL)
1195 error (0, errno, _("fdopen() failed"));
1196 goto failed;
1199 line = NULL; linesize = 0;
1200 linelen = getline (&line, &linesize, fp);
1201 if (linelen == (size_t)(-1))
1203 error (0, 0, _("%s subprocess I/O error"), prog);
1204 goto failed;
1206 if (linelen > 0 && line[linelen - 1] == '\n')
1207 line[linelen - 1] = '\0';
1209 fclose (fp);
1211 /* Remove zombie process from process list, and retrieve exit status. */
1212 exitstatus = wait_subprocess (child, prog, false, false, true, false);
1213 if (exitstatus != 0)
1215 error (0, 0, _("%s subprocess failed with exit code %d"),
1216 prog, exitstatus);
1217 goto failed;
1220 return line;
1222 failed:
1223 return "EMAIL@ADDRESS";
1227 /* Construct the value for the Last-Translator field. */
1228 static const char *
1229 last_translator ()
1231 if (no_translator)
1232 return "Automatically generated";
1233 else
1235 const char *fullname = get_user_fullname ();
1236 const char *email = get_user_email ();
1238 if (fullname != NULL)
1239 return xasprintf ("%s <%s>", fullname, email);
1240 else
1241 return xasprintf ("<%s>", email);
1246 /* Return the language team's mailing list address or homepage URL. */
1247 static const char *
1248 language_team_address ()
1250 const char *prog = relocate (PROJECTSDIR "/team-address");
1251 char *argv[7];
1252 pid_t child;
1253 int fd[1];
1254 FILE *fp;
1255 char *line;
1256 size_t linesize;
1257 size_t linelen;
1258 int exitstatus;
1260 /* Call the team-address shell script. */
1261 argv[0] = "/bin/sh";
1262 argv[1] = (char *) prog;
1263 argv[2] = (char *) relocate (PROJECTSDIR);
1264 argv[3] = (char *) relocate (LIBDIR "/gettext");
1265 argv[4] = (char *) catalogname;
1266 argv[5] = (char *) language;
1267 argv[6] = NULL;
1268 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false,
1269 fd);
1270 if (child == -1)
1271 goto failed;
1273 /* Retrieve its result. */
1274 fp = fdopen (fd[0], "r");
1275 if (fp == NULL)
1277 error (0, errno, _("fdopen() failed"));
1278 goto failed;
1281 line = NULL; linesize = 0;
1282 linelen = getline (&line, &linesize, fp);
1283 if (linelen == (size_t)(-1))
1284 line = "";
1285 else if (linelen > 0 && line[linelen - 1] == '\n')
1286 line[linelen - 1] = '\0';
1288 fclose (fp);
1290 /* Remove zombie process from process list, and retrieve exit status. */
1291 exitstatus = wait_subprocess (child, prog, false, false, true, false);
1292 if (exitstatus != 0)
1294 error (0, 0, _("%s subprocess failed with exit code %d"),
1295 prog, exitstatus);
1296 goto failed;
1299 return line;
1301 failed:
1302 return "";
1306 /* Construct the value for the Language-Team field. */
1307 static const char *
1308 language_team ()
1310 if (no_translator)
1311 return "none";
1312 else
1314 const char *englishname = englishname_of_language ();
1315 const char *address = language_team_address ();
1317 if (address != NULL && address[0] != '\0')
1318 return xasprintf ("%s %s", englishname, address);
1319 else
1320 return englishname;
1325 /* Construct the value for the MIME-Version field. */
1326 static const char *
1327 mime_version ()
1329 return "1.0";
1333 /* Construct the value for the Content-Type field. */
1334 static const char *
1335 content_type (const char *header)
1337 bool was_utf8;
1338 const char *old_field;
1340 /* If the POT file contains charset=UTF-8, it means that the POT file
1341 contains non-ASCII characters, and we keep the UTF-8 encoding.
1342 Otherwise, when the POT file is plain ASCII, we use the locale's
1343 encoding. */
1344 was_utf8 = false;
1345 old_field = get_field (header, "Content-Type");
1346 if (old_field != NULL)
1348 const char *charsetstr = strstr (old_field, "charset=");
1350 if (charsetstr != NULL)
1352 charsetstr += strlen ("charset=");
1353 was_utf8 = (c_strcasecmp (charsetstr, "UTF-8") == 0);
1356 return xasprintf ("text/plain; charset=%s",
1357 was_utf8 ? "UTF-8" : canonical_locale_charset ());
1361 /* Construct the value for the Content-Transfer-Encoding field. */
1362 static const char *
1363 content_transfer_encoding ()
1365 return "8bit";
1369 /* Construct the value for the Plural-Forms field. */
1370 static const char *
1371 plural_forms ()
1373 size_t i;
1375 /* Search for a formula depending on the catalogname. */
1376 for (i = 0; i < plural_table_size; i++)
1377 if (strcmp (plural_table[i].lang, catalogname) == 0)
1378 return plural_table[i].value;
1380 /* Search for a formula depending on the language only. */
1381 for (i = 0; i < plural_table_size; i++)
1382 if (strcmp (plural_table[i].lang, language) == 0)
1383 return plural_table[i].value;
1385 return NULL;
1389 static struct
1391 const char *name;
1392 const char * (*getter0) (void);
1393 const char * (*getter1) (const char *header);
1395 fields[] =
1397 { "Project-Id-Version", project_id_version, NULL },
1398 { "PO-Revision-Date", NULL, po_revision_date },
1399 { "Last-Translator", last_translator, NULL },
1400 { "Language-Team", language_team, NULL },
1401 { "MIME-Version", mime_version, NULL },
1402 { "Content-Type", NULL, content_type },
1403 { "Content-Transfer-Encoding", content_transfer_encoding, NULL },
1404 { "Plural-Forms", plural_forms, NULL }
1407 #define NFIELDS SIZEOF (fields)
1408 #define FIELD_LAST_TRANSLATOR 2
1411 /* Retrieve a freshly allocated copy of a field's value. */
1412 static char *
1413 get_field (const char *header, const char *field)
1415 size_t len = strlen (field);
1416 const char *line;
1418 for (line = header;;)
1420 if (strncmp (line, field, len) == 0
1421 && line[len] == ':' && line[len + 1] == ' ')
1423 const char *value_start;
1424 const char *value_end;
1425 char *value;
1427 value_start = line + len + 2;
1428 value_end = strchr (value_start, '\n');
1429 if (value_end == NULL)
1430 value_end = value_start + strlen (value_start);
1432 value = (char *) xmalloc (value_end - value_start + 1);
1433 memcpy (value, value_start, value_end - value_start);
1434 value[value_end - value_start] = '\0';
1436 return value;
1439 line = strchr (line, '\n');
1440 if (line != NULL)
1441 line++;
1442 else
1443 break;
1446 return NULL;
1449 /* Add a field with value to a header, and return the new header. */
1450 static char *
1451 put_field (const char *old_header, const char *field, const char *value)
1453 size_t len = strlen (field);
1454 const char *line;
1455 char *new_header;
1456 char *p;
1458 for (line = old_header;;)
1460 if (strncmp (line, field, len) == 0
1461 && line[len] == ':' && line[len + 1] == ' ')
1463 const char *value_start;
1464 const char *value_end;
1466 value_start = line + len + 2;
1467 value_end = strchr (value_start, '\n');
1468 if (value_end == NULL)
1469 value_end = value_start + strlen (value_start);
1471 new_header = (char *) xmalloc (strlen (old_header)
1472 - (value_end - value_start)
1473 + strlen (value)
1474 + (*value_end != '\n' ? 1 : 0)
1475 + 1);
1476 p = new_header;
1477 memcpy (p, old_header, value_start - old_header);
1478 p += value_start - old_header;
1479 memcpy (p, value, strlen (value));
1480 p += strlen (value);
1481 if (*value_end != '\n')
1482 *p++ = '\n';
1483 strcpy (p, value_end);
1485 return new_header;
1488 line = strchr (line, '\n');
1489 if (line != NULL)
1490 line++;
1491 else
1492 break;
1495 new_header = (char *) xmalloc (strlen (old_header) + 1
1496 + len + 2 + strlen (value) + 1
1497 + 1);
1498 p = new_header;
1499 memcpy (p, old_header, strlen (old_header));
1500 p += strlen (old_header);
1501 if (p > new_header && p[-1] != '\n')
1502 *p++ = '\n';
1503 memcpy (p, field, len);
1504 p += len;
1505 *p++ = ':';
1506 *p++ = ' ';
1507 memcpy (p, value, strlen (value));
1508 p += strlen (value);
1509 *p++ = '\n';
1510 *p = '\0';
1512 return new_header;
1516 /* Return the title format string. */
1517 static const char *
1518 get_title ()
1520 /* This is tricky. We want the translation in the given locale specified by
1521 the command line, not the current locale. But we want it in the encoding
1522 that we put into the header entry, not the encoding of that locale.
1523 We could avoid the use of OUTPUT_CHARSET by using a separate message
1524 catalog and bind_textdomain_codeset(), but that doesn't seem worth the
1525 trouble for one single message. */
1526 const char *encoding;
1527 const char *tmp;
1528 char *old_LC_ALL;
1529 char *old_LANGUAGE;
1530 char *old_OUTPUT_CHARSET;
1531 const char *msgid;
1532 const char *english;
1533 const char *result;
1535 encoding = canonical_locale_charset ();
1537 /* First, the English title. */
1538 english = xasprintf ("%s translations for %%s package",
1539 englishname_of_language ());
1541 /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */
1543 tmp = getenv ("LC_ALL");
1544 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
1546 tmp = getenv ("LANGUAGE");
1547 old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL);
1549 tmp = getenv ("OUTPUT_CHARSET");
1550 old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL);
1552 xsetenv ("LC_ALL", locale, 1);
1553 unsetenv ("LANGUAGE");
1554 xsetenv ("OUTPUT_CHARSET", encoding, 1);
1556 #ifdef HAVE_SETLOCALE
1557 if (setlocale (LC_ALL, "") == NULL)
1558 /* Nonexistent locale. Use the English title. */
1559 result = english;
1560 else
1561 #endif
1563 /* Fetch the translation. */
1564 /* TRANSLATORS: "English" needs to be replaced by your language.
1565 For example in it.po write "Traduzioni italiani ...",
1566 *not* "Traduzioni inglesi ...". */
1567 msgid = N_("English translations for %s package");
1568 result = gettext (msgid);
1569 if (result != msgid && strcmp (result, msgid) != 0)
1570 /* Use the English and the foreign title. */
1571 result = xasprintf ("%s\n%s", english, result);
1572 else
1573 /* No translation found. Use the English title. */
1574 result = english;
1577 /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */
1579 if (old_LC_ALL != NULL)
1580 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
1581 else
1582 unsetenv ("LC_ALL");
1584 if (old_LANGUAGE != NULL)
1585 xsetenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE);
1586 else
1587 unsetenv ("LANGUAGE");
1589 if (old_OUTPUT_CHARSET != NULL)
1590 xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET);
1591 else
1592 unsetenv ("OUTPUT_CHARSET");
1594 #ifdef HAVE_SETLOCALE
1595 setlocale (LC_ALL, "");
1596 #endif
1598 return result;
1602 /* Perform a set of substitutions in a string and return the resulting
1603 string. When subst[j][0] found, it is replaced with subst[j][1].
1604 subst[j][0] must not be the empty string. */
1605 static const char *
1606 subst_string (const char *str,
1607 unsigned int nsubst, const char *(*subst)[2])
1609 if (nsubst > 0)
1611 char *malloced = NULL;
1612 size_t *substlen;
1613 size_t i;
1614 unsigned int j;
1616 substlen = (size_t *) xallocsa (nsubst * sizeof (size_t));
1617 for (j = 0; j < nsubst; j++)
1619 substlen[j] = strlen (subst[j][0]);
1620 if (substlen[j] == 0)
1621 abort ();
1624 for (i = 0;;)
1626 if (str[i] == '\0')
1627 break;
1628 for (j = 0; j < nsubst; j++)
1629 if (*(str + i) == *subst[j][0]
1630 && strncmp (str + i, subst[j][0], substlen[j]) == 0)
1632 size_t replacement_len = strlen (subst[j][1]);
1633 size_t new_len = strlen (str) - substlen[j] + replacement_len;
1634 char *new_str = (char *) xmalloc (new_len + 1);
1635 memcpy (new_str, str, i);
1636 memcpy (new_str + i, subst[j][1], replacement_len);
1637 strcpy (new_str + i + replacement_len, str + i + substlen[j]);
1638 if (malloced != NULL)
1639 free (malloced);
1640 str = new_str;
1641 malloced = new_str;
1642 i += replacement_len;
1643 break;
1645 if (j == nsubst)
1646 i++;
1649 freesa (substlen);
1652 return str;
1655 /* Perform a set of substitutions on each string of a string list.
1656 When subst[j][0] found, it is replaced with subst[j][1]. subst[j][0]
1657 must not be the empty string. */
1658 static void
1659 subst_string_list (string_list_ty *slp,
1660 unsigned int nsubst, const char *(*subst)[2])
1662 size_t j;
1664 for (j = 0; j < slp->nitems; j++)
1665 slp->item[j] = subst_string (slp->item[j], nsubst, subst);
1669 /* Fill the templates in all fields of the header entry. */
1670 static msgdomain_list_ty *
1671 fill_header (msgdomain_list_ty *mdlp)
1673 /* Cache the strings filled in, for use when there are multiple domains
1674 and a header entry for each domain. */
1675 const char *field_value[NFIELDS];
1676 size_t k, j, i;
1678 for (i = 0; i < NFIELDS; i++)
1679 field_value[i] = NULL;
1681 for (k = 0; k < mdlp->nitems; k++)
1683 message_list_ty *mlp = mdlp->item[k]->messages;
1685 if (mlp->nitems > 0)
1687 message_ty *header_mp = NULL;
1688 char *header;
1690 /* Search the header entry. */
1691 for (j = 0; j < mlp->nitems; j++)
1692 if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
1694 header_mp = mlp->item[j];
1695 break;
1698 /* If it wasn't found, provide one. */
1699 if (header_mp == NULL)
1701 static lex_pos_ty pos = { __FILE__, __LINE__ };
1703 header_mp = message_alloc ("", NULL, "", 1, &pos);
1704 message_list_prepend (mlp, header_mp);
1707 header = xstrdup (header_mp->msgstr);
1709 /* Fill in the fields. */
1710 for (i = 0; i < NFIELDS; i++)
1712 if (field_value[i] == NULL)
1713 field_value[i] =
1714 (fields[i].getter1 != NULL
1715 ? fields[i].getter1 (header)
1716 : fields[i].getter0 ());
1718 if (field_value[i] != NULL)
1720 char *old_header = header;
1721 header = put_field (header, fields[i].name, field_value[i]);
1722 free (old_header);
1726 /* Replace the old translation in the header entry. */
1727 header_mp->msgstr = header;
1728 header_mp->msgstr_len = strlen (header) + 1;
1730 /* Update the comments in the header entry. */
1731 if (header_mp->comment != NULL)
1733 const char *subst[4][2];
1734 const char *id;
1735 time_t now;
1737 id = project_id ();
1738 subst[0][0] = "SOME DESCRIPTIVE TITLE";
1739 subst[0][1] = xasprintf (get_title (), id, id);
1740 subst[1][0] = "PACKAGE";
1741 subst[1][1] = id;
1742 subst[2][0] = "FIRST AUTHOR <EMAIL@ADDRESS>";
1743 subst[2][1] = field_value[FIELD_LAST_TRANSLATOR];
1744 subst[3][0] = "YEAR";
1745 subst[3][1] =
1746 xasprintf ("%d",
1747 (time (&now), (localtime (&now))->tm_year + 1900));
1748 subst_string_list (header_mp->comment, SIZEOF (subst), subst);
1751 /* Finally remove the fuzzy attribute. */
1752 header_mp->is_fuzzy = false;
1756 return mdlp;
1760 /* Update the msgstr plural entries according to the nplurals count. */
1761 static msgdomain_list_ty *
1762 update_msgstr_plurals (msgdomain_list_ty *mdlp)
1764 size_t k;
1766 for (k = 0; k < mdlp->nitems; k++)
1768 message_list_ty *mlp = mdlp->item[k]->messages;
1769 message_ty *header_entry;
1770 unsigned long int nplurals;
1771 char *untranslated_plural_msgstr;
1772 size_t j;
1774 header_entry = message_list_search (mlp, "");
1775 nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL);
1776 untranslated_plural_msgstr = (char *) xmalloc (nplurals);
1777 memset (untranslated_plural_msgstr, '\0', nplurals);
1779 for (j = 0; j < mlp->nitems; j++)
1781 message_ty *mp = mlp->item[j];
1782 bool is_untranslated;
1783 const char *p;
1784 const char *pend;
1786 if (mp->msgid_plural != NULL)
1788 /* Test if mp is untranslated. (It most likely is.) */
1789 is_untranslated = true;
1790 for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++)
1791 if (*p != '\0')
1793 is_untranslated = false;
1794 break;
1796 if (is_untranslated)
1798 /* Change mp->msgstr_len consecutive empty strings into
1799 nplurals consecutive empty strings. */
1800 if (nplurals > mp->msgstr_len)
1801 mp->msgstr = untranslated_plural_msgstr;
1802 mp->msgstr_len = nplurals;
1807 return mdlp;