1/* Converts Uniforum style .po files to binary .mo files 2 Copyright (C) 1995-1998, 2000-2007 Free Software Foundation, Inc. 3 Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995. 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#ifdef HAVE_CONFIG_H 19# include <config.h> 20#endif 21 22#include <ctype.h> 23#include <getopt.h> 24#include <limits.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <locale.h> 29 30#include "closeout.h" 31#include "dir-list.h" 32#include "error.h" 33#include "error-progname.h" 34#include "progname.h" 35#include "relocatable.h" 36#include "basename.h" 37#include "xerror.h" 38#include "xvasprintf.h" 39#include "xalloc.h" 40#include "msgfmt.h" 41#include "write-mo.h" 42#include "write-java.h" 43#include "write-csharp.h" 44#include "write-resources.h" 45#include "write-tcl.h" 46#include "write-qt.h" 47#include "propername.h" 48#include "message.h" 49#include "open-catalog.h" 50#include "read-catalog.h" 51#include "read-po.h" 52#include "read-properties.h" 53#include "read-stringtable.h" 54#include "po-charset.h" 55#include "msgl-check.h" 56#include "gettext.h" 57 58#define _(str) gettext (str) 59 60/* Contains exit status for case in which no premature exit occurs. */ 61static int exit_status; 62 63/* If true include even fuzzy translations in output file. */ 64static bool include_fuzzies = false; 65 66/* If true include even untranslated messages in output file. */ 67static bool include_untranslated = false; 68 69/* Specifies name of the output file. */ 70static const char *output_file_name; 71 72/* Java mode output file specification. */ 73static bool java_mode; 74static bool assume_java2; 75static const char *java_resource_name; 76static const char *java_locale_name; 77static const char *java_class_directory; 78 79/* C# mode output file specification. */ 80static bool csharp_mode; 81static const char *csharp_resource_name; 82static const char *csharp_locale_name; 83static const char *csharp_base_directory; 84 85/* C# resources mode output file specification. */ 86static bool csharp_resources_mode; 87 88/* Tcl mode output file specification. */ 89static bool tcl_mode; 90static const char *tcl_locale_name; 91static const char *tcl_base_directory; 92 93/* Qt mode output file specification. */ 94static bool qt_mode; 95 96/* We may have more than one input file. Domains with same names in 97 different files have to merged. So we need a list of tables for 98 each output file. */ 99struct msg_domain 100{ 101 /* List for mapping message IDs to message strings. */ 102 message_list_ty *mlp; 103 /* Name of domain these ID/String pairs are part of. */ 104 const char *domain_name; 105 /* Output file name. */ 106 const char *file_name; 107 /* Link to the next domain. */ 108 struct msg_domain *next; 109}; 110static struct msg_domain *domain_list; 111static struct msg_domain *current_domain; 112 113/* Be more verbose. Use only 'fprintf' and 'multiline_warning' but not 114 'error' or 'multiline_error' to emit verbosity messages, because 'error' 115 and 'multiline_error' during PO file parsing cause the program to exit 116 with EXIT_FAILURE. See function lex_end(). */ 117bool verbose = false; 118 119/* If true check strings according to format string rules for the 120 language. */ 121static bool check_format_strings = false; 122 123/* If true check the header entry is present and complete. */ 124static bool check_header = false; 125 126/* Check that domain directives can be satisfied. */ 127static bool check_domain = false; 128 129/* Check that msgfmt's behaviour is semantically compatible with 130 X/Open msgfmt or XView msgfmt. */ 131static bool check_compatibility = false; 132 133/* If true, consider that strings containing an '&' are menu items and 134 the '&' designates a keyboard accelerator, and verify that the translations 135 also have a keyboard accelerator. */ 136static bool check_accelerators = false; 137static char accelerator_char = '&'; 138 139/* Counters for statistics on translations for the processed files. */ 140static int msgs_translated; 141static int msgs_untranslated; 142static int msgs_fuzzy; 143 144/* If not zero print statistics about translation at the end. */ 145static int do_statistics; 146 147/* Long options. */ 148static const struct option long_options[] = 149{ 150 { "alignment", required_argument, NULL, 'a' }, 151 { "check", no_argument, NULL, 'c' }, 152 { "check-accelerators", optional_argument, NULL, CHAR_MAX + 1 }, 153 { "check-compatibility", no_argument, NULL, 'C' }, 154 { "check-domain", no_argument, NULL, CHAR_MAX + 2 }, 155 { "check-format", no_argument, NULL, CHAR_MAX + 3 }, 156 { "check-header", no_argument, NULL, CHAR_MAX + 4 }, 157 { "csharp", no_argument, NULL, CHAR_MAX + 10 }, 158 { "csharp-resources", no_argument, NULL, CHAR_MAX + 11 }, 159 { "directory", required_argument, NULL, 'D' }, 160 { "endianness", required_argument, NULL, CHAR_MAX + 13 }, 161 { "help", no_argument, NULL, 'h' }, 162 { "java", no_argument, NULL, 'j' }, 163 { "java2", no_argument, NULL, CHAR_MAX + 5 }, 164 { "locale", required_argument, NULL, 'l' }, 165 { "no-hash", no_argument, NULL, CHAR_MAX + 6 }, 166 { "output-file", required_argument, NULL, 'o' }, 167 { "properties-input", no_argument, NULL, 'P' }, 168 { "qt", no_argument, NULL, CHAR_MAX + 9 }, 169 { "resource", required_argument, NULL, 'r' }, 170 { "statistics", no_argument, &do_statistics, 1 }, 171 { "strict", no_argument, NULL, 'S' }, 172 { "stringtable-input", no_argument, NULL, CHAR_MAX + 8 }, 173 { "tcl", no_argument, NULL, CHAR_MAX + 7 }, 174 { "use-fuzzy", no_argument, NULL, 'f' }, 175 { "use-untranslated", no_argument, NULL, CHAR_MAX + 12 }, 176 { "verbose", no_argument, NULL, 'v' }, 177 { "version", no_argument, NULL, 'V' }, 178 { NULL, 0, NULL, 0 } 179}; 180 181 182/* Forward declaration of local functions. */ 183static void usage (int status) 184#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) 185 __attribute__ ((noreturn)) 186#endif 187; 188static const char *add_mo_suffix (const char *); 189static struct msg_domain *new_domain (const char *name, const char *file_name); 190static bool is_nonobsolete (const message_ty *mp); 191static void read_catalog_file_msgfmt (char *filename, 192 catalog_input_format_ty input_syntax); 193 194 195int 196main (int argc, char *argv[]) 197{ 198 int opt; 199 bool do_help = false; 200 bool do_version = false; 201 bool strict_uniforum = false; 202 catalog_input_format_ty input_syntax = &input_format_po; 203 const char *canon_encoding; 204 struct msg_domain *domain; 205 206 /* Set default value for global variables. */ 207 alignment = DEFAULT_OUTPUT_ALIGNMENT; 208 209 /* Set program name for messages. */ 210 set_program_name (argv[0]); 211 error_print_progname = maybe_print_progname; 212 error_one_per_line = 1; 213 exit_status = EXIT_SUCCESS; 214 215#ifdef HAVE_SETLOCALE 216 /* Set locale via LC_ALL. */ 217 setlocale (LC_ALL, ""); 218#endif 219 220 /* Set the text message domain. */ 221 bindtextdomain (PACKAGE, relocate (LOCALEDIR)); 222 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); 223 textdomain (PACKAGE); 224 225 /* Ensure that write errors on stdout are detected. */ 226 atexit (close_stdout); 227 228 while ((opt = getopt_long (argc, argv, "a:cCd:D:fhjl:o:Pr:vV", long_options, 229 NULL)) 230 != EOF) 231 switch (opt) 232 { 233 case '\0': /* Long option. */ 234 break; 235 case 'a': 236 { 237 char *endp; 238 size_t new_align = strtoul (optarg, &endp, 0); 239 240 if (endp != optarg) 241 alignment = new_align; 242 } 243 break; 244 case 'c': 245 check_domain = true; 246 check_format_strings = true; 247 check_header = true; 248 break; 249 case 'C': 250 check_compatibility = true; 251 break; 252 case 'd': 253 java_class_directory = optarg; 254 csharp_base_directory = optarg; 255 tcl_base_directory = optarg; 256 break; 257 case 'D': 258 dir_list_append (optarg); 259 break; 260 case 'f': 261 include_fuzzies = true; 262 break; 263 case 'h': 264 do_help = true; 265 break; 266 case 'j': 267 java_mode = true; 268 break; 269 case 'l': 270 java_locale_name = optarg; 271 csharp_locale_name = optarg; 272 tcl_locale_name = optarg; 273 break; 274 case 'o': 275 output_file_name = optarg; 276 break; 277 case 'P': 278 input_syntax = &input_format_properties; 279 break; 280 case 'r': 281 java_resource_name = optarg; 282 csharp_resource_name = optarg; 283 break; 284 case 'S': 285 strict_uniforum = true; 286 break; 287 case 'v': 288 verbose = true; 289 break; 290 case 'V': 291 do_version = true; 292 break; 293 case CHAR_MAX + 1: /* --check-accelerators */ 294 check_accelerators = true; 295 if (optarg != NULL) 296 { 297 if (optarg[0] != '\0' && ispunct ((unsigned char) optarg[0]) 298 && optarg[1] == '\0') 299 accelerator_char = optarg[0]; 300 else 301 error (EXIT_FAILURE, 0, 302 _("the argument to %s should be a single punctuation character"), 303 "--check-accelerators"); 304 } 305 break; 306 case CHAR_MAX + 2: /* --check-domain */ 307 check_domain = true; 308 break; 309 case CHAR_MAX + 3: /* --check-format */ 310 check_format_strings = true; 311 break; 312 case CHAR_MAX + 4: /* --check-header */ 313 check_header = true; 314 break; 315 case CHAR_MAX + 5: /* --java2 */ 316 java_mode = true; 317 assume_java2 = true; 318 break; 319 case CHAR_MAX + 6: /* --no-hash */ 320 no_hash_table = true; 321 break; 322 case CHAR_MAX + 7: /* --tcl */ 323 tcl_mode = true; 324 break; 325 case CHAR_MAX + 8: /* --stringtable-input */ 326 input_syntax = &input_format_stringtable; 327 break; 328 case CHAR_MAX + 9: /* --qt */ 329 qt_mode = true; 330 break; 331 case CHAR_MAX + 10: /* --csharp */ 332 csharp_mode = true; 333 break; 334 case CHAR_MAX + 11: /* --csharp-resources */ 335 csharp_resources_mode = true; 336 break; 337 case CHAR_MAX + 12: /* --use-untranslated (undocumented) */ 338 include_untranslated = true; 339 break; 340 case CHAR_MAX + 13: /* --endianness={big|little} */ 341 { 342 int endianness; 343 344 if (strcmp (optarg, "big") == 0) 345 endianness = 1; 346 else if (strcmp (optarg, "little") == 0) 347 endianness = 0; 348 else 349 error (EXIT_FAILURE, 0, _("invalid endianness: %s"), optarg); 350 351 byteswap = endianness ^ ENDIANNESS; 352 } 353 break; 354 default: 355 usage (EXIT_FAILURE); 356 break; 357 } 358 359 /* Version information is requested. */ 360 if (do_version) 361 { 362 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); 363 /* xgettext: no-wrap */ 364 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ 365License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\ 366This is free software: you are free to change and redistribute it.\n\ 367There is NO WARRANTY, to the extent permitted by law.\n\ 368"), 369 "1995-1998, 2000-2007"); 370 printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper")); 371 exit (EXIT_SUCCESS); 372 } 373 374 /* Help is requested. */ 375 if (do_help) 376 usage (EXIT_SUCCESS); 377 378 /* Test whether we have a .po file name as argument. */ 379 if (optind >= argc) 380 { 381 error (EXIT_SUCCESS, 0, _("no input file given")); 382 usage (EXIT_FAILURE); 383 } 384 385 /* Check for contradicting options. */ 386 { 387 unsigned int modes = 388 (java_mode ? 1 : 0) 389 | (csharp_mode ? 2 : 0) 390 | (csharp_resources_mode ? 4 : 0) 391 | (tcl_mode ? 8 : 0) 392 | (qt_mode ? 16 : 0); 393 static const char *mode_options[] = 394 { "--java", "--csharp", "--csharp-resources", "--tcl", "--qt" }; 395 /* More than one bit set? */ 396 if (modes & (modes - 1)) 397 { 398 const char *first_option; 399 const char *second_option; 400 unsigned int i; 401 for (i = 0; ; i++) 402 if (modes & (1 << i)) 403 break; 404 first_option = mode_options[i]; 405 for (i = i + 1; ; i++) 406 if (modes & (1 << i)) 407 break; 408 second_option = mode_options[i]; 409 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), 410 first_option, second_option); 411 } 412 } 413 if (java_mode) 414 { 415 if (output_file_name != NULL) 416 { 417 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), 418 "--java", "--output-file"); 419 } 420 if (java_class_directory == NULL) 421 { 422 error (EXIT_SUCCESS, 0, 423 _("%s requires a \"-d directory\" specification"), 424 "--java"); 425 usage (EXIT_FAILURE); 426 } 427 } 428 else if (csharp_mode) 429 { 430 if (output_file_name != NULL) 431 { 432 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), 433 "--csharp", "--output-file"); 434 } 435 if (csharp_locale_name == NULL) 436 { 437 error (EXIT_SUCCESS, 0, 438 _("%s requires a \"-l locale\" specification"), 439 "--csharp"); 440 usage (EXIT_FAILURE); 441 } 442 if (csharp_base_directory == NULL) 443 { 444 error (EXIT_SUCCESS, 0, 445 _("%s requires a \"-d directory\" specification"), 446 "--csharp"); 447 usage (EXIT_FAILURE); 448 } 449 } 450 else if (tcl_mode) 451 { 452 if (output_file_name != NULL) 453 { 454 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), 455 "--tcl", "--output-file"); 456 } 457 if (tcl_locale_name == NULL) 458 { 459 error (EXIT_SUCCESS, 0, 460 _("%s requires a \"-l locale\" specification"), 461 "--tcl"); 462 usage (EXIT_FAILURE); 463 } 464 if (tcl_base_directory == NULL) 465 { 466 error (EXIT_SUCCESS, 0, 467 _("%s requires a \"-d directory\" specification"), 468 "--tcl"); 469 usage (EXIT_FAILURE); 470 } 471 } 472 else 473 { 474 if (java_resource_name != NULL) 475 { 476 error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"), 477 "--resource", "--java", "--csharp"); 478 usage (EXIT_FAILURE); 479 } 480 if (java_locale_name != NULL) 481 { 482 error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"), 483 "--locale", "--java", "--csharp", "--tcl"); 484 usage (EXIT_FAILURE); 485 } 486 if (java_class_directory != NULL) 487 { 488 error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"), 489 "-d", "--java", "--csharp", "--tcl"); 490 usage (EXIT_FAILURE); 491 } 492 } 493 494 /* The -o option determines the name of the domain and therefore 495 the output file. */ 496 if (output_file_name != NULL) 497 current_domain = 498 new_domain (output_file_name, 499 strict_uniforum && !csharp_resources_mode && !qt_mode 500 ? add_mo_suffix (output_file_name) 501 : output_file_name); 502 503 /* Process all given .po files. */ 504 while (argc > optind) 505 { 506 /* Remember that we currently have not specified any domain. This 507 is of course not true when we saw the -o option. */ 508 if (output_file_name == NULL) 509 current_domain = NULL; 510 511 /* And process the input file. */ 512 read_catalog_file_msgfmt (argv[optind], input_syntax); 513 514 ++optind; 515 } 516 517 /* We know a priori that some input_syntax->parse() functions convert 518 strings to UTF-8. */ 519 canon_encoding = (input_syntax->produces_utf8 ? po_charset_utf8 : NULL); 520 521 /* Remove obsolete messages. They were only needed for duplicate 522 checking. */ 523 for (domain = domain_list; domain != NULL; domain = domain->next) 524 message_list_remove_if_not (domain->mlp, is_nonobsolete); 525 526 /* Perform all kinds of checks: plural expressions, format strings, ... */ 527 { 528 int nerrors = 0; 529 530 for (domain = domain_list; domain != NULL; domain = domain->next) 531 nerrors += 532 check_message_list (domain->mlp, 533 1, check_format_strings, check_header, 534 check_compatibility, 535 check_accelerators, accelerator_char); 536 537 /* Exit with status 1 on any error. */ 538 if (nerrors > 0) 539 { 540 error (0, 0, 541 ngettext ("found %d fatal error", "found %d fatal errors", 542 nerrors), 543 nerrors); 544 exit_status = EXIT_FAILURE; 545 } 546 } 547 548 /* Now write out all domains. */ 549 for (domain = domain_list; domain != NULL; domain = domain->next) 550 { 551 if (java_mode) 552 { 553 if (msgdomain_write_java (domain->mlp, canon_encoding, 554 java_resource_name, java_locale_name, 555 java_class_directory, assume_java2)) 556 exit_status = EXIT_FAILURE; 557 } 558 else if (csharp_mode) 559 { 560 if (msgdomain_write_csharp (domain->mlp, canon_encoding, 561 csharp_resource_name, csharp_locale_name, 562 csharp_base_directory)) 563 exit_status = EXIT_FAILURE; 564 } 565 else if (csharp_resources_mode) 566 { 567 if (msgdomain_write_csharp_resources (domain->mlp, canon_encoding, 568 domain->domain_name, 569 domain->file_name)) 570 exit_status = EXIT_FAILURE; 571 } 572 else if (tcl_mode) 573 { 574 if (msgdomain_write_tcl (domain->mlp, canon_encoding, 575 tcl_locale_name, tcl_base_directory)) 576 exit_status = EXIT_FAILURE; 577 } 578 else if (qt_mode) 579 { 580 if (msgdomain_write_qt (domain->mlp, canon_encoding, 581 domain->domain_name, domain->file_name)) 582 exit_status = EXIT_FAILURE; 583 } 584 else 585 { 586 if (msgdomain_write_mo (domain->mlp, domain->domain_name, 587 domain->file_name)) 588 exit_status = EXIT_FAILURE; 589 } 590 591 /* List is not used anymore. */ 592 message_list_free (domain->mlp, 0); 593 } 594 595 /* Print statistics if requested. */ 596 if (verbose || do_statistics) 597 { 598 fprintf (stderr, 599 ngettext ("%d translated message", "%d translated messages", 600 msgs_translated), 601 msgs_translated); 602 if (msgs_fuzzy > 0) 603 fprintf (stderr, 604 ngettext (", %d fuzzy translation", ", %d fuzzy translations", 605 msgs_fuzzy), 606 msgs_fuzzy); 607 if (msgs_untranslated > 0) 608 fprintf (stderr, 609 ngettext (", %d untranslated message", 610 ", %d untranslated messages", 611 msgs_untranslated), 612 msgs_untranslated); 613 fputs (".\n", stderr); 614 } 615 616 exit (exit_status); 617} 618 619 620/* Display usage information and exit. */ 621static void 622usage (int status) 623{ 624 if (status != EXIT_SUCCESS) 625 fprintf (stderr, _("Try `%s --help' for more information.\n"), 626 program_name); 627 else 628 { 629 printf (_("\ 630Usage: %s [OPTION] filename.po ...\n\ 631"), program_name); 632 printf ("\n"); 633 printf (_("\ 634Generate binary message catalog from textual translation description.\n\ 635")); 636 printf ("\n"); 637 /* xgettext: no-wrap */ 638 printf (_("\ 639Mandatory arguments to long options are mandatory for short options too.\n\ 640Similarly for optional arguments.\n\ 641")); 642 printf ("\n"); 643 printf (_("\ 644Input file location:\n")); 645 printf (_("\ 646 filename.po ... input files\n")); 647 printf (_("\ 648 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); 649 printf (_("\ 650If input file is -, standard input is read.\n")); 651 printf ("\n"); 652 printf (_("\ 653Operation mode:\n")); 654 printf (_("\ 655 -j, --java Java mode: generate a Java ResourceBundle class\n")); 656 printf (_("\ 657 --java2 like --java, and assume Java2 (JDK 1.2 or higher)\n")); 658 printf (_("\ 659 --csharp C# mode: generate a .NET .dll file\n")); 660 printf (_("\ 661 --csharp-resources C# resources mode: generate a .NET .resources file\n")); 662 printf (_("\ 663 --tcl Tcl mode: generate a tcl/msgcat .msg file\n")); 664 printf (_("\ 665 --qt Qt mode: generate a Qt .qm file\n")); 666 printf ("\n"); 667 printf (_("\ 668Output file location:\n")); 669 printf (_("\ 670 -o, --output-file=FILE write output to specified file\n")); 671 printf (_("\ 672 --strict enable strict Uniforum mode\n")); 673 printf (_("\ 674If output file is -, output is written to standard output.\n")); 675 printf ("\n"); 676 printf (_("\ 677Output file location in Java mode:\n")); 678 printf (_("\ 679 -r, --resource=RESOURCE resource name\n")); 680 printf (_("\ 681 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); 682 printf (_("\ 683 -d DIRECTORY base directory of classes directory hierarchy\n")); 684 printf (_("\ 685The class name is determined by appending the locale name to the resource name,\n\ 686separated with an underscore. The -d option is mandatory. The class is\n\ 687written under the specified directory.\n\ 688")); 689 printf ("\n"); 690 printf (_("\ 691Output file location in C# mode:\n")); 692 printf (_("\ 693 -r, --resource=RESOURCE resource name\n")); 694 printf (_("\ 695 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); 696 printf (_("\ 697 -d DIRECTORY base directory for locale dependent .dll files\n")); 698 printf (_("\ 699The -l and -d options are mandatory. The .dll file is written in a\n\ 700subdirectory of the specified directory whose name depends on the locale.\n")); 701 printf ("\n"); 702 printf (_("\ 703Output file location in Tcl mode:\n")); 704 printf (_("\ 705 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n")); 706 printf (_("\ 707 -d DIRECTORY base directory of .msg message catalogs\n")); 708 printf (_("\ 709The -l and -d options are mandatory. The .msg file is written in the\n\ 710specified directory.\n")); 711 printf ("\n"); 712 printf (_("\ 713Input file syntax:\n")); 714 printf (_("\ 715 -P, --properties-input input files are in Java .properties syntax\n")); 716 printf (_("\ 717 --stringtable-input input files are in NeXTstep/GNUstep .strings\n\ 718 syntax\n")); 719 printf ("\n"); 720 printf (_("\ 721Input file interpretation:\n")); 722 printf (_("\ 723 -c, --check perform all the checks implied by\n\ 724 --check-format, --check-header, --check-domain\n")); 725 printf (_("\ 726 --check-format check language dependent format strings\n")); 727 printf (_("\ 728 --check-header verify presence and contents of the header entry\n")); 729 printf (_("\ 730 --check-domain check for conflicts between domain directives\n\ 731 and the --output-file option\n")); 732 printf (_("\ 733 -C, --check-compatibility check that GNU msgfmt behaves like X/Open msgfmt\n")); 734 printf (_("\ 735 --check-accelerators[=CHAR] check presence of keyboard accelerators for\n\ 736 menu items\n")); 737 printf (_("\ 738 -f, --use-fuzzy use fuzzy entries in output\n")); 739 printf ("\n"); 740 printf (_("\ 741Output details:\n")); 742 printf (_("\ 743 -a, --alignment=NUMBER align strings to NUMBER bytes (default: %d)\n"), DEFAULT_OUTPUT_ALIGNMENT); 744 printf (_("\ 745 --no-hash binary file will not include the hash table\n")); 746 printf ("\n"); 747 printf (_("\ 748Informative output:\n")); 749 printf (_("\ 750 -h, --help display this help and exit\n")); 751 printf (_("\ 752 -V, --version output version information and exit\n")); 753 printf (_("\ 754 --statistics print statistics about translations\n")); 755 printf (_("\ 756 -v, --verbose increase verbosity level\n")); 757 printf ("\n"); 758 /* TRANSLATORS: The placeholder indicates the bug-reporting address 759 for this package. Please add _another line_ saying 760 "Report translation bugs to <...>\n" with the address for translation 761 bugs (typically your translation team's web or email address). */ 762 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout); 763 } 764 765 exit (status); 766} 767 768 769static const char * 770add_mo_suffix (const char *fname) 771{ 772 size_t len; 773 char *result; 774 775 len = strlen (fname); 776 if (len > 3 && memcmp (fname + len - 3, ".mo", 3) == 0) 777 return fname; 778 if (len > 4 && memcmp (fname + len - 4, ".gmo", 4) == 0) 779 return fname; 780 result = XNMALLOC (len + 4, char); 781 stpcpy (stpcpy (result, fname), ".mo"); 782 return result; 783} 784 785 786static struct msg_domain * 787new_domain (const char *name, const char *file_name) 788{ 789 struct msg_domain **p_dom = &domain_list; 790 791 while (*p_dom != NULL && strcmp (name, (*p_dom)->domain_name) != 0) 792 p_dom = &(*p_dom)->next; 793 794 if (*p_dom == NULL) 795 { 796 struct msg_domain *domain; 797 798 domain = XMALLOC (struct msg_domain); 799 domain->mlp = message_list_alloc (true); 800 domain->domain_name = name; 801 domain->file_name = file_name; 802 domain->next = NULL; 803 *p_dom = domain; 804 } 805 806 return *p_dom; 807} 808 809 810static bool 811is_nonobsolete (const message_ty *mp) 812{ 813 return !mp->obsolete; 814} 815 816 817/* The rest of the file defines a subclass msgfmt_catalog_reader_ty of 818 default_catalog_reader_ty. Its particularities are: 819 - The header entry check is performed on-the-fly. 820 - Comments are not stored, they are discarded right away. 821 (This is achieved by setting handle_comments = false and 822 handle_filepos_comments = false.) 823 - The multi-domain handling is adapted to our domain_list. 824 */ 825 826 827/* This structure defines a derived class of the default_catalog_reader_ty 828 class. (See read-catalog-abstract.h for an explanation.) */ 829typedef struct msgfmt_catalog_reader_ty msgfmt_catalog_reader_ty; 830struct msgfmt_catalog_reader_ty 831{ 832 /* inherited instance variables, etc */ 833 DEFAULT_CATALOG_READER_TY 834 835 bool has_header_entry; 836 bool has_nonfuzzy_header_entry; 837}; 838 839 840/* Prepare for first message. */ 841static void 842msgfmt_constructor (abstract_catalog_reader_ty *that) 843{ 844 msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that; 845 846 /* Invoke superclass constructor. */ 847 default_constructor (that); 848 849 this->has_header_entry = false; 850 this->has_nonfuzzy_header_entry = false; 851} 852 853 854/* Some checks after whole file is read. */ 855static void 856msgfmt_parse_debrief (abstract_catalog_reader_ty *that) 857{ 858 msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that; 859 860 /* Invoke superclass method. */ 861 default_parse_debrief (that); 862 863 /* Test whether header entry was found. */ 864 if (check_header) 865 { 866 if (!this->has_header_entry) 867 { 868 multiline_error (xasprintf ("%s: ", gram_pos.file_name), 869 xasprintf (_("\ 870warning: PO file header missing or invalid\n"))); 871 multiline_error (NULL, 872 xasprintf (_("\ 873warning: charset conversion will not work\n"))); 874 } 875 else if (!this->has_nonfuzzy_header_entry) 876 { 877 /* Has only a fuzzy header entry. Since the versions 0.10.xx 878 ignore a fuzzy header entry and even give an error on it, we 879 give a warning, to increase operability with these older 880 msgfmt versions. This warning can go away in January 2003. */ 881 multiline_warning (xasprintf ("%s: ", gram_pos.file_name), 882 xasprintf (_("warning: PO file header fuzzy\n"))); 883 multiline_warning (NULL, 884 xasprintf (_("\ 885warning: older versions of msgfmt will give an error on this\n"))); 886 } 887 } 888} 889 890 891/* Set 'domain' directive when seen in .po file. */ 892static void 893msgfmt_set_domain (default_catalog_reader_ty *this, char *name) 894{ 895 /* If no output file was given, we change it with each `domain' 896 directive. */ 897 if (!java_mode && !csharp_mode && !csharp_resources_mode && !tcl_mode 898 && !qt_mode && output_file_name == NULL) 899 { 900 size_t correct; 901 902 correct = strcspn (name, INVALID_PATH_CHAR); 903 if (name[correct] != '\0') 904 { 905 exit_status = EXIT_FAILURE; 906 if (correct == 0) 907 { 908 error (0, 0, _("\ 909domain name \"%s\" not suitable as file name"), name); 910 return; 911 } 912 else 913 error (0, 0, _("\ 914domain name \"%s\" not suitable as file name: will use prefix"), name); 915 name[correct] = '\0'; 916 } 917 918 /* Set new domain. */ 919 current_domain = new_domain (name, add_mo_suffix (name)); 920 this->domain = current_domain->domain_name; 921 this->mlp = current_domain->mlp; 922 } 923 else 924 { 925 if (check_domain) 926 po_gram_error_at_line (&gram_pos, 927 _("`domain %s' directive ignored"), name); 928 929 /* NAME was allocated in po-gram-gen.y but is not used anywhere. */ 930 free (name); 931 } 932} 933 934 935static void 936msgfmt_add_message (default_catalog_reader_ty *this, 937 char *msgctxt, 938 char *msgid, 939 lex_pos_ty *msgid_pos, 940 char *msgid_plural, 941 char *msgstr, size_t msgstr_len, 942 lex_pos_ty *msgstr_pos, 943 char *prev_msgctxt, 944 char *prev_msgid, 945 char *prev_msgid_plural, 946 bool force_fuzzy, bool obsolete) 947{ 948 /* Check whether already a domain is specified. If not, use default 949 domain. */ 950 if (current_domain == NULL) 951 { 952 current_domain = new_domain (MESSAGE_DOMAIN_DEFAULT, 953 add_mo_suffix (MESSAGE_DOMAIN_DEFAULT)); 954 /* Keep current_domain and this->domain synchronized. */ 955 this->domain = current_domain->domain_name; 956 this->mlp = current_domain->mlp; 957 } 958 959 /* Invoke superclass method. */ 960 default_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural, 961 msgstr, msgstr_len, msgstr_pos, 962 prev_msgctxt, prev_msgid, prev_msgid_plural, 963 force_fuzzy, obsolete); 964} 965 966 967static void 968msgfmt_frob_new_message (default_catalog_reader_ty *that, message_ty *mp, 969 const lex_pos_ty *msgid_pos, 970 const lex_pos_ty *msgstr_pos) 971{ 972 msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that; 973 974 if (!mp->obsolete) 975 { 976 /* Don't emit untranslated entries. 977 Also don't emit fuzzy entries, unless --use-fuzzy was specified. 978 But ignore fuzziness of the header entry. */ 979 if ((!include_untranslated && mp->msgstr[0] == '\0') 980 || (!include_fuzzies && mp->is_fuzzy && !is_header (mp))) 981 { 982 if (check_compatibility) 983 { 984 error_with_progname = false; 985 error_at_line (0, 0, mp->pos.file_name, mp->pos.line_number, 986 (mp->msgstr[0] == '\0' 987 ? _("empty `msgstr' entry ignored") 988 : _("fuzzy `msgstr' entry ignored"))); 989 error_with_progname = true; 990 } 991 992 /* Increment counter for fuzzy/untranslated messages. */ 993 if (mp->msgstr[0] == '\0') 994 ++msgs_untranslated; 995 else 996 ++msgs_fuzzy; 997 998 mp->obsolete = true; 999 } 1000 else 1001 { 1002 /* Test for header entry. */ 1003 if (is_header (mp)) 1004 { 1005 this->has_header_entry = true; 1006 if (!mp->is_fuzzy) 1007 this->has_nonfuzzy_header_entry = true; 1008 } 1009 else 1010 /* We don't count the header entry in the statistic so place 1011 the counter incrementation here. */ 1012 if (mp->is_fuzzy) 1013 ++msgs_fuzzy; 1014 else 1015 ++msgs_translated; 1016 } 1017 } 1018} 1019 1020 1021/* Test for `#, fuzzy' comments and warn. */ 1022static void 1023msgfmt_comment_special (abstract_catalog_reader_ty *that, const char *s) 1024{ 1025 msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that; 1026 1027 /* Invoke superclass method. */ 1028 default_comment_special (that, s); 1029 1030 if (this->is_fuzzy) 1031 { 1032 static bool warned = false; 1033 1034 if (!include_fuzzies && check_compatibility && !warned) 1035 { 1036 warned = true; 1037 error (0, 0, _("\ 1038%s: warning: source file contains fuzzy translation"), 1039 gram_pos.file_name); 1040 } 1041 } 1042} 1043 1044 1045/* So that the one parser can be used for multiple programs, and also 1046 use good data hiding and encapsulation practices, an object 1047 oriented approach has been taken. An object instance is allocated, 1048 and all actions resulting from the parse will be through 1049 invocations of method functions of that object. */ 1050 1051static default_catalog_reader_class_ty msgfmt_methods = 1052{ 1053 { 1054 sizeof (msgfmt_catalog_reader_ty), 1055 msgfmt_constructor, 1056 default_destructor, 1057 default_parse_brief, 1058 msgfmt_parse_debrief, 1059 default_directive_domain, 1060 default_directive_message, 1061 default_comment, 1062 default_comment_dot, 1063 default_comment_filepos, 1064 msgfmt_comment_special 1065 }, 1066 msgfmt_set_domain, /* set_domain */ 1067 msgfmt_add_message, /* add_message */ 1068 msgfmt_frob_new_message /* frob_new_message */ 1069}; 1070 1071 1072/* Read .po file FILENAME and store translation pairs. */ 1073static void 1074read_catalog_file_msgfmt (char *filename, catalog_input_format_ty input_syntax) 1075{ 1076 char *real_filename; 1077 FILE *fp = open_catalog_file (filename, &real_filename, true); 1078 default_catalog_reader_ty *pop; 1079 1080 pop = default_catalog_reader_alloc (&msgfmt_methods); 1081 pop->handle_comments = false; 1082 pop->handle_filepos_comments = false; 1083 pop->allow_domain_directives = true; 1084 pop->allow_duplicates = false; 1085 pop->allow_duplicates_if_same_msgstr = false; 1086 pop->mdlp = NULL; 1087 pop->mlp = NULL; 1088 if (current_domain != NULL) 1089 { 1090 /* Keep current_domain and this->domain synchronized. */ 1091 pop->domain = current_domain->domain_name; 1092 pop->mlp = current_domain->mlp; 1093 } 1094 po_lex_pass_obsolete_entries (true); 1095 catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename, 1096 filename, input_syntax); 1097 catalog_reader_free ((abstract_catalog_reader_ty *) pop); 1098 1099 if (fp != stdin) 1100 fclose (fp); 1101} 1102