1/* Initializes a new PO file. 2 Copyright (C) 2001-2007 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 3 of the License, or 8 (at your option) 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, see <http://www.gnu.org/licenses/>. */ 17 18 19#ifdef HAVE_CONFIG_H 20# include "config.h" 21#endif 22#include <alloca.h> 23 24#include <errno.h> 25#include <fcntl.h> 26#include <getopt.h> 27#include <limits.h> 28#include <locale.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32#include <time.h> 33#include <sys/types.h> 34 35#if HAVE_PWD_H 36# include <pwd.h> 37#endif 38 39#include <unistd.h> 40 41#if HAVE_DIRENT_H 42# include <dirent.h> 43#endif 44 45#if HAVE_DIRENT_H 46# define HAVE_DIR 1 47#else 48# define HAVE_DIR 0 49#endif 50 51#include "closeout.h" 52#include "error.h" 53#include "error-progname.h" 54#include "progname.h" 55#include "relocatable.h" 56#include "basename.h" 57#include "c-strstr.h" 58#include "c-strcase.h" 59#include "message.h" 60#include "read-catalog.h" 61#include "read-po.h" 62#include "read-properties.h" 63#include "read-stringtable.h" 64#include "write-catalog.h" 65#include "write-po.h" 66#include "write-properties.h" 67#include "write-stringtable.h" 68#include "po-charset.h" 69#include "localcharset.h" 70#include "localename.h" 71#include "po-time.h" 72#include "plural-table.h" 73#include "lang-table.h" 74#include "xalloc.h" 75#include "xmalloca.h" 76#include "filename.h" 77#include "xerror.h" 78#include "xvasprintf.h" 79#include "msgl-english.h" 80#include "plural-count.h" 81#include "pipe.h" 82#include "wait-process.h" 83#include "xsetenv.h" 84#include "str-list.h" 85#include "propername.h" 86#include "gettext.h" 87 88#define _(str) gettext (str) 89#define N_(str) (str) 90 91/* Get F_OK. It is lacking from <fcntl.h> on Woe32. */ 92#ifndef F_OK 93# define F_OK 0 94#endif 95 96#define SIZEOF(a) (sizeof(a) / sizeof(a[0])) 97 98extern const char * _nl_expand_alias (const char *name); 99 100/* Locale name. */ 101static const char *locale; 102 103/* Language (ISO-639 code) and optional territory (ISO-3166 code). */ 104static const char *catalogname; 105 106/* Language (ISO-639 code). */ 107static const char *language; 108 109/* If true, the user is not considered to be the translator. */ 110static bool no_translator; 111 112/* Long options. */ 113static const struct option long_options[] = 114{ 115 { "help", no_argument, NULL, 'h' }, 116 { "input", required_argument, NULL, 'i' }, 117 { "locale", required_argument, NULL, 'l' }, 118 { "no-translator", no_argument, NULL, CHAR_MAX + 1 }, 119 { "no-wrap", no_argument, NULL, CHAR_MAX + 2 }, 120 { "output-file", required_argument, NULL, 'o' }, 121 { "properties-input", no_argument, NULL, 'P' }, 122 { "properties-output", no_argument, NULL, 'p' }, 123 { "stringtable-input", no_argument, NULL, CHAR_MAX + 3 }, 124 { "stringtable-output", no_argument, NULL, CHAR_MAX + 4 }, 125 { "version", no_argument, NULL, 'V' }, 126 { "width", required_argument, NULL, 'w' }, 127 { NULL, 0, NULL, 0 } 128}; 129 130/* Forward declaration of local functions. */ 131static void usage (int status) 132#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) 133 __attribute__ ((noreturn)) 134#endif 135; 136static const char *find_pot (void); 137static const char *catalogname_for_locale (const char *locale); 138static const char *language_of_locale (const char *locale); 139static char *get_field (const char *header, const char *field); 140static msgdomain_list_ty *fill_header (msgdomain_list_ty *mdlp); 141static msgdomain_list_ty *update_msgstr_plurals (msgdomain_list_ty *mdlp); 142 143 144int 145main (int argc, char **argv) 146{ 147 int opt; 148 bool do_help; 149 bool do_version; 150 char *output_file; 151 const char *input_file; 152 msgdomain_list_ty *result; 153 catalog_input_format_ty input_syntax = &input_format_po; 154 catalog_output_format_ty output_syntax = &output_format_po; 155 156 /* Set program name for messages. */ 157 set_program_name (argv[0]); 158 error_print_progname = maybe_print_progname; 159 160#ifdef HAVE_SETLOCALE 161 /* Set locale via LC_ALL. */ 162 setlocale (LC_ALL, ""); 163#endif 164 165 /* Set the text message domain. */ 166 bindtextdomain (PACKAGE, relocate (LOCALEDIR)); 167 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); 168 textdomain (PACKAGE); 169 170 /* Ensure that write errors on stdout are detected. */ 171 atexit (close_stdout); 172 173 /* Set default values for variables. */ 174 do_help = false; 175 do_version = false; 176 output_file = NULL; 177 input_file = NULL; 178 locale = NULL; 179 180 while ((opt = getopt_long (argc, argv, "hi:l:o:pPVw:", long_options, NULL)) 181 != EOF) 182 switch (opt) 183 { 184 case '\0': /* Long option. */ 185 break; 186 187 case 'h': 188 do_help = true; 189 break; 190 191 case 'i': 192 if (input_file != NULL) 193 { 194 error (EXIT_SUCCESS, 0, _("at most one input file allowed")); 195 usage (EXIT_FAILURE); 196 } 197 input_file = optarg; 198 break; 199 200 case 'l': 201 locale = optarg; 202 break; 203 204 case 'o': 205 output_file = optarg; 206 break; 207 208 case 'p': 209 output_syntax = &output_format_properties; 210 break; 211 212 case 'P': 213 input_syntax = &input_format_properties; 214 break; 215 216 case 'V': 217 do_version = true; 218 break; 219 220 case 'w': 221 { 222 int value; 223 char *endp; 224 value = strtol (optarg, &endp, 10); 225 if (endp != optarg) 226 message_page_width_set (value); 227 } 228 break; 229 230 case CHAR_MAX + 1: 231 no_translator = true; 232 break; 233 234 case CHAR_MAX + 2: /* --no-wrap */ 235 message_page_width_ignore (); 236 break; 237 238 case CHAR_MAX + 3: /* --stringtable-input */ 239 input_syntax = &input_format_stringtable; 240 break; 241 242 case CHAR_MAX + 4: /* --stringtable-output */ 243 output_syntax = &output_format_stringtable; 244 break; 245 246 default: 247 usage (EXIT_FAILURE); 248 break; 249 } 250 251 /* Version information is requested. */ 252 if (do_version) 253 { 254 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); 255 /* xgettext: no-wrap */ 256 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ 257License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\ 258This is free software: you are free to change and redistribute it.\n\ 259There is NO WARRANTY, to the extent permitted by law.\n\ 260"), 261 "2001-2007"); 262 printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); 263 exit (EXIT_SUCCESS); 264 } 265 266 /* Help is requested. */ 267 if (do_help) 268 usage (EXIT_SUCCESS); 269 270 /* Test for extraneous arguments. */ 271 if (optind != argc) 272 error (EXIT_FAILURE, 0, _("too many arguments")); 273 274 /* Search for the input file. */ 275 if (input_file == NULL) 276 input_file = find_pot (); 277 278 /* Determine target locale. */ 279 if (locale == NULL) 280 { 281 locale = gl_locale_name (LC_MESSAGES, "LC_MESSAGES"); 282 if (strcmp (locale, "C") == 0) 283 { 284 multiline_error (xstrdup (""), 285 xstrdup (_("\ 286You are in a language indifferent environment. Please set\n\ 287your LANG environment variable, as described in the ABOUT-NLS\n\ 288file. This is necessary so you can test your translations.\n"))); 289 exit (EXIT_FAILURE); 290 } 291 } 292 { 293 const char *alias = _nl_expand_alias (locale); 294 if (alias != NULL) 295 locale = alias; 296 } 297 catalogname = catalogname_for_locale (locale); 298 language = language_of_locale (locale); 299 300 /* Default output file name is CATALOGNAME.po. */ 301 if (output_file == NULL) 302 { 303 output_file = xasprintf ("%s.po", catalogname); 304 305 /* But don't overwrite existing PO files. */ 306 if (access (output_file, F_OK) == 0) 307 { 308 multiline_error (xstrdup (""), 309 xasprintf (_("\ 310Output file %s already exists.\n\ 311Please specify the locale through the --locale option or\n\ 312the output .po file through the --output-file option.\n"), 313 output_file)); 314 exit (EXIT_FAILURE); 315 } 316 } 317 318 /* Read input file. */ 319 result = read_catalog_file (input_file, input_syntax); 320 321 /* Fill the header entry. */ 322 result = fill_header (result); 323 324 /* Initialize translations. */ 325 if (strcmp (language, "en") == 0) 326 result = msgdomain_list_english (result); 327 else 328 result = update_msgstr_plurals (result); 329 330 /* Write the modified message list out. */ 331 msgdomain_list_print (result, output_file, output_syntax, true, false); 332 333 if (!no_translator) 334 fprintf (stderr, "\n"); 335 fprintf (stderr, _("Created %s.\n"), output_file); 336 337 exit (EXIT_SUCCESS); 338} 339 340 341/* Display usage information and exit. */ 342static void 343usage (int status) 344{ 345 if (status != EXIT_SUCCESS) 346 fprintf (stderr, _("Try `%s --help' for more information.\n"), 347 program_name); 348 else 349 { 350 printf (_("\ 351Usage: %s [OPTION]\n\ 352"), program_name); 353 printf ("\n"); 354 /* xgettext: no-wrap */ 355 printf (_("\ 356Creates a new PO file, initializing the meta information with values from the\n\ 357user's environment.\n\ 358")); 359 printf ("\n"); 360 printf (_("\ 361Mandatory arguments to long options are mandatory for short options too.\n")); 362 printf ("\n"); 363 printf (_("\ 364Input file location:\n")); 365 printf (_("\ 366 -i, --input=INPUTFILE input POT file\n")); 367 printf (_("\ 368If no input file is given, the current directory is searched for the POT file.\n\ 369If it is -, standard input is read.\n")); 370 printf ("\n"); 371 printf (_("\ 372Output file location:\n")); 373 printf (_("\ 374 -o, --output-file=FILE write output to specified PO file\n")); 375 printf (_("\ 376If no output file is given, it depends on the --locale option or the user's\n\ 377locale setting. If it is -, the results are written to standard output.\n")); 378 printf ("\n"); 379 printf (_("\ 380Input file syntax:\n")); 381 printf (_("\ 382 -P, --properties-input input file is in Java .properties syntax\n")); 383 printf (_("\ 384 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); 385 printf ("\n"); 386 printf (_("\ 387Output details:\n")); 388 printf (_("\ 389 -l, --locale=LL_CC set target locale\n")); 390 printf (_("\ 391 --no-translator assume the PO file is automatically generated\n")); 392 printf (_("\ 393 -p, --properties-output write out a Java .properties file\n")); 394 printf (_("\ 395 --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); 396 printf (_("\ 397 -w, --width=NUMBER set output page width\n")); 398 printf (_("\ 399 --no-wrap do not break long message lines, longer than\n\ 400 the output page width, into several lines\n")); 401 printf ("\n"); 402 printf (_("\ 403Informative output:\n")); 404 printf (_("\ 405 -h, --help display this help and exit\n")); 406 printf (_("\ 407 -V, --version output version information and exit\n")); 408 printf ("\n"); 409 /* TRANSLATORS: The placeholder indicates the bug-reporting address 410 for this package. Please add _another line_ saying 411 "Report translation bugs to <...>\n" with the address for translation 412 bugs (typically your translation team's web or email address). */ 413 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), 414 stdout); 415 } 416 417 exit (status); 418} 419 420 421/* Search for the POT file and return its name. */ 422static const char * 423find_pot () 424{ 425#if HAVE_DIR 426 DIR *dirp; 427 char *found = NULL; 428 429 dirp = opendir ("."); 430 if (dirp != NULL) 431 { 432 for (;;) 433 { 434 struct dirent *dp; 435 436 errno = 0; 437 dp = readdir (dirp); 438 if (dp != NULL) 439 { 440 const char *name = dp->d_name; 441 size_t namlen = strlen (name); 442 443 if (namlen > 4 && memcmp (name + namlen - 4, ".pot", 4) == 0) 444 { 445 if (found == NULL) 446 found = xstrdup (name); 447 else 448 { 449 multiline_error (xstrdup (""), 450 xstrdup (_("\ 451Found more than one .pot file.\n\ 452Please specify the input .pot file through the --input option.\n"))); 453 usage (EXIT_FAILURE); 454 } 455 } 456 } 457 else if (errno != 0) 458 error (EXIT_FAILURE, errno, _("error reading current directory")); 459 else 460 break; 461 } 462 if (closedir (dirp)) 463 error (EXIT_FAILURE, errno, _("error reading current directory")); 464 465 if (found != NULL) 466 return found; 467 } 468#endif 469 470 multiline_error (xstrdup (""), 471 xstrdup (_("\ 472Found no .pot file in the current directory.\n\ 473Please specify the input .pot file through the --input option.\n"))); 474 usage (EXIT_FAILURE); 475 /* NOTREACHED */ 476 return NULL; 477} 478 479 480/* Return the gettext catalog name corresponding to a locale. If the locale 481 consists of a language and a territory, and the language is mainly spoken 482 in that territory, the territory is removed from the locale name. 483 For example, "de_DE" or "de_DE.ISO-8859-1" are simplified to "de", 484 because the resulting catalog can be used as a default for all "de_XX", 485 such as "de_AT". */ 486static const char * 487catalogname_for_locale (const char *locale) 488{ 489 static const char *locales_with_principal_territory[] = { 490 /* Language Main territory */ 491 "ace_ID", /* Achinese Indonesia */ 492 "af_ZA", /* Afrikaans South Africa */ 493 "ak_GH", /* Akan Ghana */ 494 "am_ET", /* Amharic Ethiopia */ 495 "an_ES", /* Aragonese Spain */ 496 "ang_GB", /* Old English Britain */ 497 "as_IN", /* Assamese India */ 498 "av_RU", /* Avaric Russia */ 499 "awa_IN", /* Awadhi India */ 500 "az_AZ", /* Azerbaijani Azerbaijan */ 501 "bad_CF", /* Banda Central African Republic */ 502 "ban_ID", /* Balinese Indonesia */ 503 "be_BY", /* Belarusian Belarus */ 504 "bem_ZM", /* Bemba Zambia */ 505 "bg_BG", /* Bulgarian Bulgaria */ 506 "bho_IN", /* Bhojpuri India */ 507 "bik_PH", /* Bikol Philippines */ 508 "bin_NG", /* Bini Nigeria */ 509 "bm_ML", /* Bambara Mali */ 510 "bn_IN", /* Bengali India */ 511 "bo_CN", /* Tibetan China */ 512 "br_FR", /* Breton France */ 513 "bs_BA", /* Bosnian Bosnia */ 514 "btk_ID", /* Batak Indonesia */ 515 "bug_ID", /* Buginese Indonesia */ 516 "ca_ES", /* Catalan Spain */ 517 "ce_RU", /* Chechen Russia */ 518 "ceb_PH", /* Cebuano Philippines */ 519 "co_FR", /* Corsican France */ 520 "cr_CA", /* Cree Canada */ 521 "cs_CZ", /* Czech Czech Republic */ 522 "csb_PL", /* Kashubian Poland */ 523 "cy_GB", /* Welsh Britain */ 524 "da_DK", /* Danish Denmark */ 525 "de_DE", /* German Germany */ 526 "din_SD", /* Dinka Sudan */ 527 "doi_IN", /* Dogri India */ 528 "dv_MV", /* Divehi Maldives */ 529 "dz_BT", /* Dzongkha Bhutan */ 530 "ee_GH", /* ��w�� Ghana */ 531 "el_GR", /* Greek Greece */ 532 /* Don't put "en_GB" or "en_US" here. That would be asking for fruitless 533 political discussion. */ 534 "es_ES", /* Spanish Spain */ 535 "et_EE", /* Estonian Estonia */ 536 "fa_IR", /* Persian Iran */ 537 "fi_FI", /* Finnish Finland */ 538 "fil_PH", /* Filipino Philippines */ 539 "fj_FJ", /* Fijian Fiji */ 540 "fo_FO", /* Faroese Faeroe Islands */ 541 "fon_BJ", /* Fon Benin */ 542 "fr_FR", /* French France */ 543 "fy_NL", /* Western Frisian Netherlands */ 544 "ga_IE", /* Irish Ireland */ 545 "gd_GB", /* Scots Britain */ 546 "gon_IN", /* Gondi India */ 547 "gsw_CH", /* Swiss German Switzerland */ 548 "gu_IN", /* Gujarati India */ 549 "he_IL", /* Hebrew Israel */ 550 "hi_IN", /* Hindi India */ 551 "hil_PH", /* Hiligaynon Philippines */ 552 "hr_HR", /* Croatian Croatia */ 553 "ht_HT", /* Haitian Haiti */ 554 "hu_HU", /* Hungarian Hungary */ 555 "hy_AM", /* Armenian Armenia */ 556 "id_ID", /* Indonesian Indonesia */ 557 "ig_NG", /* Igbo Nigeria */ 558 "ii_CN", /* Sichuan Yi China */ 559 "ilo_PH", /* Iloko Philippines */ 560 "is_IS", /* Icelandic Iceland */ 561 "it_IT", /* Italian Italy */ 562 "ja_JP", /* Japanese Japan */ 563 "jab_NG", /* Hyam Nigeria */ 564 "jv_ID", /* Javanese Indonesia */ 565 "ka_GE", /* Georgian Georgia */ 566 "kab_DZ", /* Kabyle Algeria */ 567 "kaj_NG", /* Jju Nigeria */ 568 "kam_KE", /* Kamba Kenya */ 569 "kmb_AO", /* Kimbundu Angola */ 570 "kcg_NG", /* Tyap Nigeria */ 571 "kdm_NG", /* Kagoma Nigeria */ 572 "kg_CD", /* Kongo Democratic Republic of Congo */ 573 "kk_KZ", /* Kazakh Kazakhstan */ 574 "kl_GL", /* Kalaallisut Greenland */ 575 "km_KH", /* Khmer Cambodia */ 576 "kn_IN", /* Kannada India */ 577 "ko_KR", /* Korean Korea (South) */ 578 "kok_IN", /* Konkani India */ 579 "kr_NG", /* Kanuri Nigeria */ 580 "kru_IN", /* Kurukh India */ 581 "lg_UG", /* Ganda Uganda */ 582 "li_BE", /* Limburgish Belgium */ 583 "lo_LA", /* Laotian Laos */ 584 "lt_LT", /* Lithuanian Lithuania */ 585 "lu_CD", /* Luba-Katanga Democratic Republic of Congo */ 586 "lua_CD", /* Luba-Lulua Democratic Republic of Congo */ 587 "luo_KE", /* Luo Kenya */ 588 "lv_LV", /* Latvian Latvia */ 589 "mad_ID", /* Madurese Indonesia */ 590 "mag_IN", /* Magahi India */ 591 "mai_IN", /* Maithili India */ 592 "mak_ID", /* Makasar Indonesia */ 593 "man_ML", /* Mandingo Mali */ 594 "men_SL", /* Mende Sierra Leone */ 595 "mg_MG", /* Malagasy Madagascar */ 596 "min_ID", /* Minangkabau Indonesia */ 597 "mk_MK", /* Macedonian Macedonia */ 598 "ml_IN", /* Malayalam India */ 599 "mn_MN", /* Mongolian Mongolia */ 600 "mni_IN", /* Manipuri India */ 601 "mos_BF", /* Mossi Burkina Faso */ 602 "mr_IN", /* Marathi India */ 603 "ms_MY", /* Malay Malaysia */ 604 "mt_MT", /* Maltese Malta */ 605 "mwr_IN", /* Marwari India */ 606 "my_MM", /* Burmese Myanmar */ 607 "na_NR", /* Nauru Nauru */ 608 "nah_MX", /* Nahuatl Mexico */ 609 "nap_IT", /* Neapolitan Italy */ 610 "nb_NO", /* Norwegian Bokm��l Norway */ 611 "nds_DE", /* Low Saxon Germany */ 612 "ne_NP", /* Nepali Nepal */ 613 "nl_NL", /* Dutch Netherlands */ 614 "nn_NO", /* Norwegian Nynorsk Norway */ 615 "no_NO", /* Norwegian Norway */ 616 "nr_ZA", /* South Ndebele South Africa */ 617 "nso_ZA", /* Northern Sotho South Africa */ 618 "nym_TZ", /* Nyamwezi Tanzania */ 619 "nyn_UG", /* Nyankole Uganda */ 620 "oc_FR", /* Occitan France */ 621 "oj_CA", /* Ojibwa Canada */ 622 "or_IN", /* Oriya India */ 623 "pa_IN", /* Punjabi India */ 624 "pag_PH", /* Pangasinan Philippines */ 625 "pam_PH", /* Pampanga Philippines */ 626 "pbb_CO", /* P��ez Colombia */ 627 "pl_PL", /* Polish Poland */ 628 "ps_AF", /* Pashto Afghanistan */ 629 "pt_PT", /* Portuguese Portugal */ 630 "raj_IN", /* Rajasthani India */ 631 "rm_CH", /* Rhaeto-Roman Switzerland */ 632 "rn_BI", /* Kirundi Burundi */ 633 "ro_RO", /* Romanian Romania */ 634 "ru_RU", /* Russian Russia */ 635 "sa_IN", /* Sanskrit India */ 636 "sas_ID", /* Sasak Indonesia */ 637 "sat_IN", /* Santali India */ 638 "sc_IT", /* Sardinian Italy */ 639 "scn_IT", /* Sicilian Italy */ 640 "sg_CF", /* Sango Central African Republic */ 641 "shn_MM", /* Shan Myanmar */ 642 "si_LK", /* Sinhala Sri Lanka */ 643 "sid_ET", /* Sidamo Ethiopia */ 644 "sk_SK", /* Slovak Slovakia */ 645 "sl_SI", /* Slovenian Slovenia */ 646 "so_SO", /* Somali Somalia */ 647 "sq_AL", /* Albanian Albania */ 648 "sr_RS", /* Serbian Serbia */ 649 "sr_YU", /* Serbian Yugoslavia - this line can be removed in 2010 */ 650 "srr_SN", /* Serer Senegal */ 651 "suk_TZ", /* Sukuma Tanzania */ 652 "sus_GN", /* Susu Guinea */ 653 "sv_SE", /* Swedish Sweden */ 654 "te_IN", /* Telugu India */ 655 "tem_SL", /* Timne Sierra Leone */ 656 "tet_ID", /* Tetum Indonesia */ 657 "tg_TJ", /* Tajik Tajikistan */ 658 "th_TH", /* Thai Thailand */ 659 "tiv_NG", /* Tiv Nigeria */ 660 "tk_TM", /* Turkmen Turkmenistan */ 661 "tl_PH", /* Tagalog Philippines */ 662 "to_TO", /* Tonga Tonga */ 663 "tr_TR", /* Turkish Turkey */ 664 "tum_MW", /* Tumbuka Malawi */ 665 "uk_UA", /* Ukrainian Ukraine */ 666 "umb_AO", /* Umbundu Angola */ 667 "ur_PK", /* Urdu Pakistan */ 668 "uz_UZ", /* Uzbek Uzbekistan */ 669 "ve_ZA", /* Venda South Africa */ 670 "vi_VN", /* Vietnamese Vietnam */ 671 "wa_BE", /* Walloon Belgium */ 672 "wal_ET", /* Walamo Ethiopia */ 673 "war_PH", /* Waray Philippines */ 674 "wen_DE", /* Sorbian Germany */ 675 "yao_MW", /* Yao Malawi */ 676 "zap_MX" /* Zapotec Mexico */ 677 }; 678 const char *dot; 679 size_t i; 680 681 /* Remove the ".codeset" part from the locale. */ 682 dot = strchr (locale, '.'); 683 if (dot != NULL) 684 { 685 const char *codeset_end; 686 char *shorter_locale; 687 688 codeset_end = strpbrk (dot + 1, "_@"); 689 if (codeset_end == NULL) 690 codeset_end = dot + strlen (dot); 691 692 shorter_locale = XNMALLOC (strlen (locale), char); 693 memcpy (shorter_locale, locale, dot - locale); 694 strcpy (shorter_locale + (dot - locale), codeset_end); 695 locale = shorter_locale; 696 } 697 698 /* If the territory is the language's principal territory, drop it. */ 699 for (i = 0; i < SIZEOF (locales_with_principal_territory); i++) 700 if (strcmp (locale, locales_with_principal_territory[i]) == 0) 701 { 702 const char *language_end; 703 size_t len; 704 char *shorter_locale; 705 706 language_end = strchr (locale, '_'); 707 if (language_end == NULL) 708 abort (); 709 710 len = language_end - locale; 711 shorter_locale = XNMALLOC (len + 1, char); 712 memcpy (shorter_locale, locale, len); 713 shorter_locale[len] = '\0'; 714 locale = shorter_locale; 715 break; 716 } 717 718 return locale; 719} 720 721 722/* Return the language of a locale. */ 723static const char * 724language_of_locale (const char *locale) 725{ 726 const char *language_end; 727 728 language_end = strpbrk (locale, "_.@"); 729 if (language_end != NULL) 730 { 731 size_t len; 732 char *result; 733 734 len = language_end - locale; 735 result = XNMALLOC (len + 1, char); 736 memcpy (result, locale, len); 737 result[len] = '\0'; 738 739 return result; 740 } 741 else 742 return locale; 743} 744 745 746/* Return the most likely desired charset for the PO file, as a portable 747 charset name. */ 748static const char * 749canonical_locale_charset () 750{ 751 const char *tmp; 752 char *old_LC_ALL; 753 const char *charset; 754 755 /* Save LC_ALL environment variable. */ 756 757 tmp = getenv ("LC_ALL"); 758 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL); 759 760 xsetenv ("LC_ALL", locale, 1); 761 762#ifdef HAVE_SETLOCALE 763 if (setlocale (LC_ALL, "") == NULL) 764 /* Nonexistent locale. Use anything. */ 765 charset = ""; 766 else 767#endif 768 /* Get the locale's charset. */ 769 charset = locale_charset (); 770 771 /* Restore LC_ALL environment variable. */ 772 773 if (old_LC_ALL != NULL) 774 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL); 775 else 776 unsetenv ("LC_ALL"); 777 778#ifdef HAVE_SETLOCALE 779 setlocale (LC_ALL, ""); 780#endif 781 782 /* Canonicalize it. */ 783 charset = po_charset_canonicalize (charset); 784 if (charset == NULL) 785 charset = po_charset_ascii; 786 787 return charset; 788} 789 790 791/* Return the English name of the language. */ 792static const char * 793englishname_of_language () 794{ 795 size_t i; 796 797 for (i = 0; i < language_table_size; i++) 798 if (strcmp (language_table[i].code, language) == 0) 799 return language_table[i].english; 800 801 return xasprintf ("Language %s", language); 802} 803 804 805/* Construct the value for the PACKAGE name. */ 806static const char * 807project_id () 808{ 809 const char *gettextlibdir; 810 char *prog; 811 char *argv[3]; 812 pid_t child; 813 int fd[1]; 814 FILE *fp; 815 char *line; 816 size_t linesize; 817 size_t linelen; 818 int exitstatus; 819 820 gettextlibdir = getenv ("GETTEXTLIBDIR"); 821 if (gettextlibdir == NULL || gettextlibdir[0] == '\0') 822 gettextlibdir = relocate (LIBDIR "/gettext"); 823 824 prog = concatenated_filename (gettextlibdir, "project-id", NULL); 825 826 /* Call the project-id shell script. */ 827 argv[0] = "/bin/sh"; 828 argv[1] = prog; 829 argv[2] = NULL; 830 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, 831 fd); 832 if (child == -1) 833 goto failed; 834 835 /* Retrieve its result. */ 836 fp = fdopen (fd[0], "r"); 837 if (fp == NULL) 838 { 839 error (0, errno, _("fdopen() failed")); 840 goto failed; 841 } 842 843 line = NULL; linesize = 0; 844 linelen = getline (&line, &linesize, fp); 845 if (linelen == (size_t)(-1)) 846 { 847 error (0, 0, _("%s subprocess I/O error"), prog); 848 fclose (fp); 849 goto failed; 850 } 851 if (linelen > 0 && line[linelen - 1] == '\n') 852 line[linelen - 1] = '\0'; 853 854 fclose (fp); 855 856 /* Remove zombie process from process list, and retrieve exit status. */ 857 exitstatus = wait_subprocess (child, prog, false, false, true, false); 858 if (exitstatus != 0) 859 { 860 error (0, 0, _("%s subprocess failed with exit code %d"), 861 prog, exitstatus); 862 goto failed; 863 } 864 865 return line; 866 867failed: 868 return "PACKAGE"; 869} 870 871 872/* Construct the value for the Project-Id-Version field. */ 873static const char * 874project_id_version (const char *header) 875{ 876 const char *old_field; 877 const char *gettextlibdir; 878 char *prog; 879 char *argv[4]; 880 pid_t child; 881 int fd[1]; 882 FILE *fp; 883 char *line; 884 size_t linesize; 885 size_t linelen; 886 int exitstatus; 887 888 /* Return the old value if present, assuming it was already filled in by 889 xgettext. */ 890 old_field = get_field (header, "Project-Id-Version"); 891 if (old_field != NULL && strcmp (old_field, "PACKAGE VERSION") != 0) 892 return old_field; 893 894 gettextlibdir = getenv ("GETTEXTLIBDIR"); 895 if (gettextlibdir == NULL || gettextlibdir[0] == '\0') 896 gettextlibdir = relocate (LIBDIR "/gettext"); 897 898 prog = concatenated_filename (gettextlibdir, "project-id", NULL); 899 900 /* Call the project-id shell script. */ 901 argv[0] = "/bin/sh"; 902 argv[1] = prog; 903 argv[2] = "yes"; 904 argv[3] = NULL; 905 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, 906 fd); 907 if (child == -1) 908 goto failed; 909 910 /* Retrieve its result. */ 911 fp = fdopen (fd[0], "r"); 912 if (fp == NULL) 913 { 914 error (0, errno, _("fdopen() failed")); 915 goto failed; 916 } 917 918 line = NULL; linesize = 0; 919 linelen = getline (&line, &linesize, fp); 920 if (linelen == (size_t)(-1)) 921 { 922 error (0, 0, _("%s subprocess I/O error"), prog); 923 fclose (fp); 924 goto failed; 925 } 926 if (linelen > 0 && line[linelen - 1] == '\n') 927 line[linelen - 1] = '\0'; 928 929 fclose (fp); 930 931 /* Remove zombie process from process list, and retrieve exit status. */ 932 exitstatus = wait_subprocess (child, prog, false, false, true, false); 933 if (exitstatus != 0) 934 { 935 error (0, 0, _("%s subprocess failed with exit code %d"), 936 prog, exitstatus); 937 goto failed; 938 } 939 940 return line; 941 942failed: 943 return "PACKAGE VERSION"; 944} 945 946 947/* Construct the value for the PO-Revision-Date field. */ 948static const char * 949po_revision_date (const char *header) 950{ 951 if (no_translator) 952 /* Because the PO file is automatically generated, we use the 953 POT-Creation-Date, not the current time. */ 954 return get_field (header, "POT-Creation-Date"); 955 else 956 { 957 /* Assume the translator will modify the PO file now. */ 958 time_t now; 959 960 time (&now); 961 return po_strftime (&now); 962 } 963} 964 965 966/* Returns the struct passwd entry for the current user. */ 967static struct passwd * 968get_user_pwd () 969{ 970#if HAVE_PWD_H /* Only Unix, not native Woe32. */ 971 const char *username; 972 struct passwd *userpasswd; 973 974 /* 1. attempt: getpwnam(getenv("USER")) */ 975 username = getenv ("USER"); 976 if (username != NULL) 977 { 978 errno = 0; 979 userpasswd = getpwnam (username); 980 if (userpasswd != NULL) 981 return userpasswd; 982 if (errno != 0) 983 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username); 984 } 985 986 /* 2. attempt: getpwnam(getlogin()) */ 987 username = getlogin (); 988 if (username != NULL) 989 { 990 errno = 0; 991 userpasswd = getpwnam (username); 992 if (userpasswd != NULL) 993 return userpasswd; 994 if (errno != 0) 995 error (EXIT_FAILURE, errno, "getpwnam(\"%s\")", username); 996 } 997 998 /* 3. attempt: getpwuid(getuid()) */ 999 errno = 0; 1000 userpasswd = getpwuid (getuid ()); 1001 if (userpasswd != NULL) 1002 return userpasswd; 1003 if (errno != 0) 1004 error (EXIT_FAILURE, errno, "getpwuid(\"%d\")", getuid ()); 1005#endif 1006 1007 return NULL; 1008} 1009 1010 1011/* Return the user's full name. */ 1012static const char * 1013get_user_fullname () 1014{ 1015 struct passwd *pwd; 1016 1017 pwd = get_user_pwd (); 1018#if HAVE_PWD_H 1019 if (pwd != NULL) 1020 { 1021 const char *fullname; 1022 const char *fullname_end; 1023 char *result; 1024 1025 /* Return the pw_gecos field, upto the first comma (if any). */ 1026 fullname = pwd->pw_gecos; 1027 fullname_end = strchr (fullname, ','); 1028 if (fullname_end == NULL) 1029 fullname_end = fullname + strlen (fullname); 1030 1031 result = XNMALLOC (fullname_end - fullname + 1, char); 1032 memcpy (result, fullname, fullname_end - fullname); 1033 result[fullname_end - fullname] = '\0'; 1034 1035 return result; 1036 } 1037#endif 1038 1039 return NULL; 1040} 1041 1042 1043/* Return the user's email address. */ 1044static const char * 1045get_user_email () 1046{ 1047 const char *prog = relocate (LIBDIR "/gettext/user-email"); 1048 char *argv[4]; 1049 pid_t child; 1050 int fd[1]; 1051 FILE *fp; 1052 char *line; 1053 size_t linesize; 1054 size_t linelen; 1055 int exitstatus; 1056 1057 /* Ask the user for his email address. */ 1058 argv[0] = "/bin/sh"; 1059 argv[1] = (char *) prog; 1060 argv[2] = (char *) _("\ 1061The new message catalog should contain your email address, so that users can\n\ 1062give you feedback about the translations, and so that maintainers can contact\n\ 1063you in case of unexpected technical problems.\n"); 1064 argv[3] = NULL; 1065 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, 1066 fd); 1067 if (child == -1) 1068 goto failed; 1069 1070 /* Retrieve his answer. */ 1071 fp = fdopen (fd[0], "r"); 1072 if (fp == NULL) 1073 { 1074 error (0, errno, _("fdopen() failed")); 1075 goto failed; 1076 } 1077 1078 line = NULL; linesize = 0; 1079 linelen = getline (&line, &linesize, fp); 1080 if (linelen == (size_t)(-1)) 1081 { 1082 error (0, 0, _("%s subprocess I/O error"), prog); 1083 fclose (fp); 1084 goto failed; 1085 } 1086 if (linelen > 0 && line[linelen - 1] == '\n') 1087 line[linelen - 1] = '\0'; 1088 1089 fclose (fp); 1090 1091 /* Remove zombie process from process list, and retrieve exit status. */ 1092 exitstatus = wait_subprocess (child, prog, false, false, true, false); 1093 if (exitstatus != 0) 1094 { 1095 error (0, 0, _("%s subprocess failed with exit code %d"), 1096 prog, exitstatus); 1097 goto failed; 1098 } 1099 1100 return line; 1101 1102failed: 1103 return "EMAIL@ADDRESS"; 1104} 1105 1106 1107/* Construct the value for the Last-Translator field. */ 1108static const char * 1109last_translator () 1110{ 1111 if (no_translator) 1112 return "Automatically generated"; 1113 else 1114 { 1115 const char *fullname = get_user_fullname (); 1116 const char *email = get_user_email (); 1117 1118 if (fullname != NULL) 1119 return xasprintf ("%s <%s>", fullname, email); 1120 else 1121 return xasprintf ("<%s>", email); 1122 } 1123} 1124 1125 1126/* Return the name of the language used by the language team, in English. */ 1127static const char * 1128language_team_englishname () 1129{ 1130 size_t i; 1131 1132 /* Search for a name depending on the catalogname. */ 1133 for (i = 0; i < language_variant_table_size; i++) 1134 if (strcmp (language_variant_table[i].code, catalogname) == 0) 1135 return language_variant_table[i].english; 1136 1137 /* Search for a name depending on the language only. */ 1138 return englishname_of_language (); 1139} 1140 1141 1142/* Return the language team's mailing list address or homepage URL. */ 1143static const char * 1144language_team_address () 1145{ 1146 const char *prog = relocate (PROJECTSDIR "/team-address"); 1147 char *argv[7]; 1148 pid_t child; 1149 int fd[1]; 1150 FILE *fp; 1151 char *line; 1152 size_t linesize; 1153 size_t linelen; 1154 int exitstatus; 1155 1156 /* Call the team-address shell script. */ 1157 argv[0] = "/bin/sh"; 1158 argv[1] = (char *) prog; 1159 argv[2] = (char *) relocate (PROJECTSDIR); 1160 argv[3] = (char *) relocate (LIBDIR "/gettext"); 1161 argv[4] = (char *) catalogname; 1162 argv[5] = (char *) language; 1163 argv[6] = NULL; 1164 child = create_pipe_in (prog, "/bin/sh", argv, DEV_NULL, false, true, false, 1165 fd); 1166 if (child == -1) 1167 goto failed; 1168 1169 /* Retrieve its result. */ 1170 fp = fdopen (fd[0], "r"); 1171 if (fp == NULL) 1172 { 1173 error (0, errno, _("fdopen() failed")); 1174 goto failed; 1175 } 1176 1177 line = NULL; linesize = 0; 1178 linelen = getline (&line, &linesize, fp); 1179 if (linelen == (size_t)(-1)) 1180 line = ""; 1181 else if (linelen > 0 && line[linelen - 1] == '\n') 1182 line[linelen - 1] = '\0'; 1183 1184 fclose (fp); 1185 1186 /* Remove zombie process from process list, and retrieve exit status. */ 1187 exitstatus = wait_subprocess (child, prog, false, false, true, false); 1188 if (exitstatus != 0) 1189 { 1190 error (0, 0, _("%s subprocess failed with exit code %d"), 1191 prog, exitstatus); 1192 goto failed; 1193 } 1194 1195 return line; 1196 1197failed: 1198 return ""; 1199} 1200 1201 1202/* Construct the value for the Language-Team field. */ 1203static const char * 1204language_team () 1205{ 1206 if (no_translator) 1207 return "none"; 1208 else 1209 { 1210 const char *englishname = language_team_englishname (); 1211 const char *address = language_team_address (); 1212 1213 if (address != NULL && address[0] != '\0') 1214 return xasprintf ("%s %s", englishname, address); 1215 else 1216 return englishname; 1217 } 1218} 1219 1220 1221/* Construct the value for the MIME-Version field. */ 1222static const char * 1223mime_version () 1224{ 1225 return "1.0"; 1226} 1227 1228 1229/* Construct the value for the Content-Type field. */ 1230static const char * 1231content_type (const char *header) 1232{ 1233 bool was_utf8; 1234 const char *old_field; 1235 1236 /* If the POT file contains charset=UTF-8, it means that the POT file 1237 contains non-ASCII characters, and we keep the UTF-8 encoding. 1238 Otherwise, when the POT file is plain ASCII, we use the locale's 1239 encoding. */ 1240 was_utf8 = false; 1241 old_field = get_field (header, "Content-Type"); 1242 if (old_field != NULL) 1243 { 1244 const char *charsetstr = c_strstr (old_field, "charset="); 1245 1246 if (charsetstr != NULL) 1247 { 1248 charsetstr += strlen ("charset="); 1249 was_utf8 = (c_strcasecmp (charsetstr, "UTF-8") == 0); 1250 } 1251 } 1252 return xasprintf ("text/plain; charset=%s", 1253 was_utf8 ? "UTF-8" : canonical_locale_charset ()); 1254} 1255 1256 1257/* Construct the value for the Content-Transfer-Encoding field. */ 1258static const char * 1259content_transfer_encoding () 1260{ 1261 return "8bit"; 1262} 1263 1264 1265/* Construct the value for the Plural-Forms field. */ 1266static const char * 1267plural_forms () 1268{ 1269 size_t i; 1270 1271 /* Search for a formula depending on the catalogname. */ 1272 for (i = 0; i < plural_table_size; i++) 1273 if (strcmp (plural_table[i].lang, catalogname) == 0) 1274 return plural_table[i].value; 1275 1276 /* Search for a formula depending on the language only. */ 1277 for (i = 0; i < plural_table_size; i++) 1278 if (strcmp (plural_table[i].lang, language) == 0) 1279 return plural_table[i].value; 1280 1281 return NULL; 1282} 1283 1284 1285static struct 1286{ 1287 const char *name; 1288 const char * (*getter0) (void); 1289 const char * (*getter1) (const char *header); 1290} 1291fields[] = 1292 { 1293 { "Project-Id-Version", NULL, project_id_version }, 1294 { "PO-Revision-Date", NULL, po_revision_date }, 1295 { "Last-Translator", last_translator, NULL }, 1296 { "Language-Team", language_team, NULL }, 1297 { "MIME-Version", mime_version, NULL }, 1298 { "Content-Type", NULL, content_type }, 1299 { "Content-Transfer-Encoding", content_transfer_encoding, NULL }, 1300 { "Plural-Forms", plural_forms, NULL } 1301 }; 1302 1303#define NFIELDS SIZEOF (fields) 1304#define FIELD_LAST_TRANSLATOR 2 1305 1306 1307/* Retrieve a freshly allocated copy of a field's value. */ 1308static char * 1309get_field (const char *header, const char *field) 1310{ 1311 size_t len = strlen (field); 1312 const char *line; 1313 1314 for (line = header;;) 1315 { 1316 if (strncmp (line, field, len) == 0 1317 && line[len] == ':' && line[len + 1] == ' ') 1318 { 1319 const char *value_start; 1320 const char *value_end; 1321 char *value; 1322 1323 value_start = line + len + 2; 1324 value_end = strchr (value_start, '\n'); 1325 if (value_end == NULL) 1326 value_end = value_start + strlen (value_start); 1327 1328 value = XNMALLOC (value_end - value_start + 1, char); 1329 memcpy (value, value_start, value_end - value_start); 1330 value[value_end - value_start] = '\0'; 1331 1332 return value; 1333 } 1334 1335 line = strchr (line, '\n'); 1336 if (line != NULL) 1337 line++; 1338 else 1339 break; 1340 } 1341 1342 return NULL; 1343} 1344 1345/* Add a field with value to a header, and return the new header. */ 1346static char * 1347put_field (const char *old_header, const char *field, const char *value) 1348{ 1349 size_t len = strlen (field); 1350 const char *line; 1351 char *new_header; 1352 char *p; 1353 1354 for (line = old_header;;) 1355 { 1356 if (strncmp (line, field, len) == 0 1357 && line[len] == ':' && line[len + 1] == ' ') 1358 { 1359 const char *value_start; 1360 const char *value_end; 1361 1362 value_start = line + len + 2; 1363 value_end = strchr (value_start, '\n'); 1364 if (value_end == NULL) 1365 value_end = value_start + strlen (value_start); 1366 1367 new_header = XNMALLOC (strlen (old_header) 1368 - (value_end - value_start) 1369 + strlen (value) 1370 + (*value_end != '\n' ? 1 : 0) 1371 + 1, 1372 char); 1373 p = new_header; 1374 memcpy (p, old_header, value_start - old_header); 1375 p += value_start - old_header; 1376 memcpy (p, value, strlen (value)); 1377 p += strlen (value); 1378 if (*value_end != '\n') 1379 *p++ = '\n'; 1380 strcpy (p, value_end); 1381 1382 return new_header; 1383 } 1384 1385 line = strchr (line, '\n'); 1386 if (line != NULL) 1387 line++; 1388 else 1389 break; 1390 } 1391 1392 new_header = XNMALLOC (strlen (old_header) + 1 1393 + len + 2 + strlen (value) + 1 1394 + 1, 1395 char); 1396 p = new_header; 1397 memcpy (p, old_header, strlen (old_header)); 1398 p += strlen (old_header); 1399 if (p > new_header && p[-1] != '\n') 1400 *p++ = '\n'; 1401 memcpy (p, field, len); 1402 p += len; 1403 *p++ = ':'; 1404 *p++ = ' '; 1405 memcpy (p, value, strlen (value)); 1406 p += strlen (value); 1407 *p++ = '\n'; 1408 *p = '\0'; 1409 1410 return new_header; 1411} 1412 1413 1414/* Return the title format string. */ 1415static const char * 1416get_title () 1417{ 1418 /* This is tricky. We want the translation in the given locale specified by 1419 the command line, not the current locale. But we want it in the encoding 1420 that we put into the header entry, not the encoding of that locale. 1421 We could avoid the use of OUTPUT_CHARSET by using a separate message 1422 catalog and bind_textdomain_codeset(), but that doesn't seem worth the 1423 trouble for one single message. */ 1424 const char *encoding; 1425 const char *tmp; 1426 char *old_LC_ALL; 1427 char *old_LANGUAGE; 1428 char *old_OUTPUT_CHARSET; 1429 const char *msgid; 1430 const char *english; 1431 const char *result; 1432 1433 encoding = canonical_locale_charset (); 1434 1435 /* First, the English title. */ 1436 english = xasprintf ("%s translations for %%s package", 1437 englishname_of_language ()); 1438 1439 /* Save LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */ 1440 1441 tmp = getenv ("LC_ALL"); 1442 old_LC_ALL = (tmp != NULL ? xstrdup (tmp) : NULL); 1443 1444 tmp = getenv ("LANGUAGE"); 1445 old_LANGUAGE = (tmp != NULL ? xstrdup (tmp) : NULL); 1446 1447 tmp = getenv ("OUTPUT_CHARSET"); 1448 old_OUTPUT_CHARSET = (tmp != NULL ? xstrdup (tmp) : NULL); 1449 1450 xsetenv ("LC_ALL", locale, 1); 1451 unsetenv ("LANGUAGE"); 1452 xsetenv ("OUTPUT_CHARSET", encoding, 1); 1453 1454#ifdef HAVE_SETLOCALE 1455 if (setlocale (LC_ALL, "") == NULL) 1456 /* Nonexistent locale. Use the English title. */ 1457 result = english; 1458 else 1459#endif 1460 { 1461 /* Fetch the translation. */ 1462 /* TRANSLATORS: "English" needs to be replaced by your language. 1463 For example in it.po write "Traduzioni italiani ...", 1464 *not* "Traduzioni inglesi ...". */ 1465 msgid = N_("English translations for %s package"); 1466 result = gettext (msgid); 1467 if (result != msgid && strcmp (result, msgid) != 0) 1468 /* Use the English and the foreign title. */ 1469 result = xasprintf ("%s\n%s", english, result); 1470 else 1471 /* No translation found. Use the English title. */ 1472 result = english; 1473 } 1474 1475 /* Restore LC_ALL, LANGUAGE, OUTPUT_CHARSET environment variables. */ 1476 1477 if (old_LC_ALL != NULL) 1478 xsetenv ("LC_ALL", old_LC_ALL, 1), free (old_LC_ALL); 1479 else 1480 unsetenv ("LC_ALL"); 1481 1482 if (old_LANGUAGE != NULL) 1483 xsetenv ("LANGUAGE", old_LANGUAGE, 1), free (old_LANGUAGE); 1484 else 1485 unsetenv ("LANGUAGE"); 1486 1487 if (old_OUTPUT_CHARSET != NULL) 1488 xsetenv ("OUTPUT_CHARSET", old_OUTPUT_CHARSET, 1), free (old_OUTPUT_CHARSET); 1489 else 1490 unsetenv ("OUTPUT_CHARSET"); 1491 1492#ifdef HAVE_SETLOCALE 1493 setlocale (LC_ALL, ""); 1494#endif 1495 1496 return result; 1497} 1498 1499 1500/* Perform a set of substitutions in a string and return the resulting 1501 string. When subst[j][0] found, it is replaced with subst[j][1]. 1502 subst[j][0] must not be the empty string. */ 1503static const char * 1504subst_string (const char *str, 1505 unsigned int nsubst, const char *(*subst)[2]) 1506{ 1507 if (nsubst > 0) 1508 { 1509 char *malloced = NULL; 1510 size_t *substlen; 1511 size_t i; 1512 unsigned int j; 1513 1514 substlen = (size_t *) xmalloca (nsubst * sizeof (size_t)); 1515 for (j = 0; j < nsubst; j++) 1516 { 1517 substlen[j] = strlen (subst[j][0]); 1518 if (substlen[j] == 0) 1519 abort (); 1520 } 1521 1522 for (i = 0;;) 1523 { 1524 if (str[i] == '\0') 1525 break; 1526 for (j = 0; j < nsubst; j++) 1527 if (*(str + i) == *subst[j][0] 1528 && strncmp (str + i, subst[j][0], substlen[j]) == 0) 1529 { 1530 size_t replacement_len = strlen (subst[j][1]); 1531 size_t new_len = strlen (str) - substlen[j] + replacement_len; 1532 char *new_str = XNMALLOC (new_len + 1, char); 1533 memcpy (new_str, str, i); 1534 memcpy (new_str + i, subst[j][1], replacement_len); 1535 strcpy (new_str + i + replacement_len, str + i + substlen[j]); 1536 if (malloced != NULL) 1537 free (malloced); 1538 str = new_str; 1539 malloced = new_str; 1540 i += replacement_len; 1541 break; 1542 } 1543 if (j == nsubst) 1544 i++; 1545 } 1546 1547 freea (substlen); 1548 } 1549 1550 return str; 1551} 1552 1553/* Perform a set of substitutions on each string of a string list. 1554 When subst[j][0] found, it is replaced with subst[j][1]. subst[j][0] 1555 must not be the empty string. */ 1556static void 1557subst_string_list (string_list_ty *slp, 1558 unsigned int nsubst, const char *(*subst)[2]) 1559{ 1560 size_t j; 1561 1562 for (j = 0; j < slp->nitems; j++) 1563 slp->item[j] = subst_string (slp->item[j], nsubst, subst); 1564} 1565 1566 1567/* Fill the templates in all fields of the header entry. */ 1568static msgdomain_list_ty * 1569fill_header (msgdomain_list_ty *mdlp) 1570{ 1571 /* Cache the strings filled in, for use when there are multiple domains 1572 and a header entry for each domain. */ 1573 const char *field_value[NFIELDS]; 1574 size_t k, j, i; 1575 1576 for (i = 0; i < NFIELDS; i++) 1577 field_value[i] = NULL; 1578 1579 for (k = 0; k < mdlp->nitems; k++) 1580 { 1581 message_list_ty *mlp = mdlp->item[k]->messages; 1582 1583 if (mlp->nitems > 0) 1584 { 1585 message_ty *header_mp = NULL; 1586 char *header; 1587 1588 /* Search the header entry. */ 1589 for (j = 0; j < mlp->nitems; j++) 1590 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) 1591 { 1592 header_mp = mlp->item[j]; 1593 break; 1594 } 1595 1596 /* If it wasn't found, provide one. */ 1597 if (header_mp == NULL) 1598 { 1599 static lex_pos_ty pos = { __FILE__, __LINE__ }; 1600 1601 header_mp = message_alloc (NULL, "", NULL, "", 1, &pos); 1602 message_list_prepend (mlp, header_mp); 1603 } 1604 1605 header = xstrdup (header_mp->msgstr); 1606 1607 /* Fill in the fields. */ 1608 for (i = 0; i < NFIELDS; i++) 1609 { 1610 if (field_value[i] == NULL) 1611 field_value[i] = 1612 (fields[i].getter1 != NULL 1613 ? fields[i].getter1 (header) 1614 : fields[i].getter0 ()); 1615 1616 if (field_value[i] != NULL) 1617 { 1618 char *old_header = header; 1619 header = put_field (header, fields[i].name, field_value[i]); 1620 free (old_header); 1621 } 1622 } 1623 1624 /* Replace the old translation in the header entry. */ 1625 header_mp->msgstr = header; 1626 header_mp->msgstr_len = strlen (header) + 1; 1627 1628 /* Update the comments in the header entry. */ 1629 if (header_mp->comment != NULL) 1630 { 1631 const char *subst[4][2]; 1632 const char *id; 1633 time_t now; 1634 1635 id = project_id (); 1636 subst[0][0] = "SOME DESCRIPTIVE TITLE"; 1637 subst[0][1] = xasprintf (get_title (), id, id); 1638 subst[1][0] = "PACKAGE"; 1639 subst[1][1] = id; 1640 subst[2][0] = "FIRST AUTHOR <EMAIL@ADDRESS>"; 1641 subst[2][1] = field_value[FIELD_LAST_TRANSLATOR]; 1642 subst[3][0] = "YEAR"; 1643 subst[3][1] = 1644 xasprintf ("%d", 1645 (time (&now), (localtime (&now))->tm_year + 1900)); 1646 subst_string_list (header_mp->comment, SIZEOF (subst), subst); 1647 } 1648 1649 /* Finally remove the fuzzy attribute. */ 1650 header_mp->is_fuzzy = false; 1651 } 1652 } 1653 1654 return mdlp; 1655} 1656 1657 1658/* Update the msgstr plural entries according to the nplurals count. */ 1659static msgdomain_list_ty * 1660update_msgstr_plurals (msgdomain_list_ty *mdlp) 1661{ 1662 size_t k; 1663 1664 for (k = 0; k < mdlp->nitems; k++) 1665 { 1666 message_list_ty *mlp = mdlp->item[k]->messages; 1667 message_ty *header_entry; 1668 unsigned long int nplurals; 1669 char *untranslated_plural_msgstr; 1670 size_t j; 1671 1672 header_entry = message_list_search (mlp, NULL, ""); 1673 nplurals = get_plural_count (header_entry ? header_entry->msgstr : NULL); 1674 untranslated_plural_msgstr = XNMALLOC (nplurals, char); 1675 memset (untranslated_plural_msgstr, '\0', nplurals); 1676 1677 for (j = 0; j < mlp->nitems; j++) 1678 { 1679 message_ty *mp = mlp->item[j]; 1680 bool is_untranslated; 1681 const char *p; 1682 const char *pend; 1683 1684 if (mp->msgid_plural != NULL) 1685 { 1686 /* Test if mp is untranslated. (It most likely is.) */ 1687 is_untranslated = true; 1688 for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++) 1689 if (*p != '\0') 1690 { 1691 is_untranslated = false; 1692 break; 1693 } 1694 if (is_untranslated) 1695 { 1696 /* Change mp->msgstr_len consecutive empty strings into 1697 nplurals consecutive empty strings. */ 1698 if (nplurals > mp->msgstr_len) 1699 mp->msgstr = untranslated_plural_msgstr; 1700 mp->msgstr_len = nplurals; 1701 } 1702 } 1703 } 1704 } 1705 return mdlp; 1706} 1707