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
check_plural_eval(struct expression * plural_expr,unsigned long nplurals_value,const message_ty * header,unsigned char ** plural_distribution)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 *
plural_help(const char * nullentry)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
check_plural(message_list_ty * mlp,unsigned char ** plural_distribution)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
formatstring_error_logger(const char * format,...)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
check_pair(const message_ty * mp,const char * msgid,const lex_pos_ty * msgid_pos,const char * msgid_plural,const char * msgstr,size_t msgstr_len,const enum is_format is_format[NFORMATS],int check_newlines,int check_format_strings,const unsigned char * plural_distribution,int check_compatibility,int check_accelerators,char accelerator_char)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
check_header_entry(const message_ty * mp,const char * msgstr_string)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
check_message(const message_ty * mp,const lex_pos_ty * msgid_pos,int check_newlines,int check_format_strings,const unsigned char * plural_distribution,int check_header,int check_compatibility,int check_accelerators,char accelerator_char)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
check_message_list(message_list_ty * mlp,int check_newlines,int check_format_strings,int check_header,int check_compatibility,int check_accelerators,char accelerator_char)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