1 /* Initializes a new PO file.
2 Copyright (C) 2001-2010 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/>. */
33 #include <sys/types.h>
53 #include "error-progname.h"
55 #include "relocatable.h"
58 #include "c-strcase.h"
60 #include "read-catalog.h"
62 #include "read-properties.h"
63 #include "read-stringtable.h"
64 #include "write-catalog.h"
66 #include "write-properties.h"
67 #include "write-stringtable.h"
69 #include "po-charset.h"
70 #include "localcharset.h"
71 #include "localename.h"
73 #include "plural-table.h"
74 #include "lang-table.h"
77 #include "concat-filename.h"
79 #include "xvasprintf.h"
80 #include "msgl-english.h"
81 #include "plural-count.h"
83 #include "wait-process.h"
86 #include "propername.h"
89 #define _(str) gettext (str)
92 /* Get F_OK. It is lacking from <fcntl.h> on Woe32. */
97 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
99 extern const char * _nl_expand_alias (const char *name);
102 static const char *locale;
104 /* Language (ISO-639 code) and optional territory (ISO-3166 code). */
105 static const char *catalogname;
107 /* Language (ISO-639 code). */
108 static const char *language;
110 /* If true, the user is not considered to be the translator. */
111 static bool no_translator;
114 static const struct option long_options[] =
116 { "color", optional_argument, NULL, CHAR_MAX + 5 },
117 { "help", no_argument, NULL, 'h' },
118 { "input", required_argument, NULL, 'i' },
119 { "locale", required_argument, NULL, 'l' },
120 { "no-translator", no_argument, NULL, CHAR_MAX + 1 },
121 { "no-wrap", no_argument, NULL, CHAR_MAX + 2 },
122 { "output-file", required_argument, NULL, 'o' },
123 { "properties-input", no_argument, NULL, 'P' },
124 { "properties-output", no_argument, NULL, 'p' },
125 { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 },
126 { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 },
127 { "style", required_argument, NULL, CHAR_MAX + 6 },
128 { "version", no_argument, NULL, 'V' },
129 { "width", required_argument, NULL, 'w' },
133 /* Forward declaration of local functions. */
134 static void usage (int status)
135 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
136 __attribute__ ((noreturn))
139 static const char *find_pot (void);
140 static const char *catalogname_for_locale (const char *locale);
141 static const char *language_of_locale (const char *locale);
142 static char *get_field (const char *header, const char *field);
143 static msgdomain_list_ty *fill_header (msgdomain_list_ty *mdlp);
144 static msgdomain_list_ty *update_msgstr_plurals (msgdomain_list_ty *mdlp);
148 main (int argc, char **argv)
154 const char *input_file;
155 msgdomain_list_ty *result;
156 catalog_input_format_ty input_syntax = &input_format_po;
157 catalog_output_format_ty output_syntax = &output_format_po;
159 /* Set program name for messages. */
160 set_program_name (argv[0]);
161 error_print_progname = maybe_print_progname;
163 #ifdef HAVE_SETLOCALE
164 /* Set locale via LC_ALL. */
165 setlocale (LC_ALL, "");
168 /* Set the text message domain. */
169 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
170 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
171 textdomain (PACKAGE);
173 /* Ensure that write errors on stdout are detected. */
174 atexit (close_stdout);
176 /* Set default values for variables. */
183 while ((opt = getopt_long (argc, argv, "hi:l:o:pPVw:", long_options, NULL))
187 case '\0': /* Long option. */
195 if (input_file != NULL)
197 error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
198 usage (EXIT_FAILURE);
208 output_file = optarg;
212 output_syntax = &output_format_properties;
216 input_syntax = &input_format_properties;
227 value = strtol (optarg, &endp, 10);
229 message_page_width_set (value);
234 no_translator = true;
237 case CHAR_MAX + 2: /* --no-wrap */
238 message_page_width_ignore ();
241 case CHAR_MAX + 3: /* --stringtable-input */
242 input_syntax = &input_format_stringtable;
245 case CHAR_MAX + 4: /* --stringtable-output */
246 output_syntax = &output_format_stringtable;
249 case CHAR_MAX + 5: /* --color */
250 if (handle_color_option (optarg) || color_test_mode)
251 usage (EXIT_FAILURE);
254 case CHAR_MAX + 6: /* --style */
255 handle_style_option (optarg);
259 usage (EXIT_FAILURE);
263 /* Version information is requested. */
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 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
270 This is free software: you are free to change and redistribute it.\n\
271 There is NO WARRANTY, to the extent permitted by law.\n\
274 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
278 /* Help is requested. */
280 usage (EXIT_SUCCESS);
282 /* Test for extraneous arguments. */
284 error (EXIT_FAILURE, 0, _("too many arguments"));
286 /* Search for the input file. */
287 if (input_file == NULL)
288 input_file = find_pot ();
290 /* Determine target locale. */
293 locale = gl_locale_name (LC_MESSAGES, "LC_MESSAGES");
294 if (strcmp (locale, "C") == 0)
296 multiline_error (xstrdup (""),
298 You are in a language indifferent environment. Please set\n\
299 your LANG environment variable, as described in the ABOUT-NLS\n\
300 file. This is necessary so you can test your translations.\n")));
305 const char *alias = _nl_expand_alias (locale);
309 catalogname = catalogname_for_locale (locale);
310 language = language_of_locale (locale);
312 /* Default output file name is CATALOGNAME.po. */
313 if (output_file == NULL)
315 output_file = xasprintf ("%s.po", catalogname);
317 /* But don't overwrite existing PO files. */
318 if (access (output_file, F_OK) == 0)
320 multiline_error (xstrdup (""),
322 Output file %s already exists.\n\
323 Please specify the locale through the --locale option or\n\
324 the output .po file through the --output-file option.\n"),
330 /* Read input file. */
331 result = read_catalog_file (input_file, input_syntax);
333 /* Fill the header entry. */
334 result = fill_header (result);
336 /* Initialize translations. */
337 if (strcmp (language, "en") == 0)
338 result = msgdomain_list_english (result);
340 result = update_msgstr_plurals (result);
342 /* Write the modified message list out. */
343 msgdomain_list_print (result, output_file, output_syntax, true, false);
346 fprintf (stderr, "\n");
347 fprintf (stderr, _("Created %s.\n"), output_file);
353 /* Display usage information and exit. */
357 if (status != EXIT_SUCCESS)
358 fprintf (stderr, _("Try `%s --help' for more information.\n"),
363 Usage: %s [OPTION]\n\
366 /* xgettext: no-wrap */
368 Creates a new PO file, initializing the meta information with values from the\n\
369 user's environment.\n\
373 Mandatory arguments to long options are mandatory for short options too.\n"));
376 Input file location:\n"));
378 -i, --input=INPUTFILE input POT file\n"));
380 If no input file is given, the current directory is searched for the POT file.\n\
381 If it is -, standard input is read.\n"));
384 Output file location:\n"));
386 -o, --output-file=FILE write output to specified PO file\n"));
388 If no output file is given, it depends on the --locale option or the user's\n\
389 locale setting. If it is -, the results are written to standard output.\n"));
392 Input file syntax:\n"));
394 -P, --properties-input input file is in Java .properties syntax\n"));
396 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
399 Output details:\n"));
401 -l, --locale=LL_CC set target locale\n"));
403 --no-translator assume the PO file is automatically generated\n"));
405 --color use colors and other text attributes always\n\
406 --color=WHEN use colors and other text attributes if WHEN.\n\
407 WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
409 --style=STYLEFILE specify CSS style rule file for --color\n"));
411 -p, --properties-output write out a Java .properties file\n"));
413 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
415 -w, --width=NUMBER set output page width\n"));
417 --no-wrap do not break long message lines, longer than\n\
418 the output page width, into several lines\n"));
421 Informative output:\n"));
423 -h, --help display this help and exit\n"));
425 -V, --version output version information and exit\n"));
427 /* TRANSLATORS: The placeholder indicates the bug-reporting address
428 for this package. Please add _another line_ saying
429 "Report translation bugs to <...>\n" with the address for translation
430 bugs (typically your translation team's web or email address). */
431 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
439 /* Search for the POT file and return its name. */
447 dirp = opendir (".");
458 const char *name = dp->d_name;
459 size_t namlen = strlen (name);
461 if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0)
464 found = xstrdup (name);
467 multiline_error (xstrdup (""),
469 Found more than one .pot file.\n\
470 Please specify the input .pot file through the --input option.\n")));
471 usage (EXIT_FAILURE);
476 error (EXIT_FAILURE, errno, _("error reading current directory"));
481 error (EXIT_FAILURE, errno, _("error reading current directory"));
488 multiline_error (xstrdup (""),
490 Found no .pot file in the current directory.\n\
491 Please specify the input .pot file through the --input option.\n")));
492 usage (EXIT_FAILURE);
498 /* Return the gettext catalog name corresponding to a locale. If the locale
499 consists of a language and a territory, and the language is mainly spoken
500 in that territory, the territory is removed from the locale name.
501 For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de",
502 because the resulting catalog can be used as a default for all "de_XX",
505 catalogname_for_locale (const char *locale)
507 static const char *locales_with_principal_territory[] = {
508 /* Language Main territory */
509 "ace_ID", /* Achinese Indonesia */
510 "af_ZA", /* Afrikaans South Africa */
511 "ak_GH", /* Akan Ghana */
512 "am_ET", /* Amharic Ethiopia */
513 "an_ES", /* Aragonese Spain */
514 "ang_GB", /* Old English Britain */
515 "arn_CL", /* Mapudungun Chile */
516 "as_IN", /* Assamese India */
517 "ast_ES", /* Asturian Spain */
518 "av_RU", /* Avaric Russia */
519 "awa_IN", /* Awadhi India */
520 "az_AZ", /* Azerbaijani Azerbaijan */
521 "ban_ID", /* Balinese Indonesia */
522 "be_BY", /* Belarusian Belarus */
523 "bej_SD", /* Beja Sudan */
524 "bem_ZM", /* Bemba Zambia */
525 "bg_BG", /* Bulgarian Bulgaria */
526 "bho_IN", /* Bhojpuri India */
527 "bik_PH", /* Bikol Philippines */
528 "bin_NG", /* Bini Nigeria */
529 "bm_ML", /* Bambara Mali */
530 "bn_IN", /* Bengali India */
531 "bo_CN", /* Tibetan China */
532 "br_FR", /* Breton France */
533 "bs_BA", /* Bosnian Bosnia */
534 "bug_ID", /* Buginese Indonesia */
535 "ca_ES", /* Catalan Spain */
536 "ce_RU", /* Chechen Russia */
537 "ceb_PH", /* Cebuano Philippines */
538 "co_FR", /* Corsican France */
539 "cr_CA", /* Cree Canada */
540 /* Don't put "crh_UZ" or "crh_UA" here. That would be asking for fruitless
541 political discussion. */
542 "cs_CZ", /* Czech Czech Republic */
543 "csb_PL", /* Kashubian Poland */
544 "cy_GB", /* Welsh Britain */
545 "da_DK", /* Danish Denmark */
546 "de_DE", /* German Germany */
547 "din_SD", /* Dinka Sudan */
548 "doi_IN", /* Dogri India */
549 "dsb_DE", /* Lower Sorbian Germany */
550 "dv_MV", /* Divehi Maldives */
551 "dz_BT", /* Dzongkha Bhutan */
552 "ee_GH", /* Éwé Ghana */
553 "el_GR", /* Greek Greece */
554 /* Don't put "en_GB" or "en_US" here. That would be asking for fruitless
555 political discussion. */
556 "es_ES", /* Spanish Spain */
557 "et_EE", /* Estonian Estonia */
558 "fa_IR", /* Persian Iran */
559 "fi_FI", /* Finnish Finland */
560 "fil_PH", /* Filipino Philippines */
561 "fj_FJ", /* Fijian Fiji */
562 "fo_FO", /* Faroese Faeroe Islands */
563 "fon_BJ", /* Fon Benin */
564 "fr_FR", /* French France */
565 "fur_IT", /* Friulian Italy */
566 "fy_NL", /* Western Frisian Netherlands */
567 "ga_IE", /* Irish Ireland */
568 "gd_GB", /* Scottish Gaelic Britain */
569 "gon_IN", /* Gondi India */
570 "gsw_CH", /* Swiss German Switzerland */
571 "gu_IN", /* Gujarati India */
572 "he_IL", /* Hebrew Israel */
573 "hi_IN", /* Hindi India */
574 "hil_PH", /* Hiligaynon Philippines */
575 "hr_HR", /* Croatian Croatia */
576 "hsb_DE", /* Upper Sorbian Germany */
577 "ht_HT", /* Haitian Haiti */
578 "hu_HU", /* Hungarian Hungary */
579 "hy_AM", /* Armenian Armenia */
580 "id_ID", /* Indonesian Indonesia */
581 "ig_NG", /* Igbo Nigeria */
582 "ii_CN", /* Sichuan Yi China */
583 "ilo_PH", /* Iloko Philippines */
584 "is_IS", /* Icelandic Iceland */
585 "it_IT", /* Italian Italy */
586 "ja_JP", /* Japanese Japan */
587 "jab_NG", /* Hyam Nigeria */
588 "jv_ID", /* Javanese Indonesia */
589 "ka_GE", /* Georgian Georgia */
590 "kab_DZ", /* Kabyle Algeria */
591 "kaj_NG", /* Jju Nigeria */
592 "kam_KE", /* Kamba Kenya */
593 "kmb_AO", /* Kimbundu Angola */
594 "kcg_NG", /* Tyap Nigeria */
595 "kdm_NG", /* Kagoma Nigeria */
596 "kg_CD", /* Kongo Democratic Republic of Congo */
597 "kk_KZ", /* Kazakh Kazakhstan */
598 "kl_GL", /* Kalaallisut Greenland */
599 "km_KH", /* Central Khmer Cambodia */
600 "kn_IN", /* Kannada India */
601 "ko_KR", /* Korean Korea (South) */
602 "kok_IN", /* Konkani India */
603 "kr_NG", /* Kanuri Nigeria */
604 "kru_IN", /* Kurukh India */
605 "lg_UG", /* Ganda Uganda */
606 "li_BE", /* Limburgish Belgium */
607 "lo_LA", /* Laotian Laos */
608 "lt_LT", /* Lithuanian Lithuania */
609 "lu_CD", /* Luba-Katanga Democratic Republic of Congo */
610 "lua_CD", /* Luba-Lulua Democratic Republic of Congo */
611 "luo_KE", /* Luo Kenya */
612 "lv_LV", /* Latvian Latvia */
613 "mad_ID", /* Madurese Indonesia */
614 "mag_IN", /* Magahi India */
615 "mai_IN", /* Maithili India */
616 "mak_ID", /* Makasar Indonesia */
617 "man_ML", /* Mandingo Mali */
618 "men_SL", /* Mende Sierra Leone */
619 "mg_MG", /* Malagasy Madagascar */
620 "mi_NZ", /* Maori New Zealand */
621 "min_ID", /* Minangkabau Indonesia */
622 "mk_MK", /* Macedonian Macedonia */
623 "ml_IN", /* Malayalam India */
624 "mn_MN", /* Mongolian Mongolia */
625 "mni_IN", /* Manipuri India */
626 "mos_BF", /* Mossi Burkina Faso */
627 "mr_IN", /* Marathi India */
628 "ms_MY", /* Malay Malaysia */
629 "mt_MT", /* Maltese Malta */
630 "mwr_IN", /* Marwari India */
631 "my_MM", /* Burmese Myanmar */
632 "na_NR", /* Nauru Nauru */
633 "nah_MX", /* Nahuatl Mexico */
634 "nap_IT", /* Neapolitan Italy */
635 "nb_NO", /* Norwegian Bokmål Norway */
636 "nds_DE", /* Low Saxon Germany */
637 "ne_NP", /* Nepali Nepal */
638 "nl_NL", /* Dutch Netherlands */
639 "nn_NO", /* Norwegian Nynorsk Norway */
640 "no_NO", /* Norwegian Norway */
641 "nr_ZA", /* South Ndebele South Africa */
642 "nso_ZA", /* Northern Sotho South Africa */
643 "nym_TZ", /* Nyamwezi Tanzania */
644 "nyn_UG", /* Nyankole Uganda */
645 "oc_FR", /* Occitan France */
646 "oj_CA", /* Ojibwa Canada */
647 "or_IN", /* Oriya India */
648 "pa_IN", /* Punjabi India */
649 "pag_PH", /* Pangasinan Philippines */
650 "pam_PH", /* Pampanga Philippines */
651 "pap_AN", /* Papiamento Netherlands Antilles */
652 "pbb_CO", /* Páez Colombia */
653 "pl_PL", /* Polish Poland */
654 "ps_AF", /* Pashto Afghanistan */
655 "pt_PT", /* Portuguese Portugal */
656 "raj_IN", /* Rajasthani India */
657 "rm_CH", /* Romansh Switzerland */
658 "rn_BI", /* Kirundi Burundi */
659 "ro_RO", /* Romanian Romania */
660 "ru_RU", /* Russian Russia */
661 "sa_IN", /* Sanskrit India */
662 "sah_RU", /* Yakut Russia */
663 "sas_ID", /* Sasak Indonesia */
664 "sat_IN", /* Santali India */
665 "sc_IT", /* Sardinian Italy */
666 "scn_IT", /* Sicilian Italy */
667 "sg_CF", /* Sango Central African Republic */
668 "shn_MM", /* Shan Myanmar */
669 "si_LK", /* Sinhala Sri Lanka */
670 "sid_ET", /* Sidamo Ethiopia */
671 "sk_SK", /* Slovak Slovakia */
672 "sl_SI", /* Slovenian Slovenia */
673 "smn_FI", /* Inari Sami Finland */
674 "sms_FI", /* Skolt Sami Finland */
675 "so_SO", /* Somali Somalia */
676 "sq_AL", /* Albanian Albania */
677 "sr_RS", /* Serbian Serbia */
678 "sr_YU", /* Serbian Yugoslavia - this line can be removed in 2010 */
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(\"%d\")", 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, upto 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. */
1337 /* Search for a formula depending on the catalogname. */
1338 for (i = 0; i < plural_table_size; i++)
1339 if (strcmp (plural_table[i].lang, catalogname) == 0)
1340 return plural_table[i].value;
1342 /* Search for a formula depending on the language only. */
1343 for (i = 0; i < plural_table_size; i++)
1344 if (strcmp (plural_table[i].lang, language) == 0)
1345 return plural_table[i].value;
1354 const char * (*getter0) (void);
1355 const char * (*getter1) (const char *header);
1359 { "Project-Id-Version", NULL, project_id_version },
1360 { "PO-Revision-Date", NULL, po_revision_date },
1361 { "Last-Translator", last_translator, NULL },
1362 { "Language-Team", language_team, NULL },
1363 { "Language", language_value, NULL },
1364 { "MIME-Version", mime_version, NULL },
1365 { "Content-Type", NULL, content_type },
1366 { "Content-Transfer-Encoding", content_transfer_encoding, NULL },
1367 { "Plural-Forms", plural_forms, NULL }
1370 #define NFIELDS SIZEOF (fields)
1371 #define FIELD_LAST_TRANSLATOR 2
1374 /* Retrieve a freshly allocated copy of a field's value. */
1376 get_field (const char *header, const char *field)
1378 size_t len = strlen (field);
1381 for (line = header;;)
1383 if (strncmp (line, field, len) == 0 && line[len] == ':')
1385 const char *value_start;
1386 const char *value_end;
1389 value_start = line + len + 1;
1390 if (*value_start == ' ')
1392 value_end = strchr (value_start, '\n');
1393 if (value_end == NULL)
1394 value_end = value_start + strlen (value_start);
1396 value = XNMALLOC (value_end - value_start + 1, char);
1397 memcpy (value, value_start, value_end - value_start);
1398 value[value_end - value_start] = '\0';
1403 line = strchr (line, '\n');
1413 /* Add a field with value to a header, and return the new header. */
1415 put_field (const char *old_header, const char *field, const char *value)
1417 size_t len = strlen (field);
1422 for (line = old_header;;)
1424 if (strncmp (line, field, len) == 0 && line[len] == ':')
1426 const char *value_start;
1427 const char *value_end;
1429 value_start = line + len + 1;
1430 if (*value_start == ' ')
1432 value_end = strchr (value_start, '\n');
1433 if (value_end == NULL)
1434 value_end = value_start + strlen (value_start);
1436 new_header = XNMALLOC (strlen (old_header)
1437 - (value_end - value_start)
1439 + (*value_end != '\n' ? 1 : 0)
1443 memcpy (p, old_header, value_start - old_header);
1444 p += value_start - old_header;
1445 memcpy (p, value, strlen (value));
1446 p += strlen (value);
1447 if (*value_end != '\n')
1449 strcpy (p, value_end);
1454 line = strchr (line, '\n');
1461 new_header = XNMALLOC (strlen (old_header) + 1
1462 + len + 2 + strlen (value) + 1
1466 memcpy (p, old_header, strlen (old_header));
1467 p += strlen (old_header);
1468 if (p > new_header && p[-1] != '\n')
1470 memcpy (p, field, len);
1474 memcpy (p, value, strlen (value));
1475 p += strlen (value);
1483 /* Return the title format string. */
1487 /* This is tricky. We want the translation in the given locale specified by
1488 the command line, not the current locale. But we want it in the encoding
1489 that we put into the header entry, not the encoding of that locale.
1490 We could avoid the use of OUTPUT_CHARSET by using a separate message
1491 catalog and bind_textdomain_codeset(), but that doesn't seem worth the
1492 trouble for one single message. */
1493 const char *encoding;
1497 char *old_OUTPUT_CHARSET;
1499 const char *english;
1502 encoding = canonical_locale_charset ();
1504 /* First, the English title. */
1505 english = xasprintf ("%s translations for %%s package",
1506 englishname_of_language ());
1508 /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */
1510 tmp = getenv ("LC_ALL");
1511 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL);
1513 tmp = getenv ("LANGUAGE");
1514 old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL);
1516 tmp = getenv ("OUTPUT_CHARSET");
1517 old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL);
1519 xsetenv ("LC_ALL", locale, 1);
1520 unsetenv ("LANGUAGE");
1521 xsetenv ("OUTPUT_CHARSET", encoding, 1);
1523 #ifdef HAVE_SETLOCALE
1524 if (setlocale (LC_ALL, "") == NULL)
1525 /* Nonexistent locale. Use the English title. */
1530 /* Fetch the translation. */
1531 /* TRANSLATORS: "English" needs to be replaced by your language.
1532 For example in it.po write "Traduzioni italiani ...",
1533 *not* "Traduzioni inglesi ...". */
1534 msgid = N_("English translations for %s package");
1535 result = gettext (msgid);
1536 if (result != msgid && strcmp (result, msgid) != 0)
1537 /* Use the English and the foreign title. */
1538 result = xasprintf ("%s\n%s", english, result);
1540 /* No translation found. Use the English title. */
1544 /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */
1546 if (old_LC_ALL != NULL)
1547 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL);
1549 unsetenv ("LC_ALL");
1551 if (old_LANGUAGE != NULL)
1552 xsetenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE);
1554 unsetenv ("LANGUAGE");
1556 if (old_OUTPUT_CHARSET != NULL)
1557 xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET);
1559 unsetenv ("OUTPUT_CHARSET");
1561 #ifdef HAVE_SETLOCALE
1562 setlocale (LC_ALL, "");
1569 /* Perform a set of substitutions in a string and return the resulting
1570 string. When subst[j][0] found, it is replaced with subst[j][1].
1571 subst[j][0] must not be the empty string. */
1573 subst_string (const char *str,
1574 unsigned int nsubst, const char *(*subst)[2])
1578 char *malloced = NULL;
1583 substlen = (size_t *) xmalloca (nsubst * sizeof (size_t));
1584 for (j = 0; j < nsubst; j++)
1586 substlen[j] = strlen (subst[j][0]);
1587 if (substlen[j] == 0)
1595 for (j = 0; j < nsubst; j++)
1596 if (*(str + i) == *subst[j][0]
1597 && strncmp (str + i, subst[j][0], substlen[j]) == 0)
1599 size_t replacement_len = strlen (subst[j][1]);
1600 size_t new_len = strlen (str) - substlen[j] + replacement_len;
1601 char *new_str = XNMALLOC (new_len + 1, char);
1602 memcpy (new_str, str, i);
1603 memcpy (new_str + i, subst[j][1], replacement_len);
1604 strcpy (new_str + i + replacement_len, str + i + substlen[j]);
1605 if (malloced != NULL)
1609 i += replacement_len;
1622 /* Perform a set of substitutions on each string of a string list.
1623 When subst[j][0] found, it is replaced with subst[j][1]. subst[j][0]
1624 must not be the empty string. */
1626 subst_string_list (string_list_ty *slp,
1627 unsigned int nsubst, const char *(*subst)[2])
1631 for (j = 0; j < slp->nitems; j++)
1632 slp->item[j] = subst_string (slp->item[j], nsubst, subst);
1636 /* Fill the templates in all fields of the header entry. */
1637 static msgdomain_list_ty *
1638 fill_header (msgdomain_list_ty *mdlp)
1640 /* Cache the strings filled in, for use when there are multiple domains
1641 and a header entry for each domain. */
1642 const char *field_value[NFIELDS];
1645 for (i = 0; i < NFIELDS; i++)
1646 field_value[i] = NULL;
1648 for (k = 0; k < mdlp->nitems; k++)
1650 message_list_ty *mlp = mdlp->item[k]->messages;
1652 if (mlp->nitems > 0)
1654 message_ty *header_mp = NULL;
1657 /* Search the header entry. */
1658 for (j = 0; j < mlp->nitems; j++)
1659 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1661 header_mp = mlp->item[j];
1665 /* If it wasn't found, provide one. */
1666 if (header_mp == NULL)
1668 static lex_pos_ty pos = { __FILE__, __LINE__ };
1670 header_mp = message_alloc (NULL, "", NULL, "", 1, &pos);
1671 message_list_prepend (mlp, header_mp);
1674 header = xstrdup (header_mp->msgstr);
1676 /* Fill in the fields. */
1677 for (i = 0; i < NFIELDS; i++)
1679 if (field_value[i] == NULL)
1681 (fields[i].getter1 != NULL
1682 ? fields[i].getter1 (header)
1683 : fields[i].getter0 ());
1685 if (field_value[i] != NULL)
1687 char *old_header = header;
1688 header = put_field (header, fields[i].name, field_value[i]);
1693 /* Replace the old translation in the header entry. */
1694 header_mp->msgstr = header;
1695 header_mp->msgstr_len = strlen (header) + 1;
1697 /* Update the comments in the header entry. */
1698 if (header_mp->comment != NULL)
1700 const char *subst[4][2];
1704 id = project_id (header);
1705 subst[0][0] = "SOME DESCRIPTIVE TITLE";
1706 subst[0][1] = xasprintf (get_title (), id, id);
1707 subst[1][0] = "PACKAGE";
1709 subst[2][0] = "FIRST AUTHOR <EMAIL@ADDRESS>";
1710 subst[2][1] = field_value[FIELD_LAST_TRANSLATOR];
1711 subst[3][0] = "YEAR";
1714 (time (&now), (localtime (&now))->tm_year + 1900));
1715 subst_string_list (header_mp->comment, SIZEOF (subst), subst);
1718 /* Finally remove the fuzzy attribute. */
1719 header_mp->is_fuzzy = false;
1727 /* Update the msgstr plural entries according to the nplurals count. */
1728 static msgdomain_list_ty *
1729 update_msgstr_plurals (msgdomain_list_ty *mdlp)
1733 for (k = 0; k < mdlp->nitems; k++)
1735 message_list_ty *mlp = mdlp->item[k]->messages;
1736 message_ty *header_entry;
1737 unsigned long int nplurals;
1738 char *untranslated_plural_msgstr;
1741 header_entry = message_list_search (mlp, NULL, "");
1742 nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL);
1743 untranslated_plural_msgstr = XNMALLOC (nplurals, char);
1744 memset (untranslated_plural_msgstr, '\0', nplurals);
1746 for (j = 0; j < mlp->nitems; j++)
1748 message_ty *mp = mlp->item[j];
1749 bool is_untranslated;
1753 if (mp->msgid_plural != NULL)
1755 /* Test if mp is untranslated. (It most likely is.) */
1756 is_untranslated = true;
1757 for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++)
1760 is_untranslated = false;
1763 if (is_untranslated)
1765 /* Change mp->msgstr_len consecutive empty strings into
1766 nplurals consecutive empty strings. */
1767 if (nplurals > mp->msgstr_len)
1768 mp->msgstr = untranslated_plural_msgstr;
1769 mp->msgstr_len = nplurals;