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