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