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