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