1 /* Checking of messages in PO files. 2 Copyright (C) 1995-1998, 2000-2006 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 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 /* Specification. */ 24 #include "msgl-check.h" 25 26 #include <limits.h> 27 #include <setjmp.h> 28 #include <signal.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <stdarg.h> 32 33 #include "c-ctype.h" 34 #include "xalloc.h" 35 #include "xvasprintf.h" 36 #include "po-xerror.h" 37 #include "format.h" 38 #include "plural-exp.h" 39 #include "plural-eval.h" 40 #include "plural-table.h" 41 #include "c-strstr.h" 42 #include "vasprintf.h" 43 #include "exit.h" 44 #include "message.h" 45 #include "gettext.h" 46 47 #define _(str) gettext (str) 48 49 #define SIZEOF(a) (sizeof(a) / sizeof(a[0])) 50 51 52 /* Check the values returned by plural_eval. 53 Return the number of errors that were seen. 54 If no errors, returns in *PLURAL_DISTRIBUTION either NULL or an array 55 of length NPLURALS_VALUE describing which plural formula values appear 56 infinitely often. */ 57 static int 58 check_plural_eval (struct expression *plural_expr, 59 unsigned long nplurals_value, 60 const message_ty *header, 61 unsigned char **plural_distribution) 62 { 63 /* Do as if the plural formula assumes a value N infinitely often if it 64 assumes it at least 5 times. */ 65 #define OFTEN 5 66 unsigned char * volatile distribution; 67 68 /* Allocate a distribution array. */ 69 if (nplurals_value <= 100) 70 distribution = (unsigned char *) xcalloc (nplurals_value, 1); 71 else 72 /* nplurals_value is nonsense. Don't risk an out-of-memory. */ 73 distribution = NULL; 74 75 if (sigsetjmp (sigfpe_exit, 1) == 0) 76 { 77 unsigned long n; 78 79 /* Protect against arithmetic exceptions. */ 80 install_sigfpe_handler (); 81 82 for (n = 0; n <= 1000; n++) 83 { 84 unsigned long val = plural_eval (plural_expr, n); 85 86 if ((long) val < 0) 87 { 88 /* End of protection against arithmetic exceptions. */ 89 uninstall_sigfpe_handler (); 90 91 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, 92 _("plural expression can produce negative values")); 93 return 1; 94 } 95 else if (val >= nplurals_value) 96 { 97 char *msg; 98 99 /* End of protection against arithmetic exceptions. */ 100 uninstall_sigfpe_handler (); 101 102 msg = xasprintf (_("nplurals = %lu but plural expression can produce values as large as %lu"), 103 nplurals_value, val); 104 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg); 105 free (msg); 106 return 1; 107 } 108 109 if (distribution != NULL && distribution[val] < OFTEN) 110 distribution[val]++; 111 } 112 113 /* End of protection against arithmetic exceptions. */ 114 uninstall_sigfpe_handler (); 115 116 /* Normalize the distribution[val] statistics. */ 117 if (distribution != NULL) 118 { 119 unsigned long val; 120 121 for (val = 0; val < nplurals_value; val++) 122 distribution[val] = (distribution[val] == OFTEN ? 1 : 0); 123 } 124 *plural_distribution = distribution; 125 126 return 0; 127 } 128 else 129 { 130 /* Caught an arithmetic exception. */ 131 const char *msg; 132 133 /* End of protection against arithmetic exceptions. */ 134 uninstall_sigfpe_handler (); 135 136 #if USE_SIGINFO 137 switch (sigfpe_code) 138 #endif 139 { 140 #if USE_SIGINFO 141 # ifdef FPE_INTDIV 142 case FPE_INTDIV: 143 msg = _("plural expression can produce division by zero"); 144 break; 145 # endif 146 # ifdef FPE_INTOVF 147 case FPE_INTOVF: 148 msg = _("plural expression can produce integer overflow"); 149 break; 150 # endif 151 default: 152 #endif 153 msg = _("plural expression can produce arithmetic exceptions, possibly division by zero"); 154 } 155 156 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg); 157 158 if (distribution != NULL) 159 free (distribution); 160 161 return 1; 162 } 163 #undef OFTEN 164 } 165 166 167 /* Try to help the translator by looking up the right plural formula for her. 168 Return a freshly allocated multiline help string, or NULL. */ 169 static char * 170 plural_help (const char *nullentry) 171 { 172 const char *language; 173 size_t j; 174 175 language = c_strstr (nullentry, "Language-Team: "); 176 if (language != NULL) 177 { 178 language += 15; 179 for (j = 0; j < plural_table_size; j++) 180 if (strncmp (language, 181 plural_table[j].language, 182 strlen (plural_table[j].language)) == 0) 183 { 184 char *helpline1 = 185 xasprintf (_("Try using the following, valid for %s:"), 186 plural_table[j].language); 187 char *help = 188 xasprintf ("%s\n\"Plural-Forms: %s\\n\"\n", 189 helpline1, plural_table[j].value); 190 free (helpline1); 191 return help; 192 } 193 } 194 return NULL; 195 } 196 197 198 /* Perform plural expression checking. 199 Return the number of errors that were seen. 200 If no errors, returns in *PLURAL_DISTRIBUTION either NULL or an array 201 describing which plural formula values appear infinitely often. */ 202 static int 203 check_plural (message_list_ty *mlp, unsigned char **plural_distribution) 204 { 205 int seen_errors = 0; 206 const message_ty *has_plural; 207 unsigned long min_nplurals; 208 const message_ty *min_pos; 209 unsigned long max_nplurals; 210 const message_ty *max_pos; 211 size_t j; 212 message_ty *header; 213 unsigned char *distribution = NULL; 214 215 /* Determine whether mlp has plural entries. */ 216 has_plural = NULL; 217 min_nplurals = ULONG_MAX; 218 min_pos = NULL; 219 max_nplurals = 0; 220 max_pos = NULL; 221 for (j = 0; j < mlp->nitems; j++) 222 { 223 message_ty *mp = mlp->item[j]; 224 225 if (!mp->obsolete && mp->msgid_plural != NULL) 226 { 227 const char *p; 228 const char *p_end; 229 unsigned long n; 230 231 if (has_plural == NULL) 232 has_plural = mp; 233 234 n = 0; 235 for (p = mp->msgstr, p_end = p + mp->msgstr_len; 236 p < p_end; 237 p += strlen (p) + 1) 238 n++; 239 if (min_nplurals > n) 240 { 241 min_nplurals = n; 242 min_pos = mp; 243 } 244 if (max_nplurals < n) 245 { 246 max_nplurals = n; 247 max_pos = mp; 248 } 249 } 250 } 251 252 /* Look at the plural entry for this domain. 253 Cf, function extract_plural_expression. */ 254 header = message_list_search (mlp, NULL, ""); 255 if (header != NULL && !header->obsolete) 256 { 257 const char *nullentry; 258 const char *plural; 259 const char *nplurals; 260 261 nullentry = header->msgstr; 262 263 plural = c_strstr (nullentry, "plural="); 264 nplurals = c_strstr (nullentry, "nplurals="); 265 if (plural == NULL && has_plural != NULL) 266 { 267 const char *msg1 = 268 _("message catalog has plural form translations"); 269 const char *msg2 = 270 _("but header entry lacks a \"plural=EXPRESSION\" attribute"); 271 char *help = plural_help (nullentry); 272 273 if (help != NULL) 274 { 275 char *msg2ext = xasprintf ("%s\n%s", msg2, help); 276 po_xerror2 (PO_SEVERITY_ERROR, 277 has_plural, NULL, 0, 0, false, msg1, 278 header, NULL, 0, 0, true, msg2ext); 279 free (msg2ext); 280 free (help); 281 } 282 else 283 po_xerror2 (PO_SEVERITY_ERROR, 284 has_plural, NULL, 0, 0, false, msg1, 285 header, NULL, 0, 0, false, msg2); 286 287 seen_errors++; 288 } 289 if (nplurals == NULL && has_plural != NULL) 290 { 291 const char *msg1 = 292 _("message catalog has plural form translations"); 293 const char *msg2 = 294 _("but header entry lacks a \"nplurals=INTEGER\" attribute"); 295 char *help = plural_help (nullentry); 296 297 if (help != NULL) 298 { 299 char *msg2ext = xasprintf ("%s\n%s", msg2, help); 300 po_xerror2 (PO_SEVERITY_ERROR, 301 has_plural, NULL, 0, 0, false, msg1, 302 header, NULL, 0, 0, true, msg2ext); 303 free (msg2ext); 304 free (help); 305 } 306 else 307 po_xerror2 (PO_SEVERITY_ERROR, 308 has_plural, NULL, 0, 0, false, msg1, 309 header, NULL, 0, 0, false, msg2); 310 311 seen_errors++; 312 } 313 if (plural != NULL && nplurals != NULL) 314 { 315 const char *endp; 316 unsigned long int nplurals_value; 317 struct parse_args args; 318 struct expression *plural_expr; 319 320 /* First check the number. */ 321 nplurals += 9; 322 while (*nplurals != '\0' && c_isspace ((unsigned char) *nplurals)) 323 ++nplurals; 324 endp = nplurals; 325 nplurals_value = 0; 326 if (*nplurals >= '0' && *nplurals <= '9') 327 nplurals_value = strtoul (nplurals, (char **) &endp, 10); 328 if (nplurals == endp) 329 { 330 const char *msg = _("invalid nplurals value"); 331 char *help = plural_help (nullentry); 332 333 if (help != NULL) 334 { 335 char *msgext = xasprintf ("%s\n%s", msg, help); 336 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true, 337 msgext); 338 free (msgext); 339 free (help); 340 } 341 else 342 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg); 343 344 seen_errors++; 345 } 346 347 /* Then check the expression. */ 348 plural += 7; 349 args.cp = plural; 350 if (parse_plural_expression (&args) != 0) 351 { 352 const char *msg = _("invalid plural expression"); 353 char *help = plural_help (nullentry); 354 355 if (help != NULL) 356 { 357 char *msgext = xasprintf ("%s\n%s", msg, help); 358 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true, 359 msgext); 360 free (msgext); 361 free (help); 362 } 363 else 364 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg); 365 366 seen_errors++; 367 } 368 plural_expr = args.res; 369 370 /* See whether nplurals and plural fit together. */ 371 if (!seen_errors) 372 seen_errors = 373 check_plural_eval (plural_expr, nplurals_value, header, 374 &distribution); 375 376 /* Check the number of plurals of the translations. */ 377 if (!seen_errors) 378 { 379 if (min_nplurals < nplurals_value) 380 { 381 char *msg1 = 382 xasprintf (_("nplurals = %lu"), nplurals_value); 383 char *msg2 = 384 xasprintf (ngettext ("but some messages have only one plural form", 385 "but some messages have only %lu plural forms", 386 min_nplurals), 387 min_nplurals); 388 po_xerror2 (PO_SEVERITY_ERROR, 389 header, NULL, 0, 0, false, msg1, 390 min_pos, NULL, 0, 0, false, msg2); 391 free (msg2); 392 free (msg1); 393 seen_errors++; 394 } 395 else if (max_nplurals > nplurals_value) 396 { 397 char *msg1 = 398 xasprintf (_("nplurals = %lu"), nplurals_value); 399 char *msg2 = 400 xasprintf (ngettext ("but some messages have one plural form", 401 "but some messages have %lu plural forms", 402 max_nplurals), 403 max_nplurals); 404 po_xerror2 (PO_SEVERITY_ERROR, 405 header, NULL, 0, 0, false, msg1, 406 max_pos, NULL, 0, 0, false, msg2); 407 free (msg2); 408 free (msg1); 409 seen_errors++; 410 } 411 /* The only valid case is max_nplurals <= n <= min_nplurals, 412 which means either has_plural == NULL or 413 max_nplurals = n = min_nplurals. */ 414 } 415 } 416 } 417 else if (has_plural != NULL) 418 { 419 po_xerror (PO_SEVERITY_ERROR, has_plural, NULL, 0, 0, false, 420 _("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\"")); 421 seen_errors++; 422 } 423 424 /* distribution is not needed if we report errors. 425 Also, if there was an error due to max_nplurals > nplurals_value, 426 we must not use distribution because we would be doing out-of-bounds 427 array accesses. */ 428 if (seen_errors > 0 && distribution != NULL) 429 { 430 free (distribution); 431 distribution = NULL; 432 } 433 *plural_distribution = distribution; 434 435 return seen_errors; 436 } 437 438 439 /* Signal an error when checking format strings. */ 440 static const message_ty *curr_mp; 441 static lex_pos_ty curr_msgid_pos; 442 static void 443 formatstring_error_logger (const char *format, ...) 444 __attribute__ ((__format__ (__printf__, 1, 2))); 445 static void 446 formatstring_error_logger (const char *format, ...) 447 { 448 va_list args; 449 char *msg; 450 451 va_start (args, format); 452 if (vasprintf (&msg, format, args) < 0) 453 error (EXIT_FAILURE, 0, _("memory exhausted")); 454 va_end (args); 455 po_xerror (PO_SEVERITY_ERROR, 456 curr_mp, curr_msgid_pos.file_name, curr_msgid_pos.line_number, 457 (size_t)(-1), false, msg); 458 free (msg); 459 } 460 461 462 /* Perform miscellaneous checks on a message. 463 PLURAL_DISTRIBUTION is either NULL or an array of nplurals elements, 464 PLURAL_DISTRIBUTION[j] being true if the value j appears to be assumed 465 infinitely often by the plural formula. */ 466 static int 467 check_pair (const message_ty *mp, 468 const char *msgid, 469 const lex_pos_ty *msgid_pos, 470 const char *msgid_plural, 471 const char *msgstr, size_t msgstr_len, 472 const enum is_format is_format[NFORMATS], 473 int check_newlines, 474 int check_format_strings, const unsigned char *plural_distribution, 475 int check_compatibility, 476 int check_accelerators, char accelerator_char) 477 { 478 int seen_errors; 479 int has_newline; 480 unsigned int j; 481 482 /* If the msgid string is empty we have the special entry reserved for 483 information about the translation. */ 484 if (msgid[0] == '\0') 485 return 0; 486 487 seen_errors = 0; 488 489 if (check_newlines) 490 { 491 /* Test 1: check whether all or none of the strings begin with a '\n'. */ 492 has_newline = (msgid[0] == '\n'); 493 #define TEST_NEWLINE(p) (p[0] == '\n') 494 if (msgid_plural != NULL) 495 { 496 const char *p; 497 498 if (TEST_NEWLINE(msgid_plural) != has_newline) 499 { 500 po_xerror (PO_SEVERITY_ERROR, 501 mp, msgid_pos->file_name, msgid_pos->line_number, 502 (size_t)(-1), false, _("\ 503 `msgid' and `msgid_plural' entries do not both begin with '\\n'")); 504 seen_errors++; 505 } 506 for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++) 507 if (TEST_NEWLINE(p) != has_newline) 508 { 509 char *msg = 510 xasprintf (_("\ 511 `msgid' and `msgstr[%u]' entries do not both begin with '\\n'"), j); 512 po_xerror (PO_SEVERITY_ERROR, 513 mp, msgid_pos->file_name, msgid_pos->line_number, 514 (size_t)(-1), false, msg); 515 free (msg); 516 seen_errors++; 517 } 518 } 519 else 520 { 521 if (TEST_NEWLINE(msgstr) != has_newline) 522 { 523 po_xerror (PO_SEVERITY_ERROR, 524 mp, msgid_pos->file_name, msgid_pos->line_number, 525 (size_t)(-1), false, _("\ 526 `msgid' and `msgstr' entries do not both begin with '\\n'")); 527 seen_errors++; 528 } 529 } 530 #undef TEST_NEWLINE 531 532 /* Test 2: check whether all or none of the strings end with a '\n'. */ 533 has_newline = (msgid[strlen (msgid) - 1] == '\n'); 534 #define TEST_NEWLINE(p) (p[0] != '\0' && p[strlen (p) - 1] == '\n') 535 if (msgid_plural != NULL) 536 { 537 const char *p; 538 539 if (TEST_NEWLINE(msgid_plural) != has_newline) 540 { 541 po_xerror (PO_SEVERITY_ERROR, 542 mp, msgid_pos->file_name, msgid_pos->line_number, 543 (size_t)(-1), false, _("\ 544 `msgid' and `msgid_plural' entries do not both end with '\\n'")); 545 seen_errors++; 546 } 547 for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++) 548 if (TEST_NEWLINE(p) != has_newline) 549 { 550 char *msg = 551 xasprintf (_("\ 552 `msgid' and `msgstr[%u]' entries do not both end with '\\n'"), j); 553 po_xerror (PO_SEVERITY_ERROR, 554 mp, msgid_pos->file_name, msgid_pos->line_number, 555 (size_t)(-1), false, msg); 556 free (msg); 557 seen_errors++; 558 } 559 } 560 else 561 { 562 if (TEST_NEWLINE(msgstr) != has_newline) 563 { 564 po_xerror (PO_SEVERITY_ERROR, 565 mp, msgid_pos->file_name, msgid_pos->line_number, 566 (size_t)(-1), false, _("\ 567 `msgid' and `msgstr' entries do not both end with '\\n'")); 568 seen_errors++; 569 } 570 } 571 #undef TEST_NEWLINE 572 } 573 574 if (check_compatibility && msgid_plural != NULL) 575 { 576 po_xerror (PO_SEVERITY_ERROR, 577 mp, msgid_pos->file_name, msgid_pos->line_number, 578 (size_t)(-1), false, _("\ 579 plural handling is a GNU gettext extension")); 580 seen_errors++; 581 } 582 583 if (check_format_strings) 584 /* Test 3: Check whether both formats strings contain the same number 585 of format specifications. */ 586 { 587 curr_mp = mp; 588 curr_msgid_pos = *msgid_pos; 589 seen_errors += 590 check_msgid_msgstr_format (msgid, msgid_plural, msgstr, msgstr_len, 591 is_format, plural_distribution, 592 formatstring_error_logger); 593 } 594 595 if (check_accelerators && msgid_plural == NULL) 596 /* Test 4: Check that if msgid is a menu item with a keyboard accelerator, 597 the msgstr has an accelerator as well. A keyboard accelerator is 598 designated by an immediately preceding '&'. We cannot check whether 599 two accelerators collide, only whether the translator has bothered 600 thinking about them. */ 601 { 602 const char *p; 603 604 /* We are only interested in msgids that contain exactly one '&'. */ 605 p = strchr (msgid, accelerator_char); 606 if (p != NULL && strchr (p + 1, accelerator_char) == NULL) 607 { 608 /* Count the number of '&' in msgstr, but ignore '&&'. */ 609 unsigned int count = 0; 610 611 for (p = msgstr; (p = strchr (p, accelerator_char)) != NULL; p++) 612 if (p[1] == accelerator_char) 613 p++; 614 else 615 count++; 616 617 if (count == 0) 618 { 619 char *msg = 620 xasprintf (_("msgstr lacks the keyboard accelerator mark '%c'"), 621 accelerator_char); 622 po_xerror (PO_SEVERITY_ERROR, 623 mp, msgid_pos->file_name, msgid_pos->line_number, 624 (size_t)(-1), false, msg); 625 free (msg); 626 } 627 else if (count > 1) 628 { 629 char *msg = 630 xasprintf (_("msgstr has too many keyboard accelerator marks '%c'"), 631 accelerator_char); 632 po_xerror (PO_SEVERITY_ERROR, 633 mp, msgid_pos->file_name, msgid_pos->line_number, 634 (size_t)(-1), false, msg); 635 free (msg); 636 } 637 } 638 } 639 640 return seen_errors; 641 } 642 643 644 /* Perform miscellaneous checks on a header entry. */ 645 static void 646 check_header_entry (const message_ty *mp, const char *msgstr_string) 647 { 648 static const char *required_fields[] = 649 { 650 "Project-Id-Version", "PO-Revision-Date", "Last-Translator", 651 "Language-Team", "MIME-Version", "Content-Type", 652 "Content-Transfer-Encoding" 653 }; 654 static const char *default_values[] = 655 { 656 "PACKAGE VERSION", "YEAR-MO-DA", "FULL NAME", "LANGUAGE", NULL, 657 "text/plain; charset=CHARSET", "ENCODING" 658 }; 659 const size_t nfields = SIZEOF (required_fields); 660 int initial = -1; 661 int cnt; 662 663 for (cnt = 0; cnt < nfields; ++cnt) 664 { 665 char *endp = c_strstr (msgstr_string, required_fields[cnt]); 666 667 if (endp == NULL) 668 { 669 char *msg = 670 xasprintf (_("headerfield `%s' missing in header\n"), 671 required_fields[cnt]); 672 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, true, msg); 673 free (msg); 674 } 675 else if (endp != msgstr_string && endp[-1] != '\n') 676 { 677 char *msg = 678 xasprintf (_("\ 679 header field `%s' should start at beginning of line\n"), 680 required_fields[cnt]); 681 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, true, msg); 682 free (msg); 683 } 684 else if (default_values[cnt] != NULL 685 && strncmp (default_values[cnt], 686 endp + strlen (required_fields[cnt]) + 2, 687 strlen (default_values[cnt])) == 0) 688 { 689 if (initial != -1) 690 { 691 po_xerror (PO_SEVERITY_ERROR, 692 mp, NULL, 0, 0, true, _("\ 693 some header fields still have the initial default value\n")); 694 initial = -1; 695 break; 696 } 697 else 698 initial = cnt; 699 } 700 } 701 702 if (initial != -1) 703 { 704 char *msg = 705 xasprintf (_("field `%s' still has initial default value\n"), 706 required_fields[initial]); 707 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, true, msg); 708 free (msg); 709 } 710 } 711 712 713 /* Perform all checks on a non-obsolete message. 714 PLURAL_DISTRIBUTION is either NULL or an array of nplurals elements, 715 PLURAL_DISTRIBUTION[j] being true if the value j appears to be assumed 716 infinitely often by the plural formula. 717 Return the number of errors that were seen. */ 718 int 719 check_message (const message_ty *mp, 720 const lex_pos_ty *msgid_pos, 721 int check_newlines, 722 int check_format_strings, const unsigned char *plural_distribution, 723 int check_header, 724 int check_compatibility, 725 int check_accelerators, char accelerator_char) 726 { 727 if (check_header && is_header (mp)) 728 check_header_entry (mp, mp->msgstr); 729 730 return check_pair (mp, 731 mp->msgid, msgid_pos, mp->msgid_plural, 732 mp->msgstr, mp->msgstr_len, 733 mp->is_format, 734 check_newlines, 735 check_format_strings, plural_distribution, 736 check_compatibility, 737 check_accelerators, accelerator_char); 738 } 739 740 741 /* Perform all checks on a message list. 742 Return the number of errors that were seen. */ 743 int 744 check_message_list (message_list_ty *mlp, 745 int check_newlines, 746 int check_format_strings, 747 int check_header, 748 int check_compatibility, 749 int check_accelerators, char accelerator_char) 750 { 751 int seen_errors = 0; 752 unsigned char *plural_distribution = NULL; 753 size_t j; 754 755 if (check_header) 756 seen_errors += check_plural (mlp, &plural_distribution); 757 758 for (j = 0; j < mlp->nitems; j++) 759 { 760 message_ty *mp = mlp->item[j]; 761 762 if (!mp->obsolete) 763 seen_errors += check_message (mp, &mp->pos, 764 check_newlines, 765 check_format_strings, plural_distribution, 766 check_header, check_compatibility, 767 check_accelerators, accelerator_char); 768 } 769 770 return seen_errors; 771 } 772