]> git.ipfire.org Git - thirdparty/cups.git/blame - locale/checkpo.c
Fix the cups.strings file generation (bug in code that generates the Unicode
[thirdparty/cups.git] / locale / checkpo.c
CommitLineData
c277e2f8 1/*
503b54c9
MS
2 * Verify that translations in the .po file have the same number and type of
3 * printf-style format strings.
c277e2f8 4 *
6d086e08 5 * Copyright 2007-2017 by Apple Inc.
503b54c9 6 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
c277e2f8 7 *
503b54c9
MS
8 * These coded instructions, statements, and computer programs are the
9 * property of Apple Inc. and are protected by Federal copyright
10 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
11 * which should have been included with this file. If this file is
57b7b66b 12 * missing or damaged, see the license at "http://www.cups.org/".
f0ab5bff 13 *
c277e2f8
MS
14 * Usage:
15 *
6d086e08 16 * checkpo filename.{po,strings} [... filenameN.{po,strings}]
c277e2f8
MS
17 *
18 * Compile with:
19 *
20 * gcc -o checkpo checkpo.c `cups-config --libs`
c277e2f8
MS
21 */
22
71e16022 23#include <cups/cups-private.h>
c277e2f8
MS
24
25
26/*
27 * Local functions...
28 */
29
30static char *abbreviate(const char *s, char *buf, int bufsize);
31static cups_array_t *collect_formats(const char *id);
6d086e08
MS
32static cups_array_t *cups_load_strings(const char *filename);
33static int cups_read_strings(cups_file_t *fp, char *buffer, size_t bufsize, char **id, char **str);
34static char *cups_scan_strings(char *buffer);
c277e2f8
MS
35static void free_formats(cups_array_t *fmts);
36
37
38/*
6d086e08 39 * 'main()' - Validate .po and .strings files.
c277e2f8
MS
40 */
41
42int /* O - Exit code */
43main(int argc, /* I - Number of command-line args */
44 char *argv[]) /* I - Command-line arguments */
45{
46 int i; /* Looping var */
47 cups_array_t *po; /* .po file */
48 _cups_message_t *msg; /* Current message */
49 cups_array_t *idfmts, /* Format strings in msgid */
50 *strfmts; /* Format strings in msgstr */
51 char *idfmt, /* Current msgid format string */
52 *strfmt; /* Current msgstr format string */
91c84a35 53 int fmtidx; /* Format index */
c277e2f8
MS
54 int status, /* Exit status */
55 pass, /* Pass/fail status */
56 untranslated; /* Untranslated messages */
57 char idbuf[80], /* Abbreviated msgid */
58 strbuf[80]; /* Abbreviated msgstr */
59
60
61 if (argc < 2)
62 {
6d086e08 63 puts("Usage: checkpo filename.{po,strings} [... filenameN.{po,strings}]");
c277e2f8
MS
64 return (1);
65 }
66
67 /*
6d086e08 68 * Check every .po or .strings file on the command-line...
c277e2f8
MS
69 */
70
71 for (i = 1, status = 0; i < argc; i ++)
72 {
73 /*
74 * Use the CUPS .po loader to get the message strings...
75 */
76
6d086e08
MS
77 if (strstr(argv[i], ".strings"))
78 po = cups_load_strings(argv[i]);
79 else
80 po = _cupsMessageLoad(argv[i], 1);
81
82 if (!po)
c277e2f8
MS
83 {
84 perror(argv[i]);
85 return (1);
86 }
87
0837b7e8
MS
88 if (i > 1)
89 putchar('\n');
c277e2f8
MS
90 printf("%s: ", argv[i]);
91 fflush(stdout);
92
93 /*
94 * Scan every message for a % string and then match them up with
95 * the corresponding string in the translation...
96 */
97
98 pass = 1;
99 untranslated = 0;
100
101 for (msg = (_cups_message_t *)cupsArrayFirst(po);
102 msg;
103 msg = (_cups_message_t *)cupsArrayNext(po))
104 {
0837b7e8
MS
105 /*
106 * Make sure filter message prefixes are not translated...
107 */
108
109 if (!strncmp(msg->id, "ALERT:", 6) || !strncmp(msg->id, "CRIT:", 5) ||
110 !strncmp(msg->id, "DEBUG:", 6) || !strncmp(msg->id, "DEBUG2:", 7) ||
111 !strncmp(msg->id, "EMERG:", 6) || !strncmp(msg->id, "ERROR:", 6) ||
112 !strncmp(msg->id, "INFO:", 5) || !strncmp(msg->id, "NOTICE:", 7) ||
113 !strncmp(msg->id, "WARNING:", 8))
114 {
115 if (pass)
116 {
117 pass = 0;
118 puts("FAIL");
119 }
120
121 printf(" Bad prefix on filter message \"%s\"\n",
122 abbreviate(msg->id, idbuf, sizeof(idbuf)));
123 }
124
125 idfmt = msg->id + strlen(msg->id) - 1;
126 if (idfmt >= msg->id && *idfmt == '\n')
127 {
128 if (pass)
129 {
130 pass = 0;
131 puts("FAIL");
132 }
133
134 printf(" Trailing newline in message \"%s\"\n",
135 abbreviate(msg->id, idbuf, sizeof(idbuf)));
136 }
137
138 for (; idfmt >= msg->id; idfmt --)
139 if (!isspace(*idfmt & 255))
140 break;
141
142 if (idfmt >= msg->id && *idfmt == '!')
143 {
144 if (pass)
145 {
146 pass = 0;
147 puts("FAIL");
148 }
149
150 printf(" Exclamation in message \"%s\"\n",
151 abbreviate(msg->id, idbuf, sizeof(idbuf)));
152 }
153
154 if ((idfmt - 2) >= msg->id && !strncmp(idfmt - 2, "...", 3))
155 {
156 if (pass)
157 {
158 pass = 0;
159 puts("FAIL");
160 }
161
162 printf(" Ellipsis in message \"%s\"\n",
163 abbreviate(msg->id, idbuf, sizeof(idbuf)));
164 }
165
166
c277e2f8
MS
167 if (!msg->str || !msg->str[0])
168 {
169 untranslated ++;
170 continue;
171 }
172 else if (strchr(msg->id, '%'))
173 {
174 idfmts = collect_formats(msg->id);
175 strfmts = collect_formats(msg->str);
176 fmtidx = 0;
177
178 for (strfmt = (char *)cupsArrayFirst(strfmts);
179 strfmt;
180 strfmt = (char *)cupsArrayNext(strfmts))
181 {
182 if (isdigit(strfmt[1] & 255) && strfmt[2] == '$')
183 {
184 /*
185 * Handle positioned format stuff...
186 */
187
188 fmtidx = strfmt[1] - '1';
189 strfmt += 3;
190 if ((idfmt = (char *)cupsArrayIndex(idfmts, fmtidx)) != NULL)
191 idfmt ++;
192 }
193 else
194 {
195 /*
196 * Compare against the current format...
197 */
198
199 idfmt = (char *)cupsArrayIndex(idfmts, fmtidx);
200 }
201
202 fmtidx ++;
203
204 if (!idfmt || strcmp(strfmt, idfmt))
205 break;
c277e2f8
MS
206 }
207
208 if (cupsArrayCount(strfmts) != cupsArrayCount(idfmts) || strfmt)
209 {
210 if (pass)
211 {
212 pass = 0;
213 puts("FAIL");
214 }
215
216 printf(" Bad translation string \"%s\"\n for \"%s\"\n",
217 abbreviate(msg->str, strbuf, sizeof(strbuf)),
218 abbreviate(msg->id, idbuf, sizeof(idbuf)));
219 fputs(" Translation formats:", stdout);
220 for (strfmt = (char *)cupsArrayFirst(strfmts);
221 strfmt;
222 strfmt = (char *)cupsArrayNext(strfmts))
223 printf(" %s", strfmt);
224 fputs("\n Original formats:", stdout);
225 for (idfmt = (char *)cupsArrayFirst(idfmts);
226 idfmt;
227 idfmt = (char *)cupsArrayNext(idfmts))
228 printf(" %s", idfmt);
229 putchar('\n');
f0ab5bff 230 putchar('\n');
c277e2f8
MS
231 }
232
233 free_formats(idfmts);
234 free_formats(strfmts);
235 }
236
8b116e60
MS
237 /*
238 * Only allow \\, \n, \r, \t, \", and \### character escapes...
239 */
240
241 for (strfmt = msg->str; *strfmt; strfmt ++)
242 if (*strfmt == '\\' &&
243 strfmt[1] != '\\' && strfmt[1] != 'n' && strfmt[1] != 'r' &&
244 strfmt[1] != 't' && strfmt[1] != '\"' && !isdigit(strfmt[1] & 255))
245 {
246 if (pass)
247 {
248 pass = 0;
249 puts("FAIL");
250 }
251
252 printf(" Bad escape \\%c in filter message \"%s\"\n"
0837b7e8 253 " for \"%s\"\n", strfmt[1],
8b116e60
MS
254 abbreviate(msg->str, strbuf, sizeof(strbuf)),
255 abbreviate(msg->id, idbuf, sizeof(idbuf)));
256 break;
257 }
c277e2f8
MS
258 }
259
260 if (pass)
261 {
0837b7e8
MS
262 if ((untranslated * 10) >= cupsArrayCount(po) &&
263 strcmp(argv[i], "cups.pot"))
c277e2f8
MS
264 {
265 /*
266 * Only allow 10% of messages to be untranslated before we fail...
267 */
268
269 pass = 0;
270 puts("FAIL");
0837b7e8 271 printf(" Too many untranslated messages (%d of %d)\n",
f0ab5bff 272 untranslated, cupsArrayCount(po));
c277e2f8
MS
273 }
274 else if (untranslated > 0)
0837b7e8 275 printf("PASS (%d of %d untranslated)\n", untranslated,
c277e2f8
MS
276 cupsArrayCount(po));
277 else
0837b7e8 278 puts("PASS");
c277e2f8
MS
279 }
280
281 if (!pass)
282 status = 1;
283
284 _cupsMessageFree(po);
285 }
286
287 return (status);
288}
289
290
291/*
292 * 'abbreviate()' - Abbreviate a message string as needed.
293 */
294
295static char * /* O - Abbreviated string */
296abbreviate(const char *s, /* I - String to abbreviate */
297 char *buf, /* I - Buffer */
298 int bufsize) /* I - Size of buffer */
299{
300 char *bufptr; /* Pointer into buffer */
301
302
303 for (bufptr = buf, bufsize -= 4; *s && bufsize > 0; s ++)
304 {
305 if (*s == '\n')
306 {
307 if (bufsize < 2)
308 break;
309
310 *bufptr++ = '\\';
311 *bufptr++ = 'n';
312 bufsize -= 2;
313 }
314 else if (*s == '\t')
315 {
316 if (bufsize < 2)
317 break;
318
319 *bufptr++ = '\\';
320 *bufptr++ = 't';
321 bufsize -= 2;
322 }
323 else if (*s >= 0 && *s < ' ')
324 {
325 if (bufsize < 4)
326 break;
327
328 sprintf(bufptr, "\\%03o", *s);
329 bufptr += 4;
330 bufsize -= 4;
331 }
332 else
333 {
334 *bufptr++ = *s;
335 bufsize --;
336 }
337 }
338
339 if (*s)
5a9febac 340 memcpy(bufptr, "...", 4);
c277e2f8
MS
341 else
342 *bufptr = '\0';
343
344 return (buf);
345}
346
347
348/*
349 * 'collect_formats()' - Collect all of the format strings in the msgid.
350 */
351
352static cups_array_t * /* O - Array of format strings */
353collect_formats(const char *id) /* I - msgid string */
354{
355 cups_array_t *fmts; /* Array of format strings */
356 char buf[255], /* Format string buffer */
357 *bufptr; /* Pointer into format string */
358
359
360 fmts = cupsArrayNew(NULL, NULL);
361
362 while ((id = strchr(id, '%')) != NULL)
363 {
364 if (id[1] == '%')
365 {
366 /*
367 * Skip %%...
368 */
369
370 id += 2;
371 continue;
372 }
373
374 for (bufptr = buf; *id && bufptr < (buf + sizeof(buf) - 1); id ++)
375 {
376 *bufptr++ = *id;
377
378 if (strchr("CDEFGIOSUXcdeifgopsux", *id))
379 {
380 id ++;
381 break;
382 }
383 }
384
385 *bufptr = '\0';
386 cupsArrayAdd(fmts, strdup(buf));
387 }
388
389 return (fmts);
390}
391
392
6d086e08
MS
393/*
394 * 'cups_load_strings()' - Load a .strings file into a _cups_msg_t array.
395 */
396
397static cups_array_t * /* O - CUPS array of _cups_msg_t values */
398cups_load_strings(const char *filename) /* I - File to load */
399{
400 cups_file_t *fp; /* .strings file */
401 cups_array_t *po; /* Localization array */
402 _cups_message_t *m; /* Localization message */
403 char buffer[8192], /* Message buffer */
404 *id, /* ID string */
405 *str; /* Translated message */
406
407
408 if ((fp = cupsFileOpen(filename, "r")) == NULL)
409 return (NULL);
410
411 po = _cupsMessageNew(NULL);
412
413 while (cups_read_strings(fp, buffer, sizeof(buffer), &id, &str))
414 {
415 if ((m = malloc(sizeof(_cups_message_t))) == NULL)
416 break;
417
418 m->id = strdup(id);
419 m->str = strdup(str);
420
421 if (m->id && m->str)
422 cupsArrayAdd(po, m);
423 else
424 {
425 if (m->id)
426 free(m->id);
427
428 if (m->str)
429 free(m->str);
430
431 free(m);
432
433 cupsArrayDelete(po);
434 po = NULL;
435 break;
436 }
437 }
438
439 cupsFileClose(fp);
440
441 return (po);
442}
443
444
445/*
446 * 'cups_read_strings()' - Read a pair of strings from a .strings file.
447 */
448
449static int /* O - 1 on success, 0 on failure */
450cups_read_strings(cups_file_t *strings, /* I - .strings file */
451 char *buffer, /* I - Line buffer */
452 size_t bufsize, /* I - Size of line buffer */
453 char **id, /* O - Pointer to ID string */
454 char **str) /* O - Pointer to translation string */
455{
456 char *bufptr; /* Pointer into buffer */
457
458
459 while (cupsFileGets(strings, buffer, bufsize))
460 {
461 if (buffer[0] != '\"')
462 continue;
463
464 *id = buffer + 1;
465 bufptr = cups_scan_strings(buffer);
466
467 if (*bufptr != '\"')
468 continue;
469
470 *bufptr++ = '\0';
471
472 while (*bufptr && *bufptr != '\"')
473 bufptr ++;
474
475 if (!*bufptr)
476 continue;
477
478 *str = bufptr + 1;
479 bufptr = cups_scan_strings(bufptr);
480
481 if (*bufptr != '\"')
482 continue;
483
484 *bufptr = '\0';
485
486 return (1);
487 }
488
489 return (0);
490}
491
492
493/*
494 * 'cups_scan_strings()' - Scan a quoted string.
495 */
496
497static char * /* O - End of string */
498cups_scan_strings(char *buffer) /* I - Start of string */
499{
500 char *bufptr; /* Pointer into string */
501
502
503 for (bufptr = buffer + 1; *bufptr && *bufptr != '\"'; bufptr ++)
504 {
505 if (*bufptr == '\\')
506 {
507 if (bufptr[1] >= '0' && bufptr[1] <= '3' &&
508 bufptr[2] >= '0' && bufptr[2] <= '7' &&
509 bufptr[3] >= '0' && bufptr[3] <= '7')
510 {
511 /*
512 * Decode \nnn octal escape...
513 */
514
515 *bufptr = (char)(((((bufptr[1] - '0') << 3) | (bufptr[2] - '0')) << 3) | (bufptr[3] - '0'));
516 _cups_strcpy(bufptr + 1, bufptr + 4);
517 }
518 else
519 {
520 /*
521 * Decode \C escape...
522 */
523
524 _cups_strcpy(bufptr, bufptr + 1);
525 if (*bufptr == 'n')
526 *bufptr = '\n';
527 else if (*bufptr == 'r')
528 *bufptr = '\r';
529 else if (*bufptr == 't')
530 *bufptr = '\t';
531 }
532 }
533 }
534
535 return (bufptr);
536}
537
538
c277e2f8
MS
539/*
540 * 'free_formats()' - Free all of the format strings.
541 */
542
543static void
544free_formats(cups_array_t *fmts) /* I - Array of format strings */
545{
546 char *s; /* Current string */
547
548
549 for (s = (char *)cupsArrayFirst(fmts); s; s = (char *)cupsArrayNext(fmts))
550 free(s);
551
552 cupsArrayDelete(fmts);
553}