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