1 /* GNU gettext - internationalization aids 2 Copyright (C) 1995-1998, 2000-2006 Free Software Foundation, Inc. 3 This file was written by Peter Miller <millerp@canb.auug.org.au> 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ 18 19 #ifdef HAVE_CONFIG_H 20 # include <config.h> 21 #endif 22 23 #include <getopt.h> 24 #include <limits.h> 25 #include <stdbool.h> 26 #include <stdio.h> 27 #include <stdlib.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 "message.h" 38 #include "exit.h" 39 #include "read-catalog.h" 40 #include "read-po.h" 41 #include "read-properties.h" 42 #include "read-stringtable.h" 43 #include "msgl-iconv.h" 44 #include "c-strstr.h" 45 #include "c-strcase.h" 46 #include "propername.h" 47 #include "gettext.h" 48 49 #define _(str) gettext (str) 50 51 52 /* Apply the .pot file to each of the domains in the PO file. */ 53 static bool multi_domain_mode = false; 54 55 /* Whether to consider fuzzy messages as translations. */ 56 static bool include_fuzzies = false; 57 58 /* Whether to consider untranslated messages as translations. */ 59 static bool include_untranslated = false; 60 61 /* Long options. */ 62 static const struct option long_options[] = 63 { 64 { "directory", required_argument, NULL, 'D' }, 65 { "help", no_argument, NULL, 'h' }, 66 { "multi-domain", no_argument, NULL, 'm' }, 67 { "properties-input", no_argument, NULL, 'P' }, 68 { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 }, 69 { "use-fuzzy", no_argument, NULL, CHAR_MAX + 2 }, 70 { "use-untranslated", no_argument, NULL, CHAR_MAX + 3 }, 71 { "version", no_argument, NULL, 'V' }, 72 { NULL, 0, NULL, 0 } 73 }; 74 75 76 /* Forward declaration of local functions. */ 77 static void usage (int status) 78 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) 79 __attribute__ ((noreturn)) 80 #endif 81 ; 82 static void compare (const char *fn1, const char *fn2, 83 catalog_input_format_ty input_syntax); 84 85 86 int 87 main (int argc, char *argv[]) 88 { 89 int optchar; 90 bool do_help; 91 bool do_version; 92 catalog_input_format_ty input_syntax = &input_format_po; 93 94 /* Set program name for messages. */ 95 set_program_name (argv[0]); 96 error_print_progname = maybe_print_progname; 97 gram_max_allowed_errors = UINT_MAX; 98 99 #ifdef HAVE_SETLOCALE 100 /* Set locale via LC_ALL. */ 101 setlocale (LC_ALL, ""); 102 #endif 103 104 /* Set the text message domain. */ 105 bindtextdomain (PACKAGE, relocate (LOCALEDIR)); 106 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); 107 textdomain (PACKAGE); 108 109 /* Ensure that write errors on stdout are detected. */ 110 atexit (close_stdout); 111 112 do_help = false; 113 do_version = false; 114 while ((optchar = getopt_long (argc, argv, "D:hmPV", long_options, NULL)) 115 != EOF) 116 switch (optchar) 117 { 118 case '\0': /* long option */ 119 break; 120 121 case 'D': 122 dir_list_append (optarg); 123 break; 124 125 case 'h': 126 do_help = true; 127 break; 128 129 case 'm': 130 multi_domain_mode = true; 131 break; 132 133 case 'P': 134 input_syntax = &input_format_properties; 135 break; 136 137 case 'V': 138 do_version = true; 139 break; 140 141 case CHAR_MAX + 1: /* --stringtable-input */ 142 input_syntax = &input_format_stringtable; 143 break; 144 145 case CHAR_MAX + 2: /* --use-fuzzy */ 146 include_fuzzies = true; 147 break; 148 149 case CHAR_MAX + 3: /* --use-untranslated */ 150 include_untranslated = true; 151 break; 152 153 default: 154 usage (EXIT_FAILURE); 155 break; 156 } 157 158 /* Version information is requested. */ 159 if (do_version) 160 { 161 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); 162 /* xgettext: no-wrap */ 163 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ 164 This is free software; see the source for copying conditions. There is NO\n\ 165 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ 166 "), 167 "1995-1998, 2000-2006"); 168 printf (_("Written by %s.\n"), proper_name ("Peter Miller")); 169 exit (EXIT_SUCCESS); 170 } 171 172 /* Help is requested. */ 173 if (do_help) 174 usage (EXIT_SUCCESS); 175 176 /* Test whether we have an .po file name as argument. */ 177 if (optind >= argc) 178 { 179 error (EXIT_SUCCESS, 0, _("no input files given")); 180 usage (EXIT_FAILURE); 181 } 182 if (optind + 2 != argc) 183 { 184 error (EXIT_SUCCESS, 0, _("exactly 2 input files required")); 185 usage (EXIT_FAILURE); 186 } 187 188 /* compare the two files */ 189 compare (argv[optind], argv[optind + 1], input_syntax); 190 exit (EXIT_SUCCESS); 191 } 192 193 194 /* Display usage information and exit. */ 195 static void 196 usage (int status) 197 { 198 if (status != EXIT_SUCCESS) 199 fprintf (stderr, _("Try `%s --help' for more information.\n"), 200 program_name); 201 else 202 { 203 printf (_("\ 204 Usage: %s [OPTION] def.po ref.pot\n\ 205 "), program_name); 206 printf ("\n"); 207 /* xgettext: no-wrap */ 208 printf (_("\ 209 Compare two Uniforum style .po files to check that both contain the same\n\ 210 set of msgid strings. The def.po file is an existing PO file with the\n\ 211 translations. The ref.pot file is the last created PO file, or a PO Template\n\ 212 file (generally created by xgettext). This is useful for checking that\n\ 213 you have translated each and every message in your program. Where an exact\n\ 214 match cannot be found, fuzzy matching is used to produce better diagnostics.\n\ 215 ")); 216 printf ("\n"); 217 printf (_("\ 218 Mandatory arguments to long options are mandatory for short options too.\n")); 219 printf ("\n"); 220 printf (_("\ 221 Input file location:\n")); 222 printf (_("\ 223 def.po translations\n")); 224 printf (_("\ 225 ref.pot references to the sources\n")); 226 printf (_("\ 227 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); 228 printf ("\n"); 229 printf (_("\ 230 Operation modifiers:\n")); 231 printf (_("\ 232 -m, --multi-domain apply ref.pot to each of the domains in def.po\n")); 233 printf (_("\ 234 --use-fuzzy consider fuzzy entries\n")); 235 printf (_("\ 236 --use-untranslated consider untranslated entries\n")); 237 printf ("\n"); 238 printf (_("\ 239 Input file syntax:\n")); 240 printf (_("\ 241 -P, --properties-input input files are in Java .properties syntax\n")); 242 printf (_("\ 243 --stringtable-input input files are in NeXTstep/GNUstep .strings\n\ 244 syntax\n")); 245 printf ("\n"); 246 printf (_("\ 247 Informative output:\n")); 248 printf (_("\ 249 -h, --help display this help and exit\n")); 250 printf (_("\ 251 -V, --version output version information and exit\n")); 252 printf ("\n"); 253 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout); 254 } 255 256 exit (status); 257 } 258 259 260 /* Return true if a message should be kept. */ 261 static bool 262 is_message_selected (const message_ty *mp) 263 { 264 /* Always keep the header entry. */ 265 if (is_header (mp)) 266 return true; 267 268 return !mp->obsolete; 269 } 270 271 272 /* Remove obsolete messages from a message list. Return the modified list. */ 273 static msgdomain_list_ty * 274 remove_obsoletes (msgdomain_list_ty *mdlp) 275 { 276 size_t k; 277 278 for (k = 0; k < mdlp->nitems; k++) 279 message_list_remove_if_not (mdlp->item[k]->messages, is_message_selected); 280 281 return mdlp; 282 } 283 284 285 static void 286 match_domain (const char *fn1, const char *fn2, 287 message_list_ty *defmlp, message_list_ty *refmlp, 288 int *nerrors) 289 { 290 size_t j; 291 292 for (j = 0; j < refmlp->nitems; j++) 293 { 294 message_ty *refmsg; 295 message_ty *defmsg; 296 297 refmsg = refmlp->item[j]; 298 299 /* See if it is in the other file. */ 300 defmsg = message_list_search (defmlp, refmsg->msgctxt, refmsg->msgid); 301 if (defmsg) 302 { 303 if (!include_untranslated && defmsg->msgstr[0] == '\0') 304 { 305 (*nerrors)++; 306 po_gram_error_at_line (&defmsg->pos, _("\ 307 this message is untranslated")); 308 } 309 else if (!include_fuzzies && defmsg->is_fuzzy && !is_header (defmsg)) 310 { 311 (*nerrors)++; 312 po_gram_error_at_line (&defmsg->pos, _("\ 313 this message needs to be reviewed by the translator")); 314 } 315 else 316 defmsg->used = 1; 317 } 318 else 319 { 320 /* If the message was not defined at all, try to find a very 321 similar message, it could be a typo, or the suggestion may 322 help. */ 323 (*nerrors)++; 324 defmsg = 325 message_list_search_fuzzy (defmlp, refmsg->msgctxt, refmsg->msgid); 326 if (defmsg) 327 { 328 po_gram_error_at_line (&refmsg->pos, _("\ 329 this message is used but not defined...")); 330 error_message_count--; 331 po_gram_error_at_line (&defmsg->pos, _("\ 332 ...but this definition is similar")); 333 defmsg->used = 1; 334 } 335 else 336 po_gram_error_at_line (&refmsg->pos, _("\ 337 this message is used but not defined in %s"), fn1); 338 } 339 } 340 } 341 342 343 static void 344 compare (const char *fn1, const char *fn2, catalog_input_format_ty input_syntax) 345 { 346 msgdomain_list_ty *def; 347 msgdomain_list_ty *ref; 348 int nerrors; 349 size_t j, k; 350 message_list_ty *empty_list; 351 352 /* This is the master file, created by a human. */ 353 def = remove_obsoletes (read_catalog_file (fn1, input_syntax)); 354 355 /* This is the generated file, created by groping the sources with 356 the xgettext program. */ 357 ref = remove_obsoletes (read_catalog_file (fn2, input_syntax)); 358 359 /* The references file can be either in ASCII or in UTF-8. If it is 360 in UTF-8, we have to convert the definitions to UTF-8 as well. */ 361 { 362 bool was_utf8 = false; 363 for (k = 0; k < ref->nitems; k++) 364 { 365 message_list_ty *mlp = ref->item[k]->messages; 366 367 for (j = 0; j < mlp->nitems; j++) 368 if (is_header (mlp->item[j]) /* && !mlp->item[j]->obsolete */) 369 { 370 const char *header = mlp->item[j]->msgstr; 371 372 if (header != NULL) 373 { 374 const char *charsetstr = c_strstr (header, "charset="); 375 376 if (charsetstr != NULL) 377 { 378 size_t len; 379 380 charsetstr += strlen ("charset="); 381 len = strcspn (charsetstr, " \t\n"); 382 if (len == strlen ("UTF-8") 383 && c_strncasecmp (charsetstr, "UTF-8", len) == 0) 384 was_utf8 = true; 385 } 386 } 387 } 388 } 389 if (was_utf8) 390 def = iconv_msgdomain_list (def, "UTF-8", fn1); 391 } 392 393 empty_list = message_list_alloc (false); 394 395 /* Every entry in the xgettext generated file must be matched by a 396 (single) entry in the human created file. */ 397 nerrors = 0; 398 if (!multi_domain_mode) 399 for (k = 0; k < ref->nitems; k++) 400 { 401 const char *domain = ref->item[k]->domain; 402 message_list_ty *refmlp = ref->item[k]->messages; 403 message_list_ty *defmlp; 404 405 defmlp = msgdomain_list_sublist (def, domain, false); 406 if (defmlp == NULL) 407 defmlp = empty_list; 408 409 match_domain (fn1, fn2, defmlp, refmlp, &nerrors); 410 } 411 else 412 { 413 /* Apply the references messages in the default domain to each of 414 the definition domains. */ 415 message_list_ty *refmlp = ref->item[0]->messages; 416 417 for (k = 0; k < def->nitems; k++) 418 { 419 message_list_ty *defmlp = def->item[k]->messages; 420 421 /* Ignore the default message domain if it has no messages. */ 422 if (k > 0 || defmlp->nitems > 0) 423 match_domain (fn1, fn2, defmlp, refmlp, &nerrors); 424 } 425 } 426 427 /* Look for messages in the definition file, which are not present 428 in the reference file, indicating messages which defined but not 429 used in the program. */ 430 for (k = 0; k < def->nitems; ++k) 431 { 432 message_list_ty *defmlp = def->item[k]->messages; 433 434 for (j = 0; j < defmlp->nitems; j++) 435 { 436 message_ty *defmsg = defmlp->item[j]; 437 438 if (!defmsg->used) 439 po_gram_error_at_line (&defmsg->pos, 440 _("warning: this message is not used")); 441 } 442 } 443 444 /* Exit with status 1 on any error. */ 445 if (nerrors > 0) 446 error (EXIT_FAILURE, 0, 447 ngettext ("found %d fatal error", "found %d fatal errors", nerrors), 448 nerrors); 449 } 450