1 /* Initializes a new PO file.
2 Copyright (C) 2001-2015 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 3 of the License, or
8 (at your option) 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, see <http://www.gnu.org/licenses/>. */
34 #include <sys/types.h>
54 #include "error-progname.h"
56 #include "relocatable.h"
59 #include "c-strcase.h"
61 #include "read-catalog.h"
63 #include "read-properties.h"
64 #include "read-stringtable.h"
65 #include "write-catalog.h"
67 #include "write-properties.h"
68 #include "write-stringtable.h"
70 #include "po-charset.h"
71 #include "localcharset.h"
72 #include "localename.h"
74 #include "plural-table.h"
75 #include "lang-table.h"
78 #include "concat-filename.h"
80 #include "xvasprintf.h"
81 #include "msgl-english.h"
82 #include "plural-count.h"
83 #include "spawn-pipe.h"
84 #include "wait-process.h"
87 #include "propername.h"
90 #define _(str) gettext (str)
93 /* Get F_OK. It is lacking from <fcntl.h> on Woe32. */
98 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
100 extern const char * _nl_expand_alias (const char *name);
103 static const char *locale;
105 /* Language (ISO-639 code) and optional territory (ISO-3166 code). */
106 static const char *catalogname;
108 /* Language (ISO-639 code). */
109 static const char *language;
111 /* If true, the user is not considered to be the translator. */
112 static bool no_translator;
115 static const struct option long_options[] =
117 { "color", optional_argument, NULL, CHAR_MAX + 5 },
118 { "help", no_argument, NULL, 'h' },
119 { "input", required_argument, NULL, 'i' },
120 { "locale", required_argument, NULL, 'l' },
121 { "no-translator", no_argument, NULL, CHAR_MAX + 1 },
122 { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
123 { "output-file", required_argument, NULL, 'o' },
124 { "properties-input", no_argument, NULL, 'P' },
125 { "properties-output", no_argument, NULL, 'p' },
126 { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 },
127 { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 },
128 { "style", required_argument, NULL, CHAR_MAX + 6 },
129 { "version", no_argument, NULL, 'V' },
130 { "width", required_argument, NULL, 'w' },
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))
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);
149 main (int argc, char **argv)
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;
160 /* Set program name for messages. */
161 set_program_name (argv[0]);
162 error_print_progname = maybe_print_progname;
164 #ifdef HAVE_SETLOCALE
165 /* Set locale via LC_ALL. */
166 setlocale (LC_ALL, "");
169 /* Set the text message domain. */
170 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
171 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
172 textdomain (PACKAGE);
174 /* Ensure that write errors on stdout are detected. */
175 atexit (close_stdout);
177 /* Set default values for variables. */
184 while ((opt = getopt_long (argc, argv, "hi:l:o:pPVw:", long_options, NULL))
188 case '\0': /* Long option. */
196 if (input_file != NULL)
198 error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
199 usage (EXIT_FAILURE);
209 output_file = optarg;
213 output_syntax = &output_format_properties;
217 input_syntax = &input_format_properties;
228 value = strtol (optarg, &endp, 10);
230 message_page_width_set (value);
235 no_translator = true;
238 case CHAR_MAX + 2: /* --no-wrap */
239 message_page_width_ignore ();
242 case CHAR_MAX + 3: /* --stringtable-input */
243 input_syntax = &input_format_stringtable;
246 case CHAR_MAX + 4: /* --stringtable-output */
247 output_syntax = &output_format_stringtable;
250 case CHAR_MAX + 5: /* --color */
251 if (handle_color_option (optarg) || color_test_mode)
252 usage (EXIT_FAILURE);
255 case CHAR_MAX + 6: /* --style */
256 handle_style_option (optarg);
260 usage (EXIT_FAILURE);
264 /* Version information is requested. */
267 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
268 /* xgettext: no-wrap */
269 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
270 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
271 This is free software: you are free to change and redistribute it.\n\
272 There is NO WARRANTY, to the extent permitted by law.\n\
275 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
279 /* Help is requested. */
281 usage (EXIT_SUCCESS);
283 /* Test for extraneous arguments. */
285 error (EXIT_FAILURE, 0, _("too many arguments"));
287 /* Search for the input file. */
288 if (input_file == NULL)
289 input_file = find_pot ();
291 /* Determine target locale. */
294 locale = gl_locale_name (LC_MESSAGES, "LC_MESSAGES");
295 if (strcmp (locale, "C") == 0)
297 multiline_error (xstrdup (""),
299 You are in a language indifferent environment. Please set\n\
300 your LANG environment variable, as described in the ABOUT-NLS\n\
301 file. This is necessary so you can test your translations.\n")));
306 const char *alias = _nl_expand_alias (locale);
310 catalogname = catalogname_for_locale (locale);
311 language = language_of_locale (locale);
313 /* Default output file name is CATALOGNAME.po. */
314 if (output_file == NULL)
316 output_file = xasprintf ("%s.po", catalogname);
318 /* But don't overwrite existing PO files. */
319 if (access (output_file, F_OK) == 0)
321 multiline_error (xstrdup (""),
323 Output file %s already exists.\n\
324 Please specify the locale through the --locale option or\n\
325 the output .po file through the --output-file option.\n"),
331 /* Read input file. */
332 result = read_catalog_file (input_file, input_syntax);
334 /* Fill the header entry. */
335 result = fill_header (result);
337 /* Initialize translations. */
338 if (strcmp (language, "en") == 0)
339 result = msgdomain_list_english (result);
341 result = update_msgstr_plurals (result);
343 /* Write the modified message list out. */
344 msgdomain_list_print (result, output_file, output_syntax, true, false);
347 fprintf (stderr, "\n");
348 fprintf (stderr, _("Created %s.\n"), output_file);
354 /* Display usage information and exit. */
358 if (status != EXIT_SUCCESS)
359 fprintf (stderr, _("Try '%s --help' for more information.\n"),
364 Usage: %s [OPTION]\n\
367 /* xgettext: no-wrap */
369 Creates a new PO file, initializing the meta information with values from the\n\
370 user's environment.\n\
374 Mandatory arguments to long options are mandatory for short options too.\n"));
377 Input file location:\n"));
379 -i, --input=INPUTFILE input POT file\n"));
381 If no input file is given, the current directory is searched for the POT file.\n\
382 If it is -, standard input is read.\n"));
385 Output file location:\n"));
387 -o, --output-file=FILE write output to specified PO file\n"));
389 If no output file is given, it depends on the --locale option or the user's\n\
390 locale setting. If it is -, the results are written to standard output.\n"));
393 Input file syntax:\n"));
395 -P, --properties-input input file is in Java .properties syntax\n"));
397 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
400 Output details:\n"));
402 -l, --locale=LL_CC set target locale\n"));
404 --no-translator assume the PO file is automatically generated\n"));
406 --color use colors and other text attributes always\n\
407 --color=WHEN use colors and other text attributes if WHEN.\n\
408 WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
410 --style=STYLEFILE specify CSS style rule file for --color\n"));
412 -p, --properties-output write out a Java .properties file\n"));
414 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
416 -w, --width=NUMBER set output page width\n"));
418 --no-wrap do not break long message lines, longer than\n\
419 the output page width, into several lines\n"));
422 Informative output:\n"));
424 -h, --help display this help and exit\n"));
426 -V, --version output version information and exit\n"));
428 /* TRANSLATORS: The placeholder indicates the bug-reporting address
429 for this package. Please add _another line_ saying
430 "Report translation bugs to <...>\n" with the address for translation
431 bugs (typically your translation team's web or email address). */
432 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
440 /* Search for the POT file and return its name. */
448 dirp = opendir (".");
459 const char *name = dp->d_name;
460 size_t namlen = strlen (name);
462 if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0)
465 found = xstrdup (name);
468 multiline_error (xstrdup (""),
470 Found more than one .pot file.\n\
471 Please specify the input .pot file through the --input option.\n")));
472 usage (EXIT_FAILURE);
477 error (EXIT_FAILURE, errno, _("error reading current directory"));
482 error (EXIT_FAILURE, errno, _("error reading current directory"));
489 multiline_error (xstrdup (""),
491 Found no .pot file in the current directory.\n\
492 Please specify the input .pot file through the --input option.\n")));
493 usage (EXIT_FAILURE);
499 /* Return the gettext catalog name corresponding to a locale. If the locale
500 consists of a language and a territory, and the language is mainly spoken
501 in that territory, the territory is removed from the locale name.
502 For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de",
503 because the resulting catalog can be used as a default for all "de_XX",
506 catalogname_for_locale (const char *locale)
508 static const char *locales_with_principal_territory[] = {
509 /* Language Main territory */
510 "ace_ID", /* Achinese Indonesia */
511 "af_ZA", /* Afrikaans South Africa */
512 "ak_GH", /* Akan Ghana */
513 "am_ET", /* Amharic Ethiopia */
514 "an_ES", /* Aragonese Spain */
515 "ang_GB", /* Old English Britain */
516 "arn_CL", /* Mapudungun Chile */
517 "as_IN", /* Assamese India */
518 "ast_ES", /* Asturian Spain */
519 "av_RU", /* Avaric Russia */
520 "awa_IN", /* Awadhi India */
521 "az_AZ", /* Azerbaijani Azerbaijan */
522 "ban_ID", /* Balinese Indonesia */
523 "be_BY", /* Belarusian Belarus */
524 "bej_SD", /* Beja Sudan */
525 "bem_ZM", /* Bemba Zambia */
526 "bg_BG", /* Bulgarian Bulgaria */
527 "bho_IN", /* Bhojpuri India */
528 "bik_PH", /* Bikol Philippines */
529 "bin_NG", /* Bini Nigeria */
530 "bm_ML", /* Bambara Mali */
531 "bn_IN", /* Bengali India */
532 "bo_CN", /* Tibetan China */
533 "br_FR", /* Breton France */
534 "bs_BA", /* Bosnian Bosnia */
535 "bug_ID", /* Buginese Indonesia */
536 "ca_ES", /* Catalan Spain */
537 "ce_RU", /* Chechen Russia */
538 "ceb_PH", /* Cebuano Philippines */
539 "co_FR", /* Corsican France */
540 "cr_CA", /* Cree Canada */
541 /* Don't put "crh_UZ" or "crh_UA" here. That would be asking for fruitless
542 political discussion. */
543 "cs_CZ", /* Czech Czech Republic */
544 "csb_PL", /* Kashubian Poland */
545 "cy_GB", /* Welsh Britain */
546 "da_DK", /* Danish Denmark */
547 "de_DE", /* German Germany */
548 "din_SD", /* Dinka Sudan */
549 "doi_IN", /* Dogri India */
550 "dsb_DE", /* Lower Sorbian Germany */
551 "dv_MV", /* Divehi Maldives */
552 "dz_BT", /* Dzongkha Bhutan */
553 "ee_GH", /* Éwé Ghana */
554 "el_GR", /* Greek Greece */
555 /* Don't put "en_GB" or "en_US" here. That would be asking for fruitless
556 political discussion. */
557 "es_ES", /* Spanish Spain */
558 "et_EE", /* Estonian Estonia */
559 "fa_IR", /* Persian Iran */
560 "fi_FI", /* Finnish Finland */
561 "fil_PH", /* Filipino Philippines */
562 "fj_FJ", /* Fijian Fiji */
563 "fo_FO", /* Faroese Faeroe Islands */
564 "fon_BJ", /* Fon Benin */
565 "fr_FR", /* French France */
566 "fur_IT", /* Friulian Italy */
567 "fy_NL", /* Western Frisian Netherlands */
568 "ga_IE", /* Irish Ireland */
569 "gd_GB", /* Scottish Gaelic Britain */
570 "gon_IN", /* Gondi India */
571 "gsw_CH", /* Swiss German Switzerland */
572 "gu_IN", /* Gujarati India */
573 "he_IL", /* Hebrew Israel */
574 "hi_IN", /* Hindi India */
575 "hil_PH", /* Hiligaynon Philippines */
576 "hr_HR", /* Croatian Croatia */
577 "hsb_DE", /* Upper Sorbian Germany */
578 "ht_HT", /* Haitian Haiti */
579 "hu_HU", /* Hungarian Hungary */
580 "hy_AM", /* Armenian Armenia */
581 "id_ID", /* Indonesian Indonesia */
582 "ig_NG", /* Igbo Nigeria */
583 "ii_CN", /* Sichuan Yi China */
584 "ilo_PH", /* Iloko Philippines */
585 "is_IS", /* Icelandic Iceland */
586 "it_IT", /* Italian Italy */
587 "ja_JP", /* Japanese Japan */
588 "jab_NG", /* Hyam Nigeria */
589 "jv_ID", /* Javanese Indonesia */
590 "ka_GE", /* Georgian Georgia */
591 "kab_DZ", /* Kabyle Algeria */
592 "kaj_NG", /* Jju Nigeria */
593 "kam_KE", /* Kamba Kenya */
594 "kmb_AO", /* Kimbundu Angola */
595 "kcg_NG", /* Tyap Nigeria */
596 "kdm_NG", /* Kagoma Nigeria */
597 "kg_CD", /* Kongo Democratic Republic of Congo */
598 "kk_KZ", /* Kazakh Kazakhstan */
599 "kl_GL", /* Kalaallisut Greenland */
600 "km_KH", /* Central Khmer Cambodia */
601 "kn_IN", /* Kannada India */
602 "ko_KR", /* Korean Korea (South) */
603 "kok_IN", /* Konkani India */
604 "kr_NG", /* Kanuri Nigeria */
605 "kru_IN", /* Kurukh India */
606 "lg_UG", /* Ganda Uganda */
607 "li_BE", /* Limburgish Belgium */
608 "lo_LA", /* Laotian Laos */
609 "lt_LT", /* Lithuanian Lithuania */
610 "lu_CD", /* Luba-Katanga Democratic Republic of Congo */
611 "lua_CD", /* Luba-Lulua Democratic Republic of Congo */
612 "luo_KE", /* Luo Kenya */
613 "lv_LV", /* Latvian Latvia */
614 "mad_ID", /* Madurese Indonesia */
615 "mag_IN", /* Magahi India */
616 "mai_IN", /* Maithili India */
617 "mak_ID", /* Makasar Indonesia */
618 "man_ML", /* Mandingo Mali */
619 "men_SL", /* Mende Sierra Leone */
620 "mg_MG", /* Malagasy Madagascar */
621 "mi_NZ", /* Maori New Zealand */
622 "min_ID", /* Minangkabau Indonesia */
623 "mk_MK", /* Macedonian Macedonia */
624 "ml_IN", /* Malayalam India */
625 "mn_MN", /* Mongolian Mongolia */
626 "mni_IN", /* Manipuri India */
627 "mos_BF", /* Mossi Burkina Faso */
628 "mr_IN", /* Marathi India */
629 "ms_MY", /* Malay Malaysia */
630 "mt_MT", /* Maltese Malta */
631 "mwr_IN", /* Marwari India */
632 "my_MM", /* Burmese Myanmar */
633 "na_NR", /* Nauru Nauru */
634 "nah_MX", /* Nahuatl Mexico */
635 "nap_IT", /* Neapolitan Italy */
636 "nb_NO", /* Norwegian Bokmål Norway */
637 "nds_DE", /* Low Saxon Germany */
638 "ne_NP", /* Nepali Nepal */
639 "nl_NL", /* Dutch Netherlands */
640 "nn_NO", /* Norwegian Nynorsk Norway */
641 "no_NO", /* Norwegian Norway */
642 "nr_ZA", /* South Ndebele South Africa */
643 "nso_ZA", /* Northern Sotho South Africa */
644 "nym_TZ", /* Nyamwezi Tanzania */
645 "nyn_UG", /* Nyankole Uganda */
646 "oc_FR", /* Occitan France */
647 "oj_CA", /* Ojibwa Canada */
648 "or_IN", /* Oriya India */
649 "pa_IN", /* Punjabi India */
650 "pag_PH", /* Pangasinan Philippines */
651 "pam_PH", /* Pampanga Philippines */
652 "pap_AN", /* Papiamento Netherlands Antilles - this line can be removed in 2018 */
653 "pbb_CO", /* Páez Colombia */
654 "pl_PL", /* Polish Poland */
655 "ps_AF", /* Pashto Afghanistan */
656 "pt_PT", /* Portuguese Portugal */
657 "raj_IN", /* Rajasthani India */
658 "rm_CH", /* Romansh Switzerland */
659 "rn_BI", /* Kirundi Burundi */
660 "ro_RO", /* Romanian Romania */
661 "ru_RU", /* Russian Russia */
662 "sa_IN", /* Sanskrit India */
663 "sah_RU", /* Yakut Russia */
664 "sas_ID", /* Sasak Indonesia */
665 "sat_IN", /* Santali India */
666 "sc_IT", /* Sardinian Italy */
667 "scn_IT", /* Sicilian Italy */
668 "sg_CF", /* Sango Central African Republic */
669 "shn_MM", /* Shan Myanmar */
670 "si_LK", /* Sinhala Sri Lanka */
671 "sid_ET", /* Sidamo Ethiopia */
672 "sk_SK", /* Slovak Slovakia */
673 "sl_SI", /* Slovenian Slovenia */
674 "smn_FI", /* Inari Sami Finland */
675 "sms_FI", /* Skolt Sami Finland */
676 "so_SO", /* Somali Somalia */
677 "sq_AL", /* Albanian Albania */
678 "sr_RS", /* Serbian Serbia */
679 "srr_SN", /* Serer Senegal */
680 "suk_TZ", /* Sukuma Tanzania */
681 "sus_GN", /* Susu Guinea */
682 "sv_SE", /* Swedish Sweden */
683 "te_IN", /* Telugu India */
684 "tem_SL", /* Timne Sierra Leone */
685 "tet_ID", /* Tetum Indonesia */
686 "tg_TJ", /* Tajik Tajikistan */
687 "th_TH", /* Thai Thailand */
688 "tiv_NG", /* Tiv Nigeria */
689 "tk_TM", /* Turkmen Turkmenistan */
690 "tl_PH", /* Tagalog Philippines */
691 "to_TO", /* Tonga Tonga */
692 "tr_TR", /* Turkish Turkey */
693 "tum_MW", /* Tumbuka Malawi */
694 "ug_CN", /* Uighur China */
695 "uk_UA", /* Ukrainian Ukraine */
696 "umb_AO", /* Umbundu Angola */
697 "ur_PK", /* Urdu Pakistan */
698 "uz_UZ", /* Uzbek Uzbekistan */
699 "ve_ZA", /* Venda South Africa */
700 "vi_VN", /* Vietnamese Vietnam */
701 "wa_BE", /* Walloon Belgium */
702 "wal_ET", /* Walamo Ethiopia */
703 "war_PH", /* Waray Philippines */
704 "wen_DE", /* Sorbian Germany */
705 "yao_MW", /* Yao Malawi */
706 "zap_MX" /* Zapotec Mexico */
711 /* Remove the ".codeset" part from the locale. */
712 dot = strchr (locale, '.');
715 const char *codeset_end;
716 char *shorter_locale;
718 codeset_end = strpbrk (dot + 1, "_@");
719 if (codeset_end == NULL)
720 codeset_end = dot + strlen (dot);
722 shorter_locale = XNMALLOC (strlen (locale), char);
723 memcpy (shorter_locale, locale, dot - locale);
724 strcpy (shorter_locale + (dot - locale), codeset_end);
725 locale = shorter_locale;
728 /* If the territory is the language's principal territory, drop it. */
729 for (i = 0; i < SIZEOF (locales_with_principal_territory); i++)
730 if (strcmp (locale, locales_with_principal_territory[i]) == 0)
732 const char *language_end;
734 char *shorter_locale;
736 language_end = strchr (locale, '_');
737 if (language_end == NULL)
740 len = language_end - locale;
741 shorter_locale = XNMALLOC (len + 1, char);
742 memcpy (shorter_locale, locale, len);
743 shorter_locale[len] = '\0';
744 locale = shorter_locale;
752 /* Return the language of a locale. */
754 language_of_locale (const char *locale)
756 const char *language_end;
758 language_end = strpbrk (locale, "_.@");
759 if (language_end != NULL)
764 len = language_end - locale;
765 result = XNMALLOC (len + 1, char);
766 memcpy (result, locale, len);
776 /* Return the most likely desired charset for the PO file, as a portable
779 canonical_locale_charset ()
785 /* Save LC_ALL environment variable. */
787 tmp = getenv ("LC_ALL");
788 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
790 xsetenv ("LC_ALL", locale, 1);
792 #ifdef HAVE_SETLOCALE
793 if (setlocale (LC_ALL, "") == NULL)
794 /* Nonexistent locale. Use anything. */
798 /* Get the locale's charset. */
799 charset = locale_charset ();
801 /* Restore LC_ALL environment variable. */
803 if (old_LC_ALL != NULL)
804 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
808 #ifdef HAVE_SETLOCALE
809 setlocale (LC_ALL, "");
812 /* Canonicalize it. */
813 charset = po_charset_canonicalize (charset);
815 charset = po_charset_ascii;
821 /* Return the English name of the language. */
823 englishname_of_language ()
827 for (i = 0; i < language_table_size; i++)
828 if (strcmp (language_table[i].code, language) == 0)
829 return language_table[i].english;
831 return xasprintf ("Language %s", language);
835 /* Construct the value for the PACKAGE name. */
837 project_id (const char *header)
839 const char *old_field;
840 const char *gettextlibdir;
851 /* Return the first part of the Project-Id-Version field if present, assuming
852 it was already filled in by xgettext. */
853 old_field = get_field (header, "Project-Id-Version");
854 if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0)
856 /* Remove the last word from old_field. */
857 const char *last_space;
859 last_space = strrchr (old_field, ' ');
860 if (last_space != NULL)
862 while (last_space > old_field && last_space[-1] == ' ')
864 if (last_space > old_field)
866 size_t package_len = last_space - old_field;
867 char *package = XNMALLOC (package_len + 1, char);
868 memcpy (package, old_field, package_len);
869 package[package_len] = '\0';
874 /* It contains no version, just a package name. */
878 gettextlibdir = getenv ("GETTEXTLIBDIR");
879 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
880 gettextlibdir = relocate (LIBDIR "/gettext");
882 prog = xconcatenated_filename (gettextlibdir, "project-id", NULL);
884 /* Call the project-id shell script. */
888 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false,
893 /* Retrieve its result. */
894 fp = fdopen (fd[0], "r");
897 error (0, errno, _("fdopen() failed"));
901 line = NULL; linesize = 0;
902 linelen = getline (&line, &linesize, fp);
903 if (linelen == (size_t)(-1))
905 error (0, 0, _("%s subprocess I/O error"), prog);
909 if (linelen > 0 && line[linelen - 1] == '\n')
910 line[linelen - 1] = '\0';
914 /* Remove zombie process from process list, and retrieve exit status. */
915 exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
918 error (0, 0, _("%s subprocess failed with exit code %d"),
930 /* Construct the value for the Project-Id-Version field. */
932 project_id_version (const char *header)
934 const char *old_field;
935 const char *gettextlibdir;
946 /* Return the old value if present, assuming it was already filled in by
948 old_field = get_field (header, "Project-Id-Version");
949 if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0)
952 gettextlibdir = getenv ("GETTEXTLIBDIR");
953 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
954 gettextlibdir = relocate (LIBDIR "/gettext");
956 prog = xconcatenated_filename (gettextlibdir, "project-id", NULL);
958 /* Call the project-id shell script. */
963 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false,
968 /* Retrieve its result. */
969 fp = fdopen (fd[0], "r");
972 error (0, errno, _("fdopen() failed"));
976 line = NULL; linesize = 0;
977 linelen = getline (&line, &linesize, fp);
978 if (linelen == (size_t)(-1))
980 error (0, 0, _("%s subprocess I/O error"), prog);
984 if (linelen > 0 && line[linelen - 1] == '\n')
985 line[linelen - 1] = '\0';
989 /* Remove zombie process from process list, and retrieve exit status. */
990 exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
993 error (0, 0, _("%s subprocess failed with exit code %d"),
1001 return "PACKAGE VERSION";
1005 /* Construct the value for the PO-Revision-Date field. */
1007 po_revision_date (const char *header)
1010 /* Because the PO file is automatically generated, we use the
1011 POT-Creation-Date, not the current time. */
1012 return get_field (header, "POT-Creation-Date");
1015 /* Assume the translator will modify the PO file now. */
1019 return po_strftime (&now);
1024 /* Returns the struct passwd entry for the current user. */
1025 static struct passwd *
1028 #if HAVE_PWD_H /* Only Unix, not native Woe32. */
1029 const char *username;
1030 struct passwd *userpasswd;
1032 /* 1. attempt: getpwnam(getenv("USER")) */
1033 username = getenv ("USER");
1034 if (username != NULL)
1037 userpasswd = getpwnam (username);
1038 if (userpasswd != NULL)
1041 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username);
1044 /* 2. attempt: getpwnam(getlogin()) */
1045 username = getlogin ();
1046 if (username != NULL)
1049 userpasswd = getpwnam (username);
1050 if (userpasswd != NULL)
1053 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username);
1056 /* 3. attempt: getpwuid(getuid()) */
1058 userpasswd = getpwuid (getuid ());
1059 if (userpasswd != NULL)
1062 error (EXIT_FAILURE, errno, "getpwuid(%ju)", (uintmax_t) getuid ());
1069 /* Return the user's full name. */
1071 get_user_fullname ()
1075 pwd = get_user_pwd ();
1079 const char *fullname;
1080 const char *fullname_end;
1083 /* Return the pw_gecos field, up to the first comma (if any). */
1084 fullname = pwd->pw_gecos;
1085 fullname_end = strchr (fullname, ',');
1086 if (fullname_end == NULL)
1087 fullname_end = fullname + strlen (fullname);
1089 result = XNMALLOC (fullname_end - fullname + 1, char);
1090 memcpy (result, fullname, fullname_end - fullname);
1091 result[fullname_end - fullname] = '\0';
1101 /* Return the user's email address. */
1105 const char *prog = relocate (LIBDIR "/gettext/user-email");
1115 /* Ask the user for his email address. */
1116 argv[0] = "/bin/sh";
1117 argv[1] = (char *) prog;
1118 argv[2] = (char *) _("\
1119 The new message catalog should contain your email address, so that users can\n\
1120 give you feedback about the translations, and so that maintainers can contact\n\
1121 you in case of unexpected technical problems.\n");
1123 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false,
1128 /* Retrieve his answer. */
1129 fp = fdopen (fd[0], "r");
1132 error (0, errno, _("fdopen() failed"));
1136 line = NULL; linesize = 0;
1137 linelen = getline (&line, &linesize, fp);
1138 if (linelen == (size_t)(-1))
1140 error (0, 0, _("%s subprocess I/O error"), prog);
1144 if (linelen > 0 && line[linelen - 1] == '\n')
1145 line[linelen - 1] = '\0';
1149 /* Remove zombie process from process list, and retrieve exit status. */
1150 exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
1151 if (exitstatus != 0)
1153 error (0, 0, _("%s subprocess failed with exit code %d"),
1161 return "EMAIL@ADDRESS";
1165 /* Construct the value for the Last-Translator field. */
1170 return "Automatically generated";
1173 const char *fullname = get_user_fullname ();
1174 const char *email = get_user_email ();
1176 if (fullname != NULL)
1177 return xasprintf ("%s <%s>", fullname, email);
1179 return xasprintf ("<%s>", email);
1184 /* Return the name of the language used by the language team, in English. */
1186 language_team_englishname ()
1190 /* Search for a name depending on the catalogname. */
1191 for (i = 0; i < language_variant_table_size; i++)
1192 if (strcmp (language_variant_table[i].code, catalogname) == 0)
1193 return language_variant_table[i].english;
1195 /* Search for a name depending on the language only. */
1196 return englishname_of_language ();
1200 /* Return the language team's mailing list address or homepage URL. */
1202 language_team_address ()
1204 const char *prog = relocate (PROJECTSDIR "/team-address");
1214 /* Call the team-address shell script. */
1215 argv[0] = "/bin/sh";
1216 argv[1] = (char *) prog;
1217 argv[2] = (char *) relocate (PROJECTSDIR);
1218 argv[3] = (char *) relocate (LIBDIR "/gettext");
1219 argv[4] = (char *) catalogname;
1220 argv[5] = (char *) language;
1222 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false,
1227 /* Retrieve its result. */
1228 fp = fdopen (fd[0], "r");
1231 error (0, errno, _("fdopen() failed"));
1235 line = NULL; linesize = 0;
1236 linelen = getline (&line, &linesize, fp);
1237 if (linelen == (size_t)(-1))
1239 else if (linelen > 0 && line[linelen - 1] == '\n')
1240 line[linelen - 1] = '\0';
1244 /* Remove zombie process from process list, and retrieve exit status. */
1245 exitstatus = wait_subprocess (child, prog, false, false, true, false, NULL);
1246 if (exitstatus != 0)
1248 error (0, 0, _("%s subprocess failed with exit code %d"),
1260 /* Construct the value for the Language-Team field. */
1268 const char *englishname = language_team_englishname ();
1269 const char *address = language_team_address ();
1271 if (address != NULL && address[0] != '\0')
1272 return xasprintf ("%s %s", englishname, address);
1279 /* Construct the value for the Language field. */
1287 /* Construct the value for the MIME-Version field. */
1295 /* Construct the value for the Content-Type field. */
1297 content_type (const char *header)
1300 const char *old_field;
1302 /* If the POT file contains charset=UTF-8, it means that the POT file
1303 contains non-ASCII characters, and we keep the UTF-8 encoding.
1304 Otherwise, when the POT file is plain ASCII, we use the locale's
1307 old_field = get_field (header, "Content-Type");
1308 if (old_field != NULL)
1310 const char *charsetstr = c_strstr (old_field, "charset=");
1312 if (charsetstr != NULL)
1314 charsetstr += strlen ("charset=");
1315 was_utf8 = (c_strcasecmp (charsetstr, "UTF-8") == 0);
1318 return xasprintf ("text/plain; charset=%s",
1319 was_utf8 ? "UTF-8" : canonical_locale_charset ());
1323 /* Construct the value for the Content-Transfer-Encoding field. */
1325 content_transfer_encoding ()
1331 /* Construct the value for the Plural-Forms field. */
1335 const char *gettextcldrdir;
1339 /* Search for a formula depending on the catalogname. */
1340 for (i = 0; i < plural_table_size; i++)
1341 if (strcmp (plural_table[i].lang, catalogname) == 0)
1342 return plural_table[i].value;
1344 /* Search for a formula depending on the language only. */
1345 for (i = 0; i < plural_table_size; i++)
1346 if (strcmp (plural_table[i].lang, language) == 0)
1347 return plural_table[i].value;
1349 gettextcldrdir = getenv ("GETTEXTCLDRDIR");
1350 if (gettextcldrdir != NULL && gettextcldrdir[0] != '\0')
1352 const char *gettextlibdir;
1353 char *dirs[3], *last_dir;
1363 gettextlibdir = getenv ("GETTEXTLIBDIR");
1364 if (gettextlibdir == NULL || gettextlibdir[0] == '\0')
1365 gettextlibdir = relocate (LIBDIR "/gettext");
1367 prog = xconcatenated_filename (gettextlibdir, "cldr-plurals", NULL);
1369 last_dir = xstrdup (gettextcldrdir);
1371 dirs[1] = "supplemental";
1372 dirs[2] = "plurals.xml";
1373 for (i = 0; i < SIZEOF (dirs); i++)
1375 char *dir = xconcatenated_filename (last_dir, dirs[i], NULL);
1380 /* Call the cldr-plurals command. */
1381 argv[0] = "cldr-plurals";
1382 argv[1] = (char *) language;
1385 child = create_pipe_in (prog, prog, argv, DEV_NULL,
1392 /* Retrieve its result. */
1393 fp = fdopen (fd[0], "r");
1396 error (0, errno, _("fdopen() failed"));
1400 line = NULL; linesize = 0;
1401 linelen = getline (&line, &linesize, fp);
1402 if (linelen == (size_t)(-1))
1404 error (0, 0, _("%s subprocess I/O error"), prog);
1408 if (linelen > 0 && line[linelen - 1] == '\n')
1409 line[linelen - 1] = '\0';
1413 /* Remove zombie process from process list, and retrieve exit status. */
1414 exitstatus = wait_subprocess (child, prog, false, false, true, false,
1416 if (exitstatus != 0)
1418 error (0, 0, _("%s subprocess failed with exit code %d"),
1435 const char * (*getter0) (void);
1436 const char * (*getter1) (const char *header);
1440 { "Project-Id-Version", NULL, project_id_version },
1441 { "PO-Revision-Date", NULL, po_revision_date },
1442 { "Last-Translator", last_translator, NULL },
1443 { "Language-Team", language_team, NULL },
1444 { "Language", language_value, NULL },
1445 { "MIME-Version", mime_version, NULL },
1446 { "Content-Type", NULL, content_type },
1447 { "Content-Transfer-Encoding", content_transfer_encoding, NULL },
1448 { "Plural-Forms", plural_forms, NULL }
1451 #define NFIELDS SIZEOF (fields)
1452 #define FIELD_LAST_TRANSLATOR 2
1455 /* Retrieve a freshly allocated copy of a field's value. */
1457 get_field (const char *header, const char *field)
1459 size_t len = strlen (field);
1462 for (line = header;;)
1464 if (strncmp (line, field, len) == 0 && line[len] == ':')
1466 const char *value_start;
1467 const char *value_end;
1470 value_start = line + len + 1;
1471 if (*value_start == ' ')
1473 value_end = strchr (value_start, '\n');
1474 if (value_end == NULL)
1475 value_end = value_start + strlen (value_start);
1477 value = XNMALLOC (value_end - value_start + 1, char);
1478 memcpy (value, value_start, value_end - value_start);
1479 value[value_end - value_start] = '\0';
1484 line = strchr (line, '\n');
1494 /* Add a field with value to a header, and return the new header. */
1496 put_field (const char *old_header, const char *field, const char *value)
1498 size_t len = strlen (field);
1503 for (line = old_header;;)
1505 if (strncmp (line, field, len) == 0 && line[len] == ':')
1507 const char *value_start;
1508 const char *value_end;
1510 value_start = line + len + 1;
1511 if (*value_start == ' ')
1513 value_end = strchr (value_start, '\n');
1514 if (value_end == NULL)
1515 value_end = value_start + strlen (value_start);
1517 new_header = XNMALLOC (strlen (old_header)
1518 - (value_end - value_start)
1520 + (*value_end != '\n' ? 1 : 0)
1524 memcpy (p, old_header, value_start - old_header);
1525 p += value_start - old_header;
1526 memcpy (p, value, strlen (value));
1527 p += strlen (value);
1528 if (*value_end != '\n')
1530 strcpy (p, value_end);
1535 line = strchr (line, '\n');
1542 new_header = XNMALLOC (strlen (old_header) + 1
1543 + len + 2 + strlen (value) + 1
1547 memcpy (p, old_header, strlen (old_header));
1548 p += strlen (old_header);
1549 if (p > new_header && p[-1] != '\n')
1551 memcpy (p, field, len);
1555 memcpy (p, value, strlen (value));
1556 p += strlen (value);
1564 /* Return the title format string. */
1568 /* This is tricky. We want the translation in the given locale specified by
1569 the command line, not the current locale. But we want it in the encoding
1570 that we put into the header entry, not the encoding of that locale.
1571 We could avoid the use of OUTPUT_CHARSET by using a separate message
1572 catalog and bind_textdomain_codeset(), but that doesn't seem worth the
1573 trouble for one single message. */
1574 const char *encoding;
1578 char *old_OUTPUT_CHARSET;
1580 const char *english;
1583 encoding = canonical_locale_charset ();
1585 /* First, the English title. */
1586 english = xasprintf ("%s translations for %%s package",
1587 englishname_of_language ());
1589 /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */
1591 tmp = getenv ("LC_ALL");
1592 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
1594 tmp = getenv ("LANGUAGE");
1595 old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL);
1597 tmp = getenv ("OUTPUT_CHARSET");
1598 old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL);
1600 xsetenv ("LC_ALL", locale, 1);
1601 unsetenv ("LANGUAGE");
1602 xsetenv ("OUTPUT_CHARSET", encoding, 1);
1604 #ifdef HAVE_SETLOCALE
1605 if (setlocale (LC_ALL, "") == NULL)
1606 /* Nonexistent locale. Use the English title. */
1611 /* Fetch the translation. */
1612 /* TRANSLATORS: "English" needs to be replaced by your language.
1613 For example in it.po write "Traduzioni italiani ...",
1614 *not* "Traduzioni inglesi ...". */
1615 msgid = N_("English translations for %s package");
1616 result = gettext (msgid);
1617 if (result != msgid && strcmp (result, msgid) != 0)
1618 /* Use the English and the foreign title. */
1619 result = xasprintf ("%s\n%s", english, result);
1621 /* No translation found. Use the English title. */
1625 /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */
1627 if (old_LC_ALL != NULL)
1628 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
1630 unsetenv ("LC_ALL");
1632 if (old_LANGUAGE != NULL)
1633 xsetenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE);
1635 unsetenv ("LANGUAGE");
1637 if (old_OUTPUT_CHARSET != NULL)
1638 xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET);
1640 unsetenv ("OUTPUT_CHARSET");
1642 #ifdef HAVE_SETLOCALE
1643 setlocale (LC_ALL, "");
1650 /* Perform a set of substitutions in a string and return the resulting
1651 string. When subst[j][0] found, it is replaced with subst[j][1].
1652 subst[j][0] must not be the empty string. */
1654 subst_string (const char *str,
1655 unsigned int nsubst, const char *(*subst)[2])
1659 char *malloced = NULL;
1664 substlen = (size_t *) xmalloca (nsubst * sizeof (size_t));
1665 for (j = 0; j < nsubst; j++)
1667 substlen[j] = strlen (subst[j][0]);
1668 if (substlen[j] == 0)
1676 for (j = 0; j < nsubst; j++)
1677 if (*(str + i) == *subst[j][0]
1678 && strncmp (str + i, subst[j][0], substlen[j]) == 0)
1680 size_t replacement_len = strlen (subst[j][1]);
1681 size_t new_len = strlen (str) - substlen[j] + replacement_len;
1682 char *new_str = XNMALLOC (new_len + 1, char);
1683 memcpy (new_str, str, i);
1684 memcpy (new_str + i, subst[j][1], replacement_len);
1685 strcpy (new_str + i + replacement_len, str + i + substlen[j]);
1686 if (malloced != NULL)
1690 i += replacement_len;
1703 /* Perform a set of substitutions on each string of a string list.
1704 When subst[j][0] found, it is replaced with subst[j][1]. subst[j][0]
1705 must not be the empty string. */
1707 subst_string_list (string_list_ty *slp,
1708 unsigned int nsubst, const char *(*subst)[2])
1712 for (j = 0; j < slp->nitems; j++)
1713 slp->item[j] = subst_string (slp->item[j], nsubst, subst);
1717 /* Fill the templates in all fields of the header entry. */
1718 static msgdomain_list_ty *
1719 fill_header (msgdomain_list_ty *mdlp)
1721 /* Cache the strings filled in, for use when there are multiple domains
1722 and a header entry for each domain. */
1723 const char *field_value[NFIELDS];
1726 for (i = 0; i < NFIELDS; i++)
1727 field_value[i] = NULL;
1729 for (k = 0; k < mdlp->nitems; k++)
1731 message_list_ty *mlp = mdlp->item[k]->messages;
1733 if (mlp->nitems > 0)
1735 message_ty *header_mp = NULL;
1738 /* Search the header entry. */
1739 for (j = 0; j < mlp->nitems; j++)
1740 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1742 header_mp = mlp->item[j];
1746 /* If it wasn't found, provide one. */
1747 if (header_mp == NULL)
1749 static lex_pos_ty pos = { __FILE__, __LINE__ };
1751 header_mp = message_alloc (NULL, "", NULL, "", 1, &pos);
1752 message_list_prepend (mlp, header_mp);
1755 header = xstrdup (header_mp->msgstr);
1757 /* Fill in the fields. */
1758 for (i = 0; i < NFIELDS; i++)
1760 if (field_value[i] == NULL)
1762 (fields[i].getter1 != NULL
1763 ? fields[i].getter1 (header)
1764 : fields[i].getter0 ());
1766 if (field_value[i] != NULL)
1768 char *old_header = header;
1769 header = put_field (header, fields[i].name, field_value[i]);
1774 /* Replace the old translation in the header entry. */
1775 header_mp->msgstr = header;
1776 header_mp->msgstr_len = strlen (header) + 1;
1778 /* Update the comments in the header entry. */
1779 if (header_mp->comment != NULL)
1781 const char *subst[4][2];
1785 id = project_id (header);
1786 subst[0][0] = "SOME DESCRIPTIVE TITLE";
1787 subst[0][1] = xasprintf (get_title (), id, id);
1788 subst[1][0] = "PACKAGE";
1790 subst[2][0] = "FIRST AUTHOR <EMAIL@ADDRESS>";
1791 subst[2][1] = field_value[FIELD_LAST_TRANSLATOR];
1792 subst[3][0] = "YEAR";
1795 (time (&now), (localtime (&now))->tm_year + 1900));
1796 subst_string_list (header_mp->comment, SIZEOF (subst), subst);
1799 /* Finally remove the fuzzy attribute. */
1800 header_mp->is_fuzzy = false;
1808 /* Update the msgstr plural entries according to the nplurals count. */
1809 static msgdomain_list_ty *
1810 update_msgstr_plurals (msgdomain_list_ty *mdlp)
1814 for (k = 0; k < mdlp->nitems; k++)
1816 message_list_ty *mlp = mdlp->item[k]->messages;
1817 message_ty *header_entry;
1818 unsigned long int nplurals;
1819 char *untranslated_plural_msgstr;
1822 header_entry = message_list_search (mlp, NULL, "");
1823 nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL);
1824 untranslated_plural_msgstr = XNMALLOC (nplurals, char);
1825 memset (untranslated_plural_msgstr, '\0', nplurals);
1827 for (j = 0; j < mlp->nitems; j++)
1829 message_ty *mp = mlp->item[j];
1830 bool is_untranslated;
1834 if (mp->msgid_plural != NULL)
1836 /* Test if mp is untranslated. (It most likely is.) */
1837 is_untranslated = true;
1838 for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++)
1841 is_untranslated = false;
1844 if (is_untranslated)
1846 /* Change mp->msgstr_len consecutive empty strings into
1847 nplurals consecutive empty strings. */
1848 if (nplurals > mp->msgstr_len)
1849 mp->msgstr = untranslated_plural_msgstr;
1850 mp->msgstr_len = nplurals;