]> git.ipfire.org Git - thirdparty/cups.git/blob - locale/checkpo.c
Fix requested attributes lists - missing -col versions of counters and the
[thirdparty/cups.git] / locale / checkpo.c
1 /*
2 * Verify that translations in the .po file have the same number and type of
3 * printf-style format strings.
4 *
5 * Copyright 2007-2017 by Apple Inc.
6 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
9 *
10 * Usage:
11 *
12 * checkpo filename.{po,strings} [... filenameN.{po,strings}]
13 *
14 * Compile with:
15 *
16 * gcc -o checkpo checkpo.c `cups-config --libs`
17 */
18
19 #include <cups/cups-private.h>
20
21
22 /*
23 * Local functions...
24 */
25
26 static char *abbreviate(const char *s, char *buf, int bufsize);
27 static cups_array_t *collect_formats(const char *id);
28 static void free_formats(cups_array_t *fmts);
29
30
31 /*
32 * 'main()' - Validate .po and .strings files.
33 */
34
35 int /* O - Exit code */
36 main(int argc, /* I - Number of command-line args */
37 char *argv[]) /* I - Command-line arguments */
38 {
39 int i; /* Looping var */
40 cups_array_t *po; /* .po file */
41 _cups_message_t *msg; /* Current message */
42 cups_array_t *idfmts, /* Format strings in msgid */
43 *strfmts; /* Format strings in msgstr */
44 char *idfmt, /* Current msgid format string */
45 *strfmt; /* Current msgstr format string */
46 int fmtidx; /* Format index */
47 int status, /* Exit status */
48 pass, /* Pass/fail status */
49 untranslated; /* Untranslated messages */
50 char idbuf[80], /* Abbreviated msgid */
51 strbuf[80]; /* Abbreviated msgstr */
52
53
54 if (argc < 2)
55 {
56 puts("Usage: checkpo filename.{po,strings} [... filenameN.{po,strings}]");
57 return (1);
58 }
59
60 /*
61 * Check every .po or .strings file on the command-line...
62 */
63
64 for (i = 1, status = 0; i < argc; i ++)
65 {
66 /*
67 * Use the CUPS .po loader to get the message strings...
68 */
69
70 if (strstr(argv[i], ".strings"))
71 po = _cupsMessageLoad(argv[i], _CUPS_MESSAGE_STRINGS);
72 else
73 po = _cupsMessageLoad(argv[i], _CUPS_MESSAGE_PO);
74
75 if (!po)
76 {
77 perror(argv[i]);
78 return (1);
79 }
80
81 if (i > 1)
82 putchar('\n');
83 printf("%s: ", argv[i]);
84 fflush(stdout);
85
86 /*
87 * Scan every message for a % string and then match them up with
88 * the corresponding string in the translation...
89 */
90
91 pass = 1;
92 untranslated = 0;
93
94 for (msg = (_cups_message_t *)cupsArrayFirst(po);
95 msg;
96 msg = (_cups_message_t *)cupsArrayNext(po))
97 {
98 /*
99 * Make sure filter message prefixes are not translated...
100 */
101
102 if (!strncmp(msg->msg, "ALERT:", 6) || !strncmp(msg->msg, "CRIT:", 5) ||
103 !strncmp(msg->msg, "DEBUG:", 6) || !strncmp(msg->msg, "DEBUG2:", 7) ||
104 !strncmp(msg->msg, "EMERG:", 6) || !strncmp(msg->msg, "ERROR:", 6) ||
105 !strncmp(msg->msg, "INFO:", 5) || !strncmp(msg->msg, "NOTICE:", 7) ||
106 !strncmp(msg->msg, "WARNING:", 8))
107 {
108 if (pass)
109 {
110 pass = 0;
111 puts("FAIL");
112 }
113
114 printf(" Bad prefix on filter message \"%s\"\n",
115 abbreviate(msg->msg, idbuf, sizeof(idbuf)));
116 }
117
118 idfmt = msg->msg + strlen(msg->msg) - 1;
119 if (idfmt >= msg->msg && *idfmt == '\n')
120 {
121 if (pass)
122 {
123 pass = 0;
124 puts("FAIL");
125 }
126
127 printf(" Trailing newline in message \"%s\"\n",
128 abbreviate(msg->msg, idbuf, sizeof(idbuf)));
129 }
130
131 for (; idfmt >= msg->msg; idfmt --)
132 if (!isspace(*idfmt & 255))
133 break;
134
135 if (idfmt >= msg->msg && *idfmt == '!')
136 {
137 if (pass)
138 {
139 pass = 0;
140 puts("FAIL");
141 }
142
143 printf(" Exclamation in message \"%s\"\n",
144 abbreviate(msg->msg, idbuf, sizeof(idbuf)));
145 }
146
147 if ((idfmt - 2) >= msg->msg && !strncmp(idfmt - 2, "...", 3))
148 {
149 if (pass)
150 {
151 pass = 0;
152 puts("FAIL");
153 }
154
155 printf(" Ellipsis in message \"%s\"\n",
156 abbreviate(msg->msg, idbuf, sizeof(idbuf)));
157 }
158
159
160 if (!msg->str || !msg->str[0])
161 {
162 untranslated ++;
163 continue;
164 }
165 else if (strchr(msg->msg, '%'))
166 {
167 idfmts = collect_formats(msg->msg);
168 strfmts = collect_formats(msg->str);
169 fmtidx = 0;
170
171 for (strfmt = (char *)cupsArrayFirst(strfmts);
172 strfmt;
173 strfmt = (char *)cupsArrayNext(strfmts))
174 {
175 if (isdigit(strfmt[1] & 255) && strfmt[2] == '$')
176 {
177 /*
178 * Handle positioned format stuff...
179 */
180
181 fmtidx = strfmt[1] - '1';
182 strfmt += 3;
183 if ((idfmt = (char *)cupsArrayIndex(idfmts, fmtidx)) != NULL)
184 idfmt ++;
185 }
186 else
187 {
188 /*
189 * Compare against the current format...
190 */
191
192 idfmt = (char *)cupsArrayIndex(idfmts, fmtidx);
193 }
194
195 fmtidx ++;
196
197 if (!idfmt || strcmp(strfmt, idfmt))
198 break;
199 }
200
201 if (cupsArrayCount(strfmts) != cupsArrayCount(idfmts) || strfmt)
202 {
203 if (pass)
204 {
205 pass = 0;
206 puts("FAIL");
207 }
208
209 printf(" Bad translation string \"%s\"\n for \"%s\"\n",
210 abbreviate(msg->str, strbuf, sizeof(strbuf)),
211 abbreviate(msg->msg, idbuf, sizeof(idbuf)));
212 fputs(" Translation formats:", stdout);
213 for (strfmt = (char *)cupsArrayFirst(strfmts);
214 strfmt;
215 strfmt = (char *)cupsArrayNext(strfmts))
216 printf(" %s", strfmt);
217 fputs("\n Original formats:", stdout);
218 for (idfmt = (char *)cupsArrayFirst(idfmts);
219 idfmt;
220 idfmt = (char *)cupsArrayNext(idfmts))
221 printf(" %s", idfmt);
222 putchar('\n');
223 putchar('\n');
224 }
225
226 free_formats(idfmts);
227 free_formats(strfmts);
228 }
229
230 /*
231 * Only allow \\, \n, \r, \t, \", and \### character escapes...
232 */
233
234 for (strfmt = msg->str; *strfmt; strfmt ++)
235 {
236 if (*strfmt == '\\')
237 {
238 strfmt ++;
239
240 if (*strfmt != '\\' && *strfmt != 'n' && *strfmt != 'r' && *strfmt != 't' && *strfmt != '\"' && !isdigit(*strfmt & 255))
241 {
242 if (pass)
243 {
244 pass = 0;
245 puts("FAIL");
246 }
247
248 printf(" Bad escape \\%c in filter message \"%s\"\n"
249 " for \"%s\"\n", strfmt[1],
250 abbreviate(msg->str, strbuf, sizeof(strbuf)),
251 abbreviate(msg->msg, idbuf, sizeof(idbuf)));
252 break;
253 }
254 }
255 }
256 }
257
258 if (pass)
259 {
260 if ((untranslated * 10) >= cupsArrayCount(po) &&
261 strcmp(argv[i], "cups.pot"))
262 {
263 /*
264 * Only allow 10% of messages to be untranslated before we fail...
265 */
266
267 pass = 0;
268 puts("FAIL");
269 printf(" Too many untranslated messages (%d of %d)\n",
270 untranslated, cupsArrayCount(po));
271 }
272 else if (untranslated > 0)
273 printf("PASS (%d of %d untranslated)\n", untranslated,
274 cupsArrayCount(po));
275 else
276 puts("PASS");
277 }
278
279 if (!pass)
280 status = 1;
281
282 _cupsMessageFree(po);
283 }
284
285 return (status);
286 }
287
288
289 /*
290 * 'abbreviate()' - Abbreviate a message string as needed.
291 */
292
293 static char * /* O - Abbreviated string */
294 abbreviate(const char *s, /* I - String to abbreviate */
295 char *buf, /* I - Buffer */
296 int bufsize) /* I - Size of buffer */
297 {
298 char *bufptr; /* Pointer into buffer */
299
300
301 for (bufptr = buf, bufsize -= 4; *s && bufsize > 0; s ++)
302 {
303 if (*s == '\n')
304 {
305 if (bufsize < 2)
306 break;
307
308 *bufptr++ = '\\';
309 *bufptr++ = 'n';
310 bufsize -= 2;
311 }
312 else if (*s == '\t')
313 {
314 if (bufsize < 2)
315 break;
316
317 *bufptr++ = '\\';
318 *bufptr++ = 't';
319 bufsize -= 2;
320 }
321 else if (*s >= 0 && *s < ' ')
322 {
323 if (bufsize < 4)
324 break;
325
326 sprintf(bufptr, "\\%03o", *s);
327 bufptr += 4;
328 bufsize -= 4;
329 }
330 else
331 {
332 *bufptr++ = *s;
333 bufsize --;
334 }
335 }
336
337 if (*s)
338 memcpy(bufptr, "...", 4);
339 else
340 *bufptr = '\0';
341
342 return (buf);
343 }
344
345
346 /*
347 * 'collect_formats()' - Collect all of the format strings in the msgid.
348 */
349
350 static cups_array_t * /* O - Array of format strings */
351 collect_formats(const char *id) /* I - msgid string */
352 {
353 cups_array_t *fmts; /* Array of format strings */
354 char buf[255], /* Format string buffer */
355 *bufptr; /* Pointer into format string */
356
357
358 fmts = cupsArrayNew(NULL, NULL);
359
360 while ((id = strchr(id, '%')) != NULL)
361 {
362 if (id[1] == '%')
363 {
364 /*
365 * Skip %%...
366 */
367
368 id += 2;
369 continue;
370 }
371
372 for (bufptr = buf; *id && bufptr < (buf + sizeof(buf) - 1); id ++)
373 {
374 *bufptr++ = *id;
375
376 if (strchr("CDEFGIOSUXcdeifgopsux", *id))
377 {
378 id ++;
379 break;
380 }
381 }
382
383 *bufptr = '\0';
384 cupsArrayAdd(fmts, strdup(buf));
385 }
386
387 return (fmts);
388 }
389
390
391 /*
392 * 'free_formats()' - Free all of the format strings.
393 */
394
395 static void
396 free_formats(cups_array_t *fmts) /* I - Array of format strings */
397 {
398 char *s; /* Current string */
399
400
401 for (s = (char *)cupsArrayFirst(fmts); s; s = (char *)cupsArrayNext(fmts))
402 free(s);
403
404 cupsArrayDelete(fmts);
405 }