]> git.ipfire.org Git - thirdparty/cups.git/blame - locale/checkpo.c
Remove all of the Subversion keywords from various source files.
[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 *
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
30static char *abbreviate(const char *s, char *buf, int bufsize);
31static cups_array_t *collect_formats(const char *id);
32static void free_formats(cups_array_t *fmts);
33
34
35/*
36 * 'main()' - Validate .po files.
37 */
38
39int /* O - Exit code */
40main(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
287static char * /* O - Abbreviated string */
288abbreviate(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
344static cups_array_t * /* O - Array of format strings */
345collect_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
389static void
390free_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}