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. 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2, or (at your option) 8 any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software Foundation, 17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 18 19 20#ifdef HAVE_CONFIG_H 21# include "config.h" 22#endif 23#include <alloca.h> 24 25#include <errno.h> 26#include <fcntl.h> 27#include <getopt.h> 28#include <limits.h> 29#include <locale.h> 30#include <stdio.h> 31#include <stdlib.h> 32#include <string.h> 33#include <time.h> 34#include <sys/types.h> 35 36#if HAVE_PWD_H 37# include <pwd.h> 38#endif 39 40#if HAVE_UNISTD_H 41# include <unistd.h> 42#endif 43 44#if HAVE_DIRENT_H 45# include <dirent.h> 46#else 47# define dirent direct 48# if HAVE_SYS_NDIR_H 49# include <sys/ndir.h> 50# endif 51# if HAVE_SYS_DIR_H 52# include <sys/dir.h> 53# endif 54# if HAVE_NDIR_H 55# include <ndir.h> 56# endif 57#endif 58 59#if CLOSEDIR_VOID 60/* Fake a return value. */ 61# define CLOSEDIR(d) (closedir (d), 0) 62#else 63# define CLOSEDIR(d) closedir (d) 64#endif 65 66#if HAVE_DIRENT_H || HAVE_NDIR_H || HAVE_SYS_DIR_H || HAVE_SYS_NDIR_H 67# define HAVE_DIR 1 68#else 69# define HAVE_DIR 0 70#endif 71 72#include "closeout.h" 73#include "error.h" 74#include "error-progname.h" 75#include "progname.h" 76#include "relocatable.h" 77#include "basename.h" 78#include "strpbrk.h" 79#include "strstr.h" 80#include "c-strcase.h" 81#include "message.h" 82#include "read-po.h" 83#include "write-po.h" 84#include "po-charset.h" 85#include "localcharset.h" 86#include "po-time.h" 87#include "plural-table.h" 88#include "xalloc.h" 89#include "xallocsa.h" 90#include "exit.h" 91#include "pathname.h" 92#include "xerror.h" 93#include "msgl-english.h" 94#include "plural-count.h" 95#include "pipe.h" 96#include "wait-process.h" 97#include "getline.h" 98#include "xsetenv.h" 99#include "str-list.h" 100#include "gettext.h" 101 102#define _(str) gettext (str) 103#define N_(str) (str) 104 105/* Get F_OK. It is lacking from <fcntl.h> on Woe32. */ 106#ifndef F_OK 107# define F_OK 0 108#endif 109 110#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) 111 112extern const char * _nl_locale_name (int category, const char *categoryname); 113extern const char * _nl_expand_alias (const char *name); 114 115/* Locale name. */ 116static const char *locale; 117 118/* Language (ISO-639 code) and optional territory (ISO-3166 code). */ 119static const char *catalogname; 120 121/* Language (ISO-639 code). */ 122static const char *language; 123 124/* If true, the user is not considered to be the translator. */ 125static bool no_translator; 126 127/* Long options. */ 128static const struct option long_options[] = 129{ 130 { "help", no_argument, NULL, 'h' }, 131 { "input", required_argument, NULL, 'i' }, 132 { "locale", required_argument, NULL, 'l' }, 133 { "no-translator", no_argument, NULL, CHAR_MAX + 1 }, 134 { "no-wrap", no_argument, NULL, CHAR_MAX + 2 }, 135 { "output-file", required_argument, NULL, 'o' }, 136 { "properties-input", no_argument, NULL, 'P' }, 137 { "properties-output", no_argument, NULL, 'p' }, 138 { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 }, 139 { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 }, 140 { "version", no_argument, NULL, 'V' }, 141 { "width", required_argument, NULL, 'w' }, 142 { NULL, 0, NULL, 0 } 143}; 144 145/* Forward declaration of local functions. */ 146static void usage (int status) 147#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) 148 __attribute__ ((noreturn)) 149#endif 150; 151static const char *find_pot (void); 152static const char *catalogname_for_locale (const char *locale); 153static const char *language_of_locale (const char *locale); 154static char *get_field (const char *header, const char *field); 155static msgdomain_list_ty *fill_header (msgdomain_list_ty *mdlp); 156static msgdomain_list_ty *update_msgstr_plurals (msgdomain_list_ty *mdlp); 157 158 159int 160main (int argc, char **argv) 161{ 162 int opt; 163 bool do_help; 164 bool do_version; 165 char *output_file; 166 const char *input_file; 167 msgdomain_list_ty *result; 168 169 /* Set program name for messages. */ 170 set_program_name (argv[0]); 171 error_print_progname = maybe_print_progname; 172 173#ifdef HAVE_SETLOCALE 174 /* Set locale via LC_ALL. */ 175 setlocale (LC_ALL, ""); 176#endif 177 178 /* Set the text message domain. */ 179 bindtextdomain (PACKAGE, relocate (LOCALEDIR)); 180 textdomain (PACKAGE); 181 182 /* Ensure that write errors on stdout are detected. */ 183 atexit (close_stdout); 184 185 /* Set default values for variables. */ 186 do_help = false; 187 do_version = false; 188 output_file = NULL; 189 input_file = NULL; 190 locale = NULL; 191 192 while ((opt = getopt_long (argc, argv, "hi:l:o:pPVw:", long_options, NULL)) 193 != EOF) 194 switch (opt) 195 { 196 case '\0': /* Long option. */ 197 break; 198 199 case 'h': 200 do_help = true; 201 break; 202 203 case 'i': 204 if (input_file != NULL) 205 { 206 error (EXIT_SUCCESS, 0, _("at most one input file allowed")); 207 usage (EXIT_FAILURE); 208 } 209 input_file = optarg; 210 break; 211 212 case 'l': 213 locale = optarg; 214 break; 215 216 case 'o': 217 output_file = optarg; 218 break; 219 220 case 'p': 221 message_print_syntax_properties (); 222 break; 223 224 case 'P': 225 input_syntax = syntax_properties; 226 break; 227 228 case 'V': 229 do_version = true; 230 break; 231 232 case 'w': 233 { 234 int value; 235 char *endp; 236 value = strtol (optarg, &endp, 10); 237 if (endp != optarg) 238 message_page_width_set (value); 239 } 240 break; 241 242 case CHAR_MAX + 1: 243 no_translator = true; 244 break; 245 246 case CHAR_MAX + 2: /* --no-wrap */ 247 message_page_width_ignore (); 248 break; 249 250 case CHAR_MAX + 3: /* --stringtable-input */ 251 input_syntax = syntax_stringtable; 252 break; 253 254 case CHAR_MAX + 4: /* --stringtable-output */ 255 message_print_syntax_stringtable (); 256 break; 257 258 default: 259 usage (EXIT_FAILURE); 260 break; 261 } 262 263 /* Version information is requested. */ 264 if (do_version) 265 { 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\ 269This is free software; see the source for copying conditions. There is NO\n\ 270warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ 271"), 272 "2001-2005"); 273 printf (_("Written by %s.\n"), "Bruno Haible"); 274 exit (EXIT_SUCCESS); 275 } 276 277 /* Help is requested. */ 278 if (do_help) 279 usage (EXIT_SUCCESS); 280 281 /* Test for extraneous arguments. */ 282 if (optind != argc) 283 error (EXIT_FAILURE, 0, _("too many arguments")); 284 285 /* Search for the input file. */ 286 if (input_file == NULL) 287 input_file = find_pot (); 288 289 /* Determine target locale. */ 290 if (locale == NULL) 291 { 292 locale = _nl_locale_name (LC_MESSAGES, "LC_MESSAGES"); 293 if (strcmp (locale, "C") == 0) 294 { 295 multiline_error (xstrdup (""), 296 xstrdup (_("\ 297You are in a language indifferent environment. Please set\n\ 298your LANG environment variable, as described in the ABOUT-NLS\n\ 299file. This is necessary so you can test your translations.\n"))); 300 exit (EXIT_FAILURE); 301 } 302 } 303 { 304 const char *alias = _nl_expand_alias (locale); 305 if (alias != NULL) 306 locale = alias; 307 } 308 catalogname = catalogname_for_locale (locale); 309 language = language_of_locale (locale); 310 311 /* Default output file name is CATALOGNAME.po. */ 312 if (output_file == NULL) 313 { 314 size_t cnlen = strlen (catalogname); 315 316 output_file = (char *) xmalloc (cnlen + 3 + 1); 317 memcpy (output_file, catalogname, cnlen); 318 memcpy (output_file + cnlen, ".po", 3 + 1); 319 320 /* But don't overwrite existing PO files. */ 321 if (access (output_file, F_OK) == 0) 322 { 323 multiline_error (xstrdup (""), 324 xasprintf (_("\ 325Output file %s already exists.\n\ 326Please specify the locale through the --locale option or\n\ 327the output .po file through the --output-file option.\n"), 328 output_file)); 329 exit (EXIT_FAILURE); 330 } 331 } 332 333 /* Read input file. */ 334 result = read_po_file (input_file); 335 336 /* Fill the header entry. */ 337 result = fill_header (result); 338 339 /* Initialize translations. */ 340 if (strcmp (language, "en") == 0) 341 result = msgdomain_list_english (result); 342 else 343 result = update_msgstr_plurals (result); 344 345 /* Write the modified message list out. */ 346 msgdomain_list_print (result, output_file, true, false); 347 348 if (!no_translator) 349 fprintf (stderr, "\n"); 350 fprintf (stderr, _("Created %s.\n"), output_file); 351 352 exit (EXIT_SUCCESS); 353} 354 355 356/* Display usage information and exit. */ 357static void 358usage (int status) 359{ 360 if (status != EXIT_SUCCESS) 361 fprintf (stderr, _("Try `%s --help' for more information.\n"), 362 program_name); 363 else 364 { 365 printf (_("\ 366Usage: %s [OPTION]\n\ 367"), program_name); 368 printf ("\n"); 369 /* xgettext: no-wrap */ 370 printf (_("\ 371Creates a new PO file, initializing the meta information with values from the\n\ 372user's environment.\n\ 373")); 374 printf ("\n"); 375 printf (_("\ 376Mandatory arguments to long options are mandatory for short options too.\n")); 377 printf ("\n"); 378 printf (_("\ 379Input file location:\n")); 380 printf (_("\ 381 -i, --input=INPUTFILE input POT file\n")); 382 printf (_("\ 383If no input file is given, the current directory is searched for the POT file.\n\ 384If it is -, standard input is read.\n")); 385 printf ("\n"); 386 printf (_("\ 387Output file location:\n")); 388 printf (_("\ 389 -o, --output-file=FILE write output to specified PO file\n")); 390 printf (_("\ 391If no output file is given, it depends on the --locale option or the user's\n\ 392locale setting. If it is -, the results are written to standard output.\n")); 393 printf ("\n"); 394 printf (_("\ 395Input file syntax:\n")); 396 printf (_("\ 397 -P, --properties-input input file is in Java .properties syntax\n")); 398 printf (_("\ 399 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); 400 printf ("\n"); 401 printf (_("\ 402Output details:\n")); 403 printf (_("\ 404 -l, --locale=LL_CC set target locale\n")); 405 printf (_("\ 406 --no-translator assume the PO file is automatically generated\n")); 407 printf (_("\ 408 -p, --properties-output write out a Java .properties file\n")); 409 printf (_("\ 410 --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); 411 printf (_("\ 412 -w, --width=NUMBER set output page width\n")); 413 printf (_("\ 414 --no-wrap do not break long message lines, longer than\n\ 415 the output page width, into several lines\n")); 416 printf ("\n"); 417 printf (_("\ 418Informative output:\n")); 419 printf (_("\ 420 -h, --help display this help and exit\n")); 421 printf (_("\ 422 -V, --version output version information and exit\n")); 423 printf ("\n"); 424 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), 425 stdout); 426 } 427 428 exit (status); 429} 430 431 432/* Search for the POT file and return its name. */ 433static const char * 434find_pot () 435{ 436#if HAVE_DIR 437 DIR *dirp; 438 char *found = NULL; 439 440 dirp = opendir ("."); 441 if (dirp != NULL) 442 { 443 for (;;) 444 { 445 struct dirent *dp; 446 447 errno = 0; 448 dp = readdir (dirp); 449 if (dp != NULL) 450 { 451 const char *name = dp->d_name; 452 size_t namlen = strlen (name); 453 454 if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0) 455 { 456 if (found == NULL) 457 found = xstrdup (name); 458 else 459 { 460 multiline_error (xstrdup (""), 461 xstrdup (_("\ 462Found more than one .pot file.\n\ 463Please specify the input .pot file through the --input option.\n"))); 464 usage (EXIT_FAILURE); 465 } 466 } 467 } 468 else if (errno != 0) 469 error (EXIT_FAILURE, errno, _("error reading current directory")); 470 else 471 break; 472 } 473 if (CLOSEDIR (dirp)) 474 error (EXIT_FAILURE, errno, _("error reading current directory")); 475 476 if (found != NULL) 477 return found; 478 } 479#endif 480 481 multiline_error (xstrdup (""), 482 xstrdup (_("\ 483Found no .pot file in the current directory.\n\ 484Please specify the input .pot file through the --input option.\n"))); 485 usage (EXIT_FAILURE); 486 /* NOTREACHED */ 487 return NULL; 488} 489 490 491/* Return the gettext catalog name corresponding to a locale. If the locale 492 consists of a language and a territory, and the language is mainly spoken 493 in that territory, the territory is removed from the locale name. 494 For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de", 495 because the resulting catalog can be used as a default for all "de_XX", 496 such as "de_AT". */ 497static const char * 498catalogname_for_locale (const char *locale) 499{ 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 */ 619 }; 620 const char *dot; 621 size_t i; 622 623 /* Remove the ".codeset" part from the locale. */ 624 dot = strchr (locale, '.'); 625 if (dot != NULL) 626 { 627 const char *codeset_end; 628 char *shorter_locale; 629 630 codeset_end = strpbrk (dot + 1, "_@+,"); 631 if (codeset_end == NULL) 632 codeset_end = dot + strlen (dot); 633 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; 638 } 639 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) 643 { 644 const char *language_end; 645 size_t len; 646 char *shorter_locale; 647 648 language_end = strchr (locale, '_'); 649 if (language_end == NULL) 650 abort (); 651 652 len = language_end - locale; 653 shorter_locale = (char *) xmalloc (len + 1); 654 memcpy (shorter_locale, locale, len); 655 shorter_locale[len] = '\0'; 656 locale = shorter_locale; 657 break; 658 } 659 660 return locale; 661} 662 663 664/* Return the language of a locale. */ 665static const char * 666language_of_locale (const char *locale) 667{ 668 const char *language_end; 669 670 language_end = strpbrk (locale, "_.@+,"); 671 if (language_end != NULL) 672 { 673 size_t len; 674 char *result; 675 676 len = language_end - locale; 677 result = (char *) xmalloc (len + 1); 678 memcpy (result, locale, len); 679 result[len] = '\0'; 680 681 return result; 682 } 683 else 684 return locale; 685} 686 687 688/* Return the most likely desired charset for the PO file, as a portable 689 charset name. */ 690static const char * 691canonical_locale_charset () 692{ 693 const char *tmp; 694 char *old_LC_ALL; 695 const char *charset; 696 697 /* Save LC_ALL environment variable. */ 698 699 tmp = getenv ("LC_ALL"); 700 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL); 701 702 xsetenv ("LC_ALL", locale, 1); 703 704#ifdef HAVE_SETLOCALE 705 if (setlocale (LC_ALL, "") == NULL) 706 /* Nonexistent locale. Use anything. */ 707 charset = ""; 708 else 709#endif 710 /* Get the locale's charset. */ 711 charset = locale_charset (); 712 713 /* Restore LC_ALL environment variable. */ 714 715 if (old_LC_ALL != NULL) 716 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL); 717 else 718 unsetenv ("LC_ALL"); 719 720#ifdef HAVE_SETLOCALE 721 setlocale (LC_ALL, ""); 722#endif 723 724 /* Canonicalize it. */ 725 charset = po_charset_canonicalize (charset); 726 if (charset == NULL) 727 charset = po_charset_ascii; 728 729 return charset; 730} 731 732 733/* Return the English name of the language. */ 734static const char * 735englishname_of_language () 736{ 737 /* Derived from ISO 639. */ 738 static struct { const char *code; const char *english; } table[] = { 739 { "aa", "Afar" }, 740 { "ab", "Abkhazian" }, 741 { "ae", "Avestan" }, 742 { "af", "Afrikaans" }, 743 { "ak", "Akan" }, 744 { "am", "Amharic" }, 745 { "an", "Aragonese" }, 746 { "ar", "Arabic" }, 747 { "as", "Assamese" }, 748 { "av", "Avaric" }, 749 { "ay", "Aymara" }, 750 { "az", "Azerbaijani" }, 751 { "ba", "Bashkir" }, 752 { "be", "Belarusian" }, 753 { "bg", "Bulgarian" }, 754 { "bh", "Bihari" }, 755 { "bi", "Bislama" }, 756 { "bm", "Bambara" }, 757 { "bn", "Bengali" }, 758 { "bo", "Tibetan" }, 759 { "br", "Breton" }, 760 { "bs", "Bosnian" }, 761 { "ca", "Catalan" }, 762 { "ce", "Chechen" }, 763 { "ch", "Chamorro" }, 764 { "co", "Corsican" }, 765 { "cr", "Cree" }, 766 { "cs", "Czech" }, 767 { "cu", "Church Slavic" }, 768 { "cv", "Chuvash" }, 769 { "cy", "Welsh" }, 770 { "da", "Danish" }, 771 { "de", "German" }, 772 { "dv", "Divehi" }, 773 { "dz", "Dzongkha" }, 774 { "ee", "Ewe" }, 775 { "el", "Greek" }, 776 { "en", "English" }, 777 { "eo", "Esperanto" }, 778 { "es", "Spanish" }, 779 { "et", "Estonian" }, 780 { "eu", "Basque" }, 781 { "fa", "Persian" }, 782 { "ff", "Fulah" }, 783 { "fi", "Finnish" }, 784 { "fj", "Fijian" }, 785 { "fo", "Faroese" }, 786 { "fr", "French" }, 787 { "fy", "Frisian" }, 788 { "ga", "Irish" }, 789 { "gd", "Scots" }, 790 { "gl", "Galician" }, 791 { "gn", "Guarani" }, 792 { "gu", "Gujarati" }, 793 { "gv", "Manx" }, 794 { "ha", "Hausa" }, 795 { "he", "Hebrew" }, 796 { "hi", "Hindi" }, 797 { "ho", "Hiri Motu" }, 798 { "hr", "Croatian" }, 799 { "ht", "Haitian" }, 800 { "hu", "Hungarian" }, 801 { "hy", "Armenian" }, 802 { "hz", "Herero" }, 803 { "ia", "Interlingua" }, 804 { "id", "Indonesian" }, 805 { "ie", "Interlingue" }, 806 { "ig", "Igbo" }, 807 { "ii", "Sichuan Yi" }, 808 { "ik", "Inupiak" }, 809 { "is", "Icelandic" }, 810 { "it", "Italian" }, 811 { "iu", "Inuktitut" }, 812 { "ja", "Japanese" }, 813 { "jw", "Javanese" }, 814 { "ka", "Georgian" }, 815 { "kg", "Kongo" }, 816 { "ki", "Kikuyu" }, 817 { "kj", "Kuanyama" }, 818 { "kk", "Kazakh" }, 819 { "kl", "Kalaallisut" }, 820 { "km", "Khmer" }, 821 { "kn", "Kannada" }, 822 { "ko", "Korean" }, 823 { "kr", "Kanuri" }, 824 { "ks", "Kashmiri" }, 825 { "ku", "Kurdish" }, 826 { "kv", "Komi" }, 827 { "kw", "Cornish" }, 828 { "ky", "Kirghiz" }, 829 { "kok", "Konkani" }, 830 { "la", "Latin" }, 831 { "lb", "Letzeburgesch" }, 832 { "lg", "Ganda" }, 833 { "li", "Limburgish" }, 834 { "ln", "Lingala" }, 835 { "lo", "Laotian" }, 836 { "lt", "Lithuanian" }, 837 { "lu", "Luba-Katanga" }, 838 { "lv", "Latvian" }, 839 { "mg", "Malagasy" }, 840 { "mh", "Marshall" }, 841 { "mi", "Maori" }, 842 { "mk", "Macedonian" }, 843 { "ml", "Malayalam" }, 844 { "mn", "Mongolian" }, 845 { "mo", "Moldavian" }, 846 { "mr", "Marathi" }, 847 { "ms", "Malay" }, 848 { "mt", "Maltese" }, 849 { "my", "Burmese" }, 850 { "mni", "Manipuri" }, 851 { "na", "Nauru" }, 852 { "nb", "Norwegian Bokmal" }, 853 { "nd", "North Ndebele" }, 854 { "ne", "Nepali" }, 855 { "ng", "Ndonga" }, 856 { "nl", "Dutch" }, 857 { "nn", "Norwegian Nynorsk" }, 858 { "no", "Norwegian" }, 859 { "nr", "South Ndebele" }, 860 { "nv", "Navajo" }, 861 { "ny", "Nyanja" }, 862 { "oc", "Occitan" }, 863 { "oj", "Ojibwa" }, 864 { "om", "(Afan) Oromo" }, 865 { "or", "Oriya" }, 866 { "os", "Ossetian" }, 867 { "pa", "Punjabi" }, 868 { "pi", "Pali" }, 869 { "pl", "Polish" }, 870 { "ps", "Pashto" }, 871 { "pt", "Portuguese" }, 872 { "qu", "Quechua" }, 873 { "rm", "Rhaeto-Roman" }, 874 { "rn", "Kirundi" }, 875 { "ro", "Romanian" }, 876 { "ru", "Russian" }, 877 { "rw", "Kinyarwanda" }, 878 { "sa", "Sanskrit" }, 879 { "sc", "Sardinian" }, 880 { "sd", "Sindhi" }, 881 { "se", "Northern Sami" }, 882 { "sg", "Sango" }, 883 { "si", "Sinhalese" }, 884 { "sk", "Slovak" }, 885 { "sl", "Slovenian" }, 886 { "sm", "Samoan" }, 887 { "sn", "Shona" }, 888 { "so", "Somali" }, 889 { "sq", "Albanian" }, 890 { "sr", "Serbian" }, 891 { "ss", "Siswati" }, 892 { "st", "Sesotho" }, 893 { "su", "Sundanese" }, 894 { "sv", "Swedish" }, 895 { "sw", "Swahili" }, 896 { "ta", "Tamil" }, 897 { "te", "Telugu" }, 898 { "tg", "Tajik" }, 899 { "th", "Thai" }, 900 { "ti", "Tigrinya" }, 901 { "tk", "Turkmen" }, 902 { "tl", "Tagalog" }, 903 { "tn", "Setswana" }, 904 { "to", "Tonga" }, 905 { "tr", "Turkish" }, 906 { "ts", "Tsonga" }, 907 { "tt", "Tatar" }, 908 { "tw", "Twi" }, 909 { "ty", "Tahitian" }, 910 { "ug", "Uighur" }, 911 { "uk", "Ukrainian" }, 912 { "ur", "Urdu" }, 913 { "uz", "Uzbek" }, 914 { "ve", "Venda" }, 915 { "vi", "Vietnamese" }, 916 { "vo", "Volapuk" }, 917 { "wo", "Wolof" }, 918 { "wen", "Sorbian" }, 919 { "xh", "Xhosa" }, 920 { "yi", "Yiddish" }, 921 { "yo", "Yoruba" }, 922 { "za", "Zhuang" }, 923 { "zh", "Chinese" }, 924 { "zu", "Zulu" } 925 }; 926 size_t i; 927 928 for (i = 0; i < SIZEOF (table); i ++) 929 if (strcmp (table[i].code, language) == 0) 930 return table[i].english; 931 932 return xasprintf ("Language %s", language); 933} 934 935 936/* Construct the value for the PACKAGE name. */ 937static const char * 938project_id () 939{ 940 const char *gettextlibdir; 941 char *prog; 942 char *argv[3]; 943 pid_t child; 944 int fd[1]; 945 FILE *fp; 946 char *line; 947 size_t linesize; 948 size_t linelen; 949 int exitstatus; 950 951 gettextlibdir = getenv ("GETTEXTLIBDIR"); 952 if (gettextlibdir == NULL || gettextlibdir[0] == '\0') 953 gettextlibdir = relocate (LIBDIR "/gettext"); 954 955 prog = concatenated_pathname (gettextlibdir, "project-id", NULL); 956 957 /* Call the project-id shell script. */ 958 argv[0] = "/bin/sh"; 959 argv[1] = prog; 960 argv[2] = NULL; 961 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, 962 fd); 963 if (child == -1) 964 goto failed; 965 966 /* Retrieve its result. */ 967 fp = fdopen (fd[0], "r"); 968 if (fp == NULL) 969 { 970 error (0, errno, _("fdopen() failed")); 971 goto failed; 972 } 973 974 line = NULL; linesize = 0; 975 linelen = getline (&line, &linesize, fp); 976 if (linelen == (size_t)(-1)) 977 { 978 error (0, 0, _("%s subprocess I/O error"), prog); 979 goto failed; 980 } 981 if (linelen > 0 && line[linelen - 1] == '\n') 982 line[linelen - 1] = '\0'; 983 984 fclose (fp); 985 986 /* Remove zombie process from process list, and retrieve exit status. */ 987 exitstatus = wait_subprocess (child, prog, false, false, true, false); 988 if (exitstatus != 0) 989 { 990 error (0, 0, _("%s subprocess failed with exit code %d"), 991 prog, exitstatus); 992 goto failed; 993 } 994 995 return line; 996 997failed: 998 return "PACKAGE"; 999} 1000 1001 1002/* Construct the value for the Project-Id-Version field. */ 1003static const char * 1004project_id_version () 1005{ 1006 const char *gettextlibdir; 1007 char *prog; 1008 char *argv[4]; 1009 pid_t child; 1010 int fd[1]; 1011 FILE *fp; 1012 char *line; 1013 size_t linesize; 1014 size_t linelen; 1015 int exitstatus; 1016 1017 gettextlibdir = getenv ("GETTEXTLIBDIR"); 1018 if (gettextlibdir == NULL || gettextlibdir[0] == '\0') 1019 gettextlibdir = relocate (LIBDIR "/gettext"); 1020 1021 prog = concatenated_pathname (gettextlibdir, "project-id", NULL); 1022 1023 /* Call the project-id shell script. */ 1024 argv[0] = "/bin/sh"; 1025 argv[1] = prog; 1026 argv[2] = "yes"; 1027 argv[3] = NULL; 1028 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, 1029 fd); 1030 if (child == -1) 1031 goto failed; 1032 1033 /* Retrieve its result. */ 1034 fp = fdopen (fd[0], "r"); 1035 if (fp == NULL) 1036 { 1037 error (0, errno, _("fdopen() failed")); 1038 goto failed; 1039 } 1040 1041 line = NULL; linesize = 0; 1042 linelen = getline (&line, &linesize, fp); 1043 if (linelen == (size_t)(-1)) 1044 { 1045 error (0, 0, _("%s subprocess I/O error"), prog); 1046 goto failed; 1047 } 1048 if (linelen > 0 && line[linelen - 1] == '\n') 1049 line[linelen - 1] = '\0'; 1050 1051 fclose (fp); 1052 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) 1056 { 1057 error (0, 0, _("%s subprocess failed with exit code %d"), 1058 prog, exitstatus); 1059 goto failed; 1060 } 1061 1062 return line; 1063 1064failed: 1065 return "PACKAGE VERSION"; 1066} 1067 1068 1069/* Construct the value for the PO-Revision-Date field. */ 1070static const char * 1071po_revision_date (const char *header) 1072{ 1073 if (no_translator) 1074 /* Because the PO file is automatically generated, we use the 1075 POT-Creation-Date, not the current time. */ 1076 return get_field (header, "POT-Creation-Date"); 1077 else 1078 { 1079 /* Assume the translator will modify the PO file now. */ 1080 time_t now; 1081 1082 time (&now); 1083 return po_strftime (&now); 1084 } 1085} 1086 1087 1088/* Returns the struct passwd entry for the current user. */ 1089static struct passwd * 1090get_user_pwd () 1091{ 1092#if HAVE_PWD_H /* Only Unix, not native Woe32. */ 1093 const char *username; 1094 struct passwd *userpasswd; 1095 1096 /* 1. attempt: getpwnam(getenv("USER")) */ 1097 username = getenv ("USER"); 1098 if (username != NULL) 1099 { 1100 errno = 0; 1101 userpasswd = getpwnam (username); 1102 if (userpasswd != NULL) 1103 return userpasswd; 1104 if (errno != 0) 1105 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username); 1106 } 1107 1108 /* 2. attempt: getpwnam(getlogin()) */ 1109 username = getlogin (); 1110 if (username != NULL) 1111 { 1112 errno = 0; 1113 userpasswd = getpwnam (username); 1114 if (userpasswd != NULL) 1115 return userpasswd; 1116 if (errno != 0) 1117 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username); 1118 } 1119 1120 /* 3. attempt: getpwuid(getuid()) */ 1121 errno = 0; 1122 userpasswd = getpwuid (getuid ()); 1123 if (userpasswd != NULL) 1124 return userpasswd; 1125 if (errno != 0) 1126 error (EXIT_FAILURE, errno, "getpwuid(\"%d\")", getuid ()); 1127#endif 1128 1129 return NULL; 1130} 1131 1132 1133/* Return the user's full name. */ 1134static const char * 1135get_user_fullname () 1136{ 1137 struct passwd *pwd; 1138 const char *fullname; 1139 const char *fullname_end; 1140 char *result; 1141 1142 pwd = get_user_pwd (); 1143#if HAVE_PWD_H 1144 if (pwd != NULL) 1145 { 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); 1151 1152 result = (char *) xmalloc (fullname_end - fullname + 1); 1153 memcpy (result, fullname, fullname_end - fullname); 1154 result[fullname_end - fullname] = '\0'; 1155 1156 return result; 1157 } 1158#endif 1159 1160 return NULL; 1161} 1162 1163 1164/* Return the user's email address. */ 1165static const char * 1166get_user_email () 1167{ 1168 const char *prog = relocate (LIBDIR "/gettext/user-email"); 1169 char *argv[4]; 1170 pid_t child; 1171 int fd[1]; 1172 FILE *fp; 1173 char *line; 1174 size_t linesize; 1175 size_t linelen; 1176 int exitstatus; 1177 1178 /* Ask the user for his email address. */ 1179 argv[0] = "/bin/sh"; 1180 argv[1] = (char *) prog; 1181 argv[2] = (char *) _("\ 1182The new message catalog should contain your email address, so that users can\n\ 1183give you feedback about the translations, and so that maintainers can contact\n\ 1184you in case of unexpected technical problems.\n"); 1185 argv[3] = NULL; 1186 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, 1187 fd); 1188 if (child == -1) 1189 goto failed; 1190 1191 /* Retrieve his answer. */ 1192 fp = fdopen (fd[0], "r"); 1193 if (fp == NULL) 1194 { 1195 error (0, errno, _("fdopen() failed")); 1196 goto failed; 1197 } 1198 1199 line = NULL; linesize = 0; 1200 linelen = getline (&line, &linesize, fp); 1201 if (linelen == (size_t)(-1)) 1202 { 1203 error (0, 0, _("%s subprocess I/O error"), prog); 1204 goto failed; 1205 } 1206 if (linelen > 0 && line[linelen - 1] == '\n') 1207 line[linelen - 1] = '\0'; 1208 1209 fclose (fp); 1210 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) 1214 { 1215 error (0, 0, _("%s subprocess failed with exit code %d"), 1216 prog, exitstatus); 1217 goto failed; 1218 } 1219 1220 return line; 1221 1222failed: 1223 return "EMAIL@ADDRESS"; 1224} 1225 1226 1227/* Construct the value for the Last-Translator field. */ 1228static const char * 1229last_translator () 1230{ 1231 if (no_translator) 1232 return "Automatically generated"; 1233 else 1234 { 1235 const char *fullname = get_user_fullname (); 1236 const char *email = get_user_email (); 1237 1238 if (fullname != NULL) 1239 return xasprintf ("%s <%s>", fullname, email); 1240 else 1241 return xasprintf ("<%s>", email); 1242 } 1243} 1244 1245 1246/* Return the language team's mailing list address or homepage URL. */ 1247static const char * 1248language_team_address () 1249{ 1250 const char *prog = relocate (PROJECTSDIR "/team-address"); 1251 char *argv[7]; 1252 pid_t child; 1253 int fd[1]; 1254 FILE *fp; 1255 char *line; 1256 size_t linesize; 1257 size_t linelen; 1258 int exitstatus; 1259 1260 /* Call the team-address shell script. */ 1261 argv[0] = "/bin/sh"; 1262 argv[1] = (char *) prog; 1263 argv[2] = (char *) relocate (PROJECTSDIR); 1264 argv[3] = (char *) relocate (LIBDIR "/gettext"); 1265 argv[4] = (char *) catalogname; 1266 argv[5] = (char *) language; 1267 argv[6] = NULL; 1268 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, 1269 fd); 1270 if (child == -1) 1271 goto failed; 1272 1273 /* Retrieve its result. */ 1274 fp = fdopen (fd[0], "r"); 1275 if (fp == NULL) 1276 { 1277 error (0, errno, _("fdopen() failed")); 1278 goto failed; 1279 } 1280 1281 line = NULL; linesize = 0; 1282 linelen = getline (&line, &linesize, fp); 1283 if (linelen == (size_t)(-1)) 1284 line = ""; 1285 else if (linelen > 0 && line[linelen - 1] == '\n') 1286 line[linelen - 1] = '\0'; 1287 1288 fclose (fp); 1289 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) 1293 { 1294 error (0, 0, _("%s subprocess failed with exit code %d"), 1295 prog, exitstatus); 1296 goto failed; 1297 } 1298 1299 return line; 1300 1301failed: 1302 return ""; 1303} 1304 1305 1306/* Construct the value for the Language-Team field. */ 1307static const char * 1308language_team () 1309{ 1310 if (no_translator) 1311 return "none"; 1312 else 1313 { 1314 const char *englishname = englishname_of_language (); 1315 const char *address = language_team_address (); 1316 1317 if (address != NULL && address[0] != '\0') 1318 return xasprintf ("%s %s", englishname, address); 1319 else 1320 return englishname; 1321 } 1322} 1323 1324 1325/* Construct the value for the MIME-Version field. */ 1326static const char * 1327mime_version () 1328{ 1329 return "1.0"; 1330} 1331 1332 1333/* Construct the value for the Content-Type field. */ 1334static const char * 1335content_type (const char *header) 1336{ 1337 bool was_utf8; 1338 const char *old_field; 1339 1340 /* If the POT file contains charset=UTF-8, it means that the POT file 1341 contains non-ASCII characters, and we keep the UTF-8 encoding. 1342 Otherwise, when the POT file is plain ASCII, we use the locale's 1343 encoding. */ 1344 was_utf8 = false; 1345 old_field = get_field (header, "Content-Type"); 1346 if (old_field != NULL) 1347 { 1348 const char *charsetstr = strstr (old_field, "charset="); 1349 1350 if (charsetstr != NULL) 1351 { 1352 charsetstr += strlen ("charset="); 1353 was_utf8 = (c_strcasecmp (charsetstr, "UTF-8") == 0); 1354 } 1355 } 1356 return xasprintf ("text/plain; charset=%s", 1357 was_utf8 ? "UTF-8" : canonical_locale_charset ()); 1358} 1359 1360 1361/* Construct the value for the Content-Transfer-Encoding field. */ 1362static const char * 1363content_transfer_encoding () 1364{ 1365 return "8bit"; 1366} 1367 1368 1369/* Construct the value for the Plural-Forms field. */ 1370static const char * 1371plural_forms () 1372{ 1373 size_t i; 1374 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; 1379 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; 1384 1385 return NULL; 1386} 1387 1388 1389static struct 1390{ 1391 const char *name; 1392 const char * (*getter0) (void); 1393 const char * (*getter1) (const char *header); 1394} 1395fields[] = 1396 { 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 } 1405 }; 1406 1407#define NFIELDS SIZEOF (fields) 1408#define FIELD_LAST_TRANSLATOR 2 1409 1410 1411/* Retrieve a freshly allocated copy of a field's value. */ 1412static char * 1413get_field (const char *header, const char *field) 1414{ 1415 size_t len = strlen (field); 1416 const char *line; 1417 1418 for (line = header;;) 1419 { 1420 if (strncmp (line, field, len) == 0 1421 && line[len] == ':' && line[len + 1] == ' ') 1422 { 1423 const char *value_start; 1424 const char *value_end; 1425 char *value; 1426 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); 1431 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'; 1435 1436 return value; 1437 } 1438 1439 line = strchr (line, '\n'); 1440 if (line != NULL) 1441 line++; 1442 else 1443 break; 1444 } 1445 1446 return NULL; 1447} 1448 1449/* Add a field with value to a header, and return the new header. */ 1450static char * 1451put_field (const char *old_header, const char *field, const char *value) 1452{ 1453 size_t len = strlen (field); 1454 const char *line; 1455 char *new_header; 1456 char *p; 1457 1458 for (line = old_header;;) 1459 { 1460 if (strncmp (line, field, len) == 0 1461 && line[len] == ':' && line[len + 1] == ' ') 1462 { 1463 const char *value_start; 1464 const char *value_end; 1465 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); 1470 1471 new_header = (char *) xmalloc (strlen (old_header) 1472 - (value_end - value_start) 1473 + strlen (value) 1474 + (*value_end != '\n' ? 1 : 0) 1475 + 1); 1476 p = new_header; 1477 memcpy (p, old_header, value_start - old_header); 1478 p += value_start - old_header; 1479 memcpy (p, value, strlen (value)); 1480 p += strlen (value); 1481 if (*value_end != '\n') 1482 *p++ = '\n'; 1483 strcpy (p, value_end); 1484 1485 return new_header; 1486 } 1487 1488 line = strchr (line, '\n'); 1489 if (line != NULL) 1490 line++; 1491 else 1492 break; 1493 } 1494 1495 new_header = (char *) xmalloc (strlen (old_header) + 1 1496 + len + 2 + strlen (value) + 1 1497 + 1); 1498 p = new_header; 1499 memcpy (p, old_header, strlen (old_header)); 1500 p += strlen (old_header); 1501 if (p > new_header && p[-1] != '\n') 1502 *p++ = '\n'; 1503 memcpy (p, field, len); 1504 p += len; 1505 *p++ = ':'; 1506 *p++ = ' '; 1507 memcpy (p, value, strlen (value)); 1508 p += strlen (value); 1509 *p++ = '\n'; 1510 *p = '\0'; 1511 1512 return new_header; 1513} 1514 1515 1516/* Return the title format string. */ 1517static const char * 1518get_title () 1519{ 1520 /* This is tricky. We want the translation in the given locale specified by 1521 the command line, not the current locale. But we want it in the encoding 1522 that we put into the header entry, not the encoding of that locale. 1523 We could avoid the use of OUTPUT_CHARSET by using a separate message 1524 catalog and bind_textdomain_codeset(), but that doesn't seem worth the 1525 trouble for one single message. */ 1526 const char *encoding; 1527 const char *tmp; 1528 char *old_LC_ALL; 1529 char *old_LANGUAGE; 1530 char *old_OUTPUT_CHARSET; 1531 const char *msgid; 1532 const char *english; 1533 const char *result; 1534 1535 encoding = canonical_locale_charset (); 1536 1537 /* First, the English title. */ 1538 english = xasprintf ("%s translations for %%s package", 1539 englishname_of_language ()); 1540 1541 /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */ 1542 1543 tmp = getenv ("LC_ALL"); 1544 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL); 1545 1546 tmp = getenv ("LANGUAGE"); 1547 old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL); 1548 1549 tmp = getenv ("OUTPUT_CHARSET"); 1550 old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL); 1551 1552 xsetenv ("LC_ALL", locale, 1); 1553 unsetenv ("LANGUAGE"); 1554 xsetenv ("OUTPUT_CHARSET", encoding, 1); 1555 1556#ifdef HAVE_SETLOCALE 1557 if (setlocale (LC_ALL, "") == NULL) 1558 /* Nonexistent locale. Use the English title. */ 1559 result = english; 1560 else 1561#endif 1562 { 1563 /* Fetch the translation. */ 1564 /* TRANSLATORS: "English" needs to be replaced by your language. 1565 For example in it.po write "Traduzioni italiani ...", 1566 *not* "Traduzioni inglesi ...". */ 1567 msgid = N_("English translations for %s package"); 1568 result = gettext (msgid); 1569 if (result != msgid && strcmp (result, msgid) != 0) 1570 /* Use the English and the foreign title. */ 1571 result = xasprintf ("%s\n%s", english, result); 1572 else 1573 /* No translation found. Use the English title. */ 1574 result = english; 1575 } 1576 1577 /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */ 1578 1579 if (old_LC_ALL != NULL) 1580 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL); 1581 else 1582 unsetenv ("LC_ALL"); 1583 1584 if (old_LANGUAGE != NULL) 1585 xsetenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE); 1586 else 1587 unsetenv ("LANGUAGE"); 1588 1589 if (old_OUTPUT_CHARSET != NULL) 1590 xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET); 1591 else 1592 unsetenv ("OUTPUT_CHARSET"); 1593 1594#ifdef HAVE_SETLOCALE 1595 setlocale (LC_ALL, ""); 1596#endif 1597 1598 return result; 1599} 1600 1601 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. */ 1605static const char * 1606subst_string (const char *str, 1607 unsigned int nsubst, const char *(*subst)[2]) 1608{ 1609 if (nsubst > 0) 1610 { 1611 char *malloced = NULL; 1612 size_t *substlen; 1613 size_t i; 1614 unsigned int j; 1615 1616 substlen = (size_t *) xallocsa (nsubst * sizeof (size_t)); 1617 for (j = 0; j < nsubst; j++) 1618 { 1619 substlen[j] = strlen (subst[j][0]); 1620 if (substlen[j] == 0) 1621 abort (); 1622 } 1623 1624 for (i = 0;;) 1625 { 1626 if (str[i] == '\0') 1627 break; 1628 for (j = 0; j < nsubst; j++) 1629 if (*(str + i) == *subst[j][0] 1630 && strncmp (str + i, subst[j][0], substlen[j]) == 0) 1631 { 1632 size_t replacement_len = strlen (subst[j][1]); 1633 size_t new_len = strlen (str) - substlen[j] + replacement_len; 1634 char *new_str = (char *) xmalloc (new_len + 1); 1635 memcpy (new_str, str, i); 1636 memcpy (new_str + i, subst[j][1], replacement_len); 1637 strcpy (new_str + i + replacement_len, str + i + substlen[j]); 1638 if (malloced != NULL) 1639 free (malloced); 1640 str = new_str; 1641 malloced = new_str; 1642 i += replacement_len; 1643 break; 1644 } 1645 if (j == nsubst) 1646 i++; 1647 } 1648 1649 freesa (substlen); 1650 } 1651 1652 return str; 1653} 1654 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. */ 1658static void 1659subst_string_list (string_list_ty *slp, 1660 unsigned int nsubst, const char *(*subst)[2]) 1661{ 1662 size_t j; 1663 1664 for (j = 0; j < slp->nitems; j++) 1665 slp->item[j] = subst_string (slp->item[j], nsubst, subst); 1666} 1667 1668 1669/* Fill the templates in all fields of the header entry. */ 1670static msgdomain_list_ty * 1671fill_header (msgdomain_list_ty *mdlp) 1672{ 1673 /* Cache the strings filled in, for use when there are multiple domains 1674 and a header entry for each domain. */ 1675 const char *field_value[NFIELDS]; 1676 size_t k, j, i; 1677 1678 for (i = 0; i < NFIELDS; i++) 1679 field_value[i] = NULL; 1680 1681 for (k = 0; k < mdlp->nitems; k++) 1682 { 1683 message_list_ty *mlp = mdlp->item[k]->messages; 1684 1685 if (mlp->nitems > 0) 1686 { 1687 message_ty *header_mp = NULL; 1688 char *header; 1689 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) 1693 { 1694 header_mp = mlp->item[j]; 1695 break; 1696 } 1697 1698 /* If it wasn't found, provide one. */ 1699 if (header_mp == NULL) 1700 { 1701 static lex_pos_ty pos = { __FILE__, __LINE__ }; 1702 1703 header_mp = message_alloc ("", NULL, "", 1, &pos); 1704 message_list_prepend (mlp, header_mp); 1705 } 1706 1707 header = xstrdup (header_mp->msgstr); 1708 1709 /* Fill in the fields. */ 1710 for (i = 0; i < NFIELDS; i++) 1711 { 1712 if (field_value[i] == NULL) 1713 field_value[i] = 1714 (fields[i].getter1 != NULL 1715 ? fields[i].getter1 (header) 1716 : fields[i].getter0 ()); 1717 1718 if (field_value[i] != NULL) 1719 { 1720 char *old_header = header; 1721 header = put_field (header, fields[i].name, field_value[i]); 1722 free (old_header); 1723 } 1724 } 1725 1726 /* Replace the old translation in the header entry. */ 1727 header_mp->msgstr = header; 1728 header_mp->msgstr_len = strlen (header) + 1; 1729 1730 /* Update the comments in the header entry. */ 1731 if (header_mp->comment != NULL) 1732 { 1733 const char *subst[4][2]; 1734 const char *id; 1735 time_t now; 1736 1737 id = project_id (); 1738 subst[0][0] = "SOME DESCRIPTIVE TITLE"; 1739 subst[0][1] = xasprintf (get_title (), id, id); 1740 subst[1][0] = "PACKAGE"; 1741 subst[1][1] = id; 1742 subst[2][0] = "FIRST AUTHOR <EMAIL@ADDRESS>"; 1743 subst[2][1] = field_value[FIELD_LAST_TRANSLATOR]; 1744 subst[3][0] = "YEAR"; 1745 subst[3][1] = 1746 xasprintf ("%d", 1747 (time (&now), (localtime (&now))->tm_year + 1900)); 1748 subst_string_list (header_mp->comment, SIZEOF (subst), subst); 1749 } 1750 1751 /* Finally remove the fuzzy attribute. */ 1752 header_mp->is_fuzzy = false; 1753 } 1754 } 1755 1756 return mdlp; 1757} 1758 1759 1760/* Update the msgstr plural entries according to the nplurals count. */ 1761static msgdomain_list_ty * 1762update_msgstr_plurals (msgdomain_list_ty *mdlp) 1763{ 1764 size_t k; 1765 1766 for (k = 0; k < mdlp->nitems; k++) 1767 { 1768 message_list_ty *mlp = mdlp->item[k]->messages; 1769 message_ty *header_entry; 1770 unsigned long int nplurals; 1771 char *untranslated_plural_msgstr; 1772 size_t j; 1773 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); 1778 1779 for (j = 0; j < mlp->nitems; j++) 1780 { 1781 message_ty *mp = mlp->item[j]; 1782 bool is_untranslated; 1783 const char *p; 1784 const char *pend; 1785 1786 if (mp->msgid_plural != NULL) 1787 { 1788 /* Test if mp is untranslated. (It most likely is.) */ 1789 is_untranslated = true; 1790 for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++) 1791 if (*p != '\0') 1792 { 1793 is_untranslated = false; 1794 break; 1795 } 1796 if (is_untranslated) 1797 { 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; 1803 } 1804 } 1805 } 1806 } 1807 return mdlp; 1808} 1809