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)
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. */
34 #include <sys/types.h>
47 # define dirent direct
49 # include <sys/ndir.h>
60 /* Fake a return value. */
61 # define CLOSEDIR(d) (closedir (d), 0)
63 # define CLOSEDIR(d) closedir (d)
66 #if HAVE_DIRENT_H || HAVE_NDIR_H || HAVE_SYS_DIR_H || HAVE_SYS_NDIR_H
74 #include "error-progname.h"
76 #include "relocatable.h"
80 #include "c-strcase.h"
84 #include "po-charset.h"
85 #include "localcharset.h"
87 #include "plural-table.h"
93 #include "msgl-english.h"
94 #include "plural-count.h"
96 #include "wait-process.h"
102 #define _(str) gettext (str)
103 #define N_(str) (str)
105 /* Get F_OK. It is lacking from <fcntl.h> on Woe32. */
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
);
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
;
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' },
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
))
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
)
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
, "");
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. */
192 while ((opt
= getopt_long (argc
, argv
, "hi:l:o:pPVw:", long_options
, NULL
))
196 case '\0': /* Long option. */
204 if (input_file
!= NULL
)
206 error (EXIT_SUCCESS
, 0, _("at most one input file allowed"));
207 usage (EXIT_FAILURE
);
217 output_file
= optarg
;
221 message_print_syntax_properties ();
225 input_syntax
= syntax_properties
;
236 value
= strtol (optarg
, &endp
, 10);
238 message_page_width_set (value
);
243 no_translator
= true;
246 case CHAR_MAX
+ 2: /* --no-wrap */
247 message_page_width_ignore ();
250 case CHAR_MAX
+ 3: /* --stringtable-input */
251 input_syntax
= syntax_stringtable
;
254 case CHAR_MAX
+ 4: /* --stringtable-output */
255 message_print_syntax_stringtable ();
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 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\
273 printf (_("Written by %s.\n"), "Bruno Haible");
277 /* Help is requested. */
279 usage (EXIT_SUCCESS
);
281 /* Test for extraneous arguments. */
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. */
292 locale
= _nl_locale_name (LC_MESSAGES
, "LC_MESSAGES");
293 if (strcmp (locale
, "C") == 0)
295 multiline_error (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")));
304 const char *alias
= _nl_expand_alias (locale
);
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 (""),
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"),
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
);
343 result
= update_msgstr_plurals (result
);
345 /* Write the modified message list out. */
346 msgdomain_list_print (result
, output_file
, true, false);
349 fprintf (stderr
, "\n");
350 fprintf (stderr
, _("Created %s.\n"), output_file
);
356 /* Display usage information and exit. */
360 if (status
!= EXIT_SUCCESS
)
361 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
366 Usage: %s [OPTION]\n\
369 /* xgettext: no-wrap */
371 Creates a new PO file, initializing the meta information with values from the\n\
372 user's environment.\n\
376 Mandatory arguments to long options are mandatory for short options too.\n"));
379 Input file location:\n"));
381 -i, --input=INPUTFILE input POT file\n"));
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"));
387 Output file location:\n"));
389 -o, --output-file=FILE write output to specified PO file\n"));
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"));
395 Input file syntax:\n"));
397 -P, --properties-input input file is in Java .properties syntax\n"));
399 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
402 Output details:\n"));
404 -l, --locale=LL_CC set target locale\n"));
406 --no-translator assume the PO file is automatically generated\n"));
408 -p, --properties-output write out a Java .properties file\n"));
410 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
412 -w, --width=NUMBER set output page width\n"));
414 --no-wrap do not break long message lines, longer than\n\
415 the output page width, into several lines\n"));
418 Informative output:\n"));
420 -h, --help display this help and exit\n"));
422 -V, --version output version information and exit\n"));
424 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
432 /* Search for the POT file and return its name. */
440 dirp
= opendir (".");
451 const char *name
= dp
->d_name
;
452 size_t namlen
= strlen (name
);
454 if (namlen
> 4 && memcmp (name
+ namlen
- 4, ".pot", 4) == 0)
457 found
= xstrdup (name
);
460 multiline_error (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
);
469 error (EXIT_FAILURE
, errno
, _("error reading current directory"));
474 error (EXIT_FAILURE
, errno
, _("error reading current directory"));
481 multiline_error (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
);
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",
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 */
623 /* Remove the ".codeset" part from the locale. */
624 dot
= strchr (locale
, '.');
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
;
646 char *shorter_locale
;
648 language_end
= strchr (locale
, '_');
649 if (language_end
== NULL
)
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
;
664 /* Return the language of a locale. */
666 language_of_locale (const char *locale
)
668 const char *language_end
;
670 language_end
= strpbrk (locale
, "_.@+,");
671 if (language_end
!= NULL
)
676 len
= language_end
- locale
;
677 result
= (char *) xmalloc (len
+ 1);
678 memcpy (result
, locale
, len
);
688 /* Return the most likely desired charset for the PO file, as a portable
691 canonical_locale_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. */
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
);
720 #ifdef HAVE_SETLOCALE
721 setlocale (LC_ALL
, "");
724 /* Canonicalize it. */
725 charset
= po_charset_canonicalize (charset
);
727 charset
= po_charset_ascii
;
733 /* Return the English name of the language. */
735 englishname_of_language ()
737 /* Derived from ISO 639. */
738 static struct { const char *code
; const char *english
; } table
[] = {
740 { "ab", "Abkhazian" },
742 { "af", "Afrikaans" },
745 { "an", "Aragonese" },
747 { "as", "Assamese" },
750 { "az", "Azerbaijani" },
752 { "be", "Belarusian" },
753 { "bg", "Bulgarian" },
763 { "ch", "Chamorro" },
764 { "co", "Corsican" },
767 { "cu", "Church Slavic" },
773 { "dz", "Dzongkha" },
777 { "eo", "Esperanto" },
779 { "et", "Estonian" },
790 { "gl", "Galician" },
792 { "gu", "Gujarati" },
797 { "ho", "Hiri Motu" },
798 { "hr", "Croatian" },
800 { "hu", "Hungarian" },
801 { "hy", "Armenian" },
803 { "ia", "Interlingua" },
804 { "id", "Indonesian" },
805 { "ie", "Interlingue" },
807 { "ii", "Sichuan Yi" },
809 { "is", "Icelandic" },
811 { "iu", "Inuktitut" },
812 { "ja", "Japanese" },
813 { "jw", "Javanese" },
814 { "ka", "Georgian" },
817 { "kj", "Kuanyama" },
819 { "kl", "Kalaallisut" },
824 { "ks", "Kashmiri" },
829 { "kok", "Konkani" },
831 { "lb", "Letzeburgesch" },
833 { "li", "Limburgish" },
836 { "lt", "Lithuanian" },
837 { "lu", "Luba-Katanga" },
839 { "mg", "Malagasy" },
840 { "mh", "Marshall" },
842 { "mk", "Macedonian" },
843 { "ml", "Malayalam" },
844 { "mn", "Mongolian" },
845 { "mo", "Moldavian" },
850 { "mni", "Manipuri" },
852 { "nb", "Norwegian Bokmal" },
853 { "nd", "North Ndebele" },
857 { "nn", "Norwegian Nynorsk" },
858 { "no", "Norwegian" },
859 { "nr", "South Ndebele" },
864 { "om", "(Afan) Oromo" },
866 { "os", "Ossetian" },
871 { "pt", "Portuguese" },
873 { "rm", "Rhaeto-Roman" },
875 { "ro", "Romanian" },
877 { "rw", "Kinyarwanda" },
878 { "sa", "Sanskrit" },
879 { "sc", "Sardinian" },
881 { "se", "Northern Sami" },
883 { "si", "Sinhalese" },
885 { "sl", "Slovenian" },
889 { "sq", "Albanian" },
893 { "su", "Sundanese" },
900 { "ti", "Tigrinya" },
903 { "tn", "Setswana" },
909 { "ty", "Tahitian" },
911 { "uk", "Ukrainian" },
915 { "vi", "Vietnamese" },
918 { "wen", "Sorbian" },
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. */
940 const char *gettextlibdir
;
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. */
961 child
= create_pipe_in (prog
, "/bin/sh", argv
, DEV_NULL
, false, true, false,
966 /* Retrieve its result. */
967 fp
= fdopen (fd
[0], "r");
970 error (0, errno
, _("fdopen() 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
);
981 if (linelen
> 0 && line
[linelen
- 1] == '\n')
982 line
[linelen
- 1] = '\0';
986 /* Remove zombie process from process list, and retrieve exit status. */
987 exitstatus
= wait_subprocess (child
, prog
, false, false, true, false);
990 error (0, 0, _("%s subprocess failed with exit code %d"),
1002 /* Construct the value for the Project-Id-Version field. */
1004 project_id_version ()
1006 const char *gettextlibdir
;
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";
1028 child
= create_pipe_in (prog
, "/bin/sh", argv
, DEV_NULL
, false, true, false,
1033 /* Retrieve its result. */
1034 fp
= fdopen (fd
[0], "r");
1037 error (0, errno
, _("fdopen() 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
);
1048 if (linelen
> 0 && line
[linelen
- 1] == '\n')
1049 line
[linelen
- 1] = '\0';
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"),
1065 return "PACKAGE VERSION";
1069 /* Construct the value for the PO-Revision-Date field. */
1071 po_revision_date (const char *header
)
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");
1079 /* Assume the translator will modify the PO file now. */
1083 return po_strftime (&now
);
1088 /* Returns the struct passwd entry for the current user. */
1089 static struct passwd
*
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
)
1101 userpasswd
= getpwnam (username
);
1102 if (userpasswd
!= NULL
)
1105 error (EXIT_FAILURE
, errno
, "getpwnam(\"%s\")", username
);
1108 /* 2. attempt: getpwnam(getlogin()) */
1109 username
= getlogin ();
1110 if (username
!= NULL
)
1113 userpasswd
= getpwnam (username
);
1114 if (userpasswd
!= NULL
)
1117 error (EXIT_FAILURE
, errno
, "getpwnam(\"%s\")", username
);
1120 /* 3. attempt: getpwuid(getuid()) */
1122 userpasswd
= getpwuid (getuid ());
1123 if (userpasswd
!= NULL
)
1126 error (EXIT_FAILURE
, errno
, "getpwuid(\"%d\")", getuid ());
1133 /* Return the user's full name. */
1135 get_user_fullname ()
1138 const char *fullname
;
1139 const char *fullname_end
;
1142 pwd
= get_user_pwd ();
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';
1164 /* Return the user's email address. */
1168 const char *prog
= relocate (LIBDIR
"/gettext/user-email");
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");
1186 child
= create_pipe_in (prog
, "/bin/sh", argv
, DEV_NULL
, false, true, false,
1191 /* Retrieve his answer. */
1192 fp
= fdopen (fd
[0], "r");
1195 error (0, errno
, _("fdopen() 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
);
1206 if (linelen
> 0 && line
[linelen
- 1] == '\n')
1207 line
[linelen
- 1] = '\0';
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"),
1223 return "EMAIL@ADDRESS";
1227 /* Construct the value for the Last-Translator field. */
1232 return "Automatically generated";
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
);
1241 return xasprintf ("<%s>", email
);
1246 /* Return the language team's mailing list address or homepage URL. */
1248 language_team_address ()
1250 const char *prog
= relocate (PROJECTSDIR
"/team-address");
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
;
1268 child
= create_pipe_in (prog
, "/bin/sh", argv
, DEV_NULL
, false, true, false,
1273 /* Retrieve its result. */
1274 fp
= fdopen (fd
[0], "r");
1277 error (0, errno
, _("fdopen() failed"));
1281 line
= NULL
; linesize
= 0;
1282 linelen
= getline (&line
, &linesize
, fp
);
1283 if (linelen
== (size_t)(-1))
1285 else if (linelen
> 0 && line
[linelen
- 1] == '\n')
1286 line
[linelen
- 1] = '\0';
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"),
1306 /* Construct the value for the Language-Team field. */
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
);
1325 /* Construct the value for the MIME-Version field. */
1333 /* Construct the value for the Content-Type field. */
1335 content_type (const char *header
)
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
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. */
1363 content_transfer_encoding ()
1369 /* Construct the value for the Plural-Forms field. */
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
;
1392 const char * (*getter0
) (void);
1393 const char * (*getter1
) (const char *header
);
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. */
1413 get_field (const char *header
, const char *field
)
1415 size_t len
= strlen (field
);
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
;
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';
1439 line
= strchr (line
, '\n');
1449 /* Add a field with value to a header, and return the new header. */
1451 put_field (const char *old_header
, const char *field
, const char *value
)
1453 size_t len
= strlen (field
);
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
)
1474 + (*value_end
!= '\n' ? 1 : 0)
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')
1483 strcpy (p
, value_end
);
1488 line
= strchr (line
, '\n');
1495 new_header
= (char *) xmalloc (strlen (old_header
) + 1
1496 + len
+ 2 + strlen (value
) + 1
1499 memcpy (p
, old_header
, strlen (old_header
));
1500 p
+= strlen (old_header
);
1501 if (p
> new_header
&& p
[-1] != '\n')
1503 memcpy (p
, field
, len
);
1507 memcpy (p
, value
, strlen (value
));
1508 p
+= strlen (value
);
1516 /* Return the title format string. */
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
;
1530 char *old_OUTPUT_CHARSET
;
1532 const char *english
;
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. */
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
);
1573 /* No translation found. Use the English title. */
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
);
1582 unsetenv ("LC_ALL");
1584 if (old_LANGUAGE
!= NULL
)
1585 xsetenv ("LANGUAGE", old_LANGUAGE
, 1), free (old_LANGUAGE
);
1587 unsetenv ("LANGUAGE");
1589 if (old_OUTPUT_CHARSET
!= NULL
)
1590 xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET
, 1), free (old_OUTPUT_CHARSET
);
1592 unsetenv ("OUTPUT_CHARSET");
1594 #ifdef HAVE_SETLOCALE
1595 setlocale (LC_ALL
, "");
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. */
1606 subst_string (const char *str
,
1607 unsigned int nsubst
, const char *(*subst
)[2])
1611 char *malloced
= NULL
;
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)
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
)
1642 i
+= replacement_len
;
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. */
1659 subst_string_list (string_list_ty
*slp
,
1660 unsigned int nsubst
, const char *(*subst
)[2])
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
];
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
;
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
];
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
)
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
]);
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];
1738 subst
[0][0] = "SOME DESCRIPTIVE TITLE";
1739 subst
[0][1] = xasprintf (get_title (), id
, id
);
1740 subst
[1][0] = "PACKAGE";
1742 subst
[2][0] = "FIRST AUTHOR <EMAIL@ADDRESS>";
1743 subst
[2][1] = field_value
[FIELD_LAST_TRANSLATOR
];
1744 subst
[3][0] = "YEAR";
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;
1760 /* Update the msgstr plural entries according to the nplurals count. */
1761 static msgdomain_list_ty
*
1762 update_msgstr_plurals (msgdomain_list_ty
*mdlp
)
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
;
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
;
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
++)
1793 is_untranslated
= false;
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
;