]>
Commit | Line | Data |
---|---|---|
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 | ||
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 | /* | |
6d086e08 | 32 | * 'main()' - Validate .po and .strings files. |
c277e2f8 MS |
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 */ | |
91c84a35 | 46 | int fmtidx; /* Format index */ |
c277e2f8 MS |
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 | { | |
6d086e08 | 56 | puts("Usage: checkpo filename.{po,strings} [... filenameN.{po,strings}]"); |
c277e2f8 MS |
57 | return (1); |
58 | } | |
59 | ||
60 | /* | |
6d086e08 | 61 | * Check every .po or .strings file on the command-line... |
c277e2f8 MS |
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 | ||
6d086e08 | 70 | if (strstr(argv[i], ".strings")) |
84de5e92 | 71 | po = _cupsMessageLoad(argv[i], _CUPS_MESSAGE_STRINGS); |
6d086e08 | 72 | else |
84de5e92 | 73 | po = _cupsMessageLoad(argv[i], _CUPS_MESSAGE_PO); |
6d086e08 MS |
74 | |
75 | if (!po) | |
c277e2f8 MS |
76 | { |
77 | perror(argv[i]); | |
78 | return (1); | |
79 | } | |
80 | ||
0837b7e8 MS |
81 | if (i > 1) |
82 | putchar('\n'); | |
c277e2f8 MS |
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 | { | |
0837b7e8 MS |
98 | /* |
99 | * Make sure filter message prefixes are not translated... | |
100 | */ | |
101 | ||
89550f3f MS |
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)) | |
0837b7e8 MS |
107 | { |
108 | if (pass) | |
109 | { | |
110 | pass = 0; | |
111 | puts("FAIL"); | |
112 | } | |
113 | ||
114 | printf(" Bad prefix on filter message \"%s\"\n", | |
89550f3f | 115 | abbreviate(msg->msg, idbuf, sizeof(idbuf))); |
0837b7e8 MS |
116 | } |
117 | ||
89550f3f MS |
118 | idfmt = msg->msg + strlen(msg->msg) - 1; |
119 | if (idfmt >= msg->msg && *idfmt == '\n') | |
0837b7e8 MS |
120 | { |
121 | if (pass) | |
122 | { | |
123 | pass = 0; | |
124 | puts("FAIL"); | |
125 | } | |
126 | ||
127 | printf(" Trailing newline in message \"%s\"\n", | |
89550f3f | 128 | abbreviate(msg->msg, idbuf, sizeof(idbuf))); |
0837b7e8 MS |
129 | } |
130 | ||
89550f3f | 131 | for (; idfmt >= msg->msg; idfmt --) |
0837b7e8 MS |
132 | if (!isspace(*idfmt & 255)) |
133 | break; | |
134 | ||
89550f3f | 135 | if (idfmt >= msg->msg && *idfmt == '!') |
0837b7e8 MS |
136 | { |
137 | if (pass) | |
138 | { | |
139 | pass = 0; | |
140 | puts("FAIL"); | |
141 | } | |
142 | ||
143 | printf(" Exclamation in message \"%s\"\n", | |
89550f3f | 144 | abbreviate(msg->msg, idbuf, sizeof(idbuf))); |
0837b7e8 MS |
145 | } |
146 | ||
89550f3f | 147 | if ((idfmt - 2) >= msg->msg && !strncmp(idfmt - 2, "...", 3)) |
0837b7e8 MS |
148 | { |
149 | if (pass) | |
150 | { | |
151 | pass = 0; | |
152 | puts("FAIL"); | |
153 | } | |
154 | ||
155 | printf(" Ellipsis in message \"%s\"\n", | |
89550f3f | 156 | abbreviate(msg->msg, idbuf, sizeof(idbuf))); |
0837b7e8 MS |
157 | } |
158 | ||
159 | ||
c277e2f8 MS |
160 | if (!msg->str || !msg->str[0]) |
161 | { | |
162 | untranslated ++; | |
163 | continue; | |
164 | } | |
89550f3f | 165 | else if (strchr(msg->msg, '%')) |
c277e2f8 | 166 | { |
89550f3f | 167 | idfmts = collect_formats(msg->msg); |
c277e2f8 MS |
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; | |
c277e2f8 MS |
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)), | |
89550f3f | 211 | abbreviate(msg->msg, idbuf, sizeof(idbuf))); |
c277e2f8 MS |
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'); | |
f0ab5bff | 223 | putchar('\n'); |
c277e2f8 MS |
224 | } |
225 | ||
226 | free_formats(idfmts); | |
227 | free_formats(strfmts); | |
228 | } | |
229 | ||
8b116e60 MS |
230 | /* |
231 | * Only allow \\, \n, \r, \t, \", and \### character escapes... | |
232 | */ | |
233 | ||
234 | for (strfmt = msg->str; *strfmt; strfmt ++) | |
84de5e92 MS |
235 | { |
236 | if (*strfmt == '\\') | |
237 | { | |
238 | strfmt ++; | |
239 | ||
240 | if (*strfmt != '\\' && *strfmt != 'n' && *strfmt != 'r' && *strfmt != 't' && *strfmt != '\"' && !isdigit(*strfmt & 255)) | |
8b116e60 | 241 | { |
84de5e92 MS |
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; | |
8b116e60 | 253 | } |
84de5e92 MS |
254 | } |
255 | } | |
c277e2f8 MS |
256 | } |
257 | ||
258 | if (pass) | |
259 | { | |
0837b7e8 MS |
260 | if ((untranslated * 10) >= cupsArrayCount(po) && |
261 | strcmp(argv[i], "cups.pot")) | |
c277e2f8 MS |
262 | { |
263 | /* | |
264 | * Only allow 10% of messages to be untranslated before we fail... | |
265 | */ | |
266 | ||
267 | pass = 0; | |
268 | puts("FAIL"); | |
0837b7e8 | 269 | printf(" Too many untranslated messages (%d of %d)\n", |
f0ab5bff | 270 | untranslated, cupsArrayCount(po)); |
c277e2f8 MS |
271 | } |
272 | else if (untranslated > 0) | |
0837b7e8 | 273 | printf("PASS (%d of %d untranslated)\n", untranslated, |
c277e2f8 MS |
274 | cupsArrayCount(po)); |
275 | else | |
0837b7e8 | 276 | puts("PASS"); |
c277e2f8 MS |
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) | |
5a9febac | 338 | memcpy(bufptr, "...", 4); |
c277e2f8 MS |
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 | } |