]> git.ipfire.org Git - thirdparty/cups.git/blame - locale/checkpo.c
Changelog
[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 *
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
26static char *abbreviate(const char *s, char *buf, int bufsize);
27static cups_array_t *collect_formats(const char *id);
28static void free_formats(cups_array_t *fmts);
29
30
31/*
6d086e08 32 * 'main()' - Validate .po and .strings files.
c277e2f8
MS
33 */
34
35int /* O - Exit code */
36main(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
fe0d6115 73 po = _cupsMessageLoad(argv[i], _CUPS_MESSAGE_PO | _CUPS_MESSAGE_EMPTY);
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
c277e2f8
MS
159 if (!msg->str || !msg->str[0])
160 {
161 untranslated ++;
162 continue;
163 }
89550f3f 164 else if (strchr(msg->msg, '%'))
c277e2f8 165 {
89550f3f 166 idfmts = collect_formats(msg->msg);
c277e2f8
MS
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)),
89550f3f 210 abbreviate(msg->msg, idbuf, sizeof(idbuf)));
c277e2f8
MS
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 ++)
84de5e92
MS
234 {
235 if (*strfmt == '\\')
236 {
237 strfmt ++;
238
239 if (*strfmt != '\\' && *strfmt != 'n' && *strfmt != 'r' && *strfmt != 't' && *strfmt != '\"' && !isdigit(*strfmt & 255))
8b116e60 240 {
84de5e92
MS
241 if (pass)
242 {
243 pass = 0;
244 puts("FAIL");
245 }
246
247 printf(" Bad escape \\%c in filter message \"%s\"\n"
248 " for \"%s\"\n", strfmt[1],
249 abbreviate(msg->str, strbuf, sizeof(strbuf)),
250 abbreviate(msg->msg, idbuf, sizeof(idbuf)));
251 break;
8b116e60 252 }
84de5e92
MS
253 }
254 }
c277e2f8
MS
255 }
256
257 if (pass)
258 {
340e5f8f
MS
259 int count = cupsArrayCount(po); /* Total number of messages */
260
261 if (untranslated >= (count / 10) && 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");
340e5f8f 269 printf(" Too many untranslated messages (%d of %d or %.1f%% are translated)\n", count - untranslated, count, 100.0 - 100.0 * untranslated / count);
c277e2f8
MS
270 }
271 else if (untranslated > 0)
340e5f8f 272 printf("PASS (%d of %d or %.1f%% are translated)\n", count - untranslated, count, 100.0 - 100.0 * untranslated / count);
c277e2f8 273 else
0837b7e8 274 puts("PASS");
c277e2f8
MS
275 }
276
277 if (!pass)
278 status = 1;
279
280 _cupsMessageFree(po);
281 }
282
283 return (status);
284}
285
286
287/*
288 * 'abbreviate()' - Abbreviate a message string as needed.
289 */
290
291static char * /* O - Abbreviated string */
292abbreviate(const char *s, /* I - String to abbreviate */
293 char *buf, /* I - Buffer */
294 int bufsize) /* I - Size of buffer */
295{
296 char *bufptr; /* Pointer into buffer */
297
298
299 for (bufptr = buf, bufsize -= 4; *s && bufsize > 0; s ++)
300 {
301 if (*s == '\n')
302 {
303 if (bufsize < 2)
304 break;
305
306 *bufptr++ = '\\';
307 *bufptr++ = 'n';
308 bufsize -= 2;
309 }
310 else if (*s == '\t')
311 {
312 if (bufsize < 2)
313 break;
314
315 *bufptr++ = '\\';
316 *bufptr++ = 't';
317 bufsize -= 2;
318 }
319 else if (*s >= 0 && *s < ' ')
320 {
321 if (bufsize < 4)
322 break;
323
324 sprintf(bufptr, "\\%03o", *s);
325 bufptr += 4;
326 bufsize -= 4;
327 }
328 else
329 {
330 *bufptr++ = *s;
331 bufsize --;
332 }
333 }
334
335 if (*s)
5a9febac 336 memcpy(bufptr, "...", 4);
c277e2f8
MS
337 else
338 *bufptr = '\0';
339
340 return (buf);
341}
342
343
344/*
345 * 'collect_formats()' - Collect all of the format strings in the msgid.
346 */
347
348static cups_array_t * /* O - Array of format strings */
349collect_formats(const char *id) /* I - msgid string */
350{
351 cups_array_t *fmts; /* Array of format strings */
352 char buf[255], /* Format string buffer */
353 *bufptr; /* Pointer into format string */
354
355
356 fmts = cupsArrayNew(NULL, NULL);
357
358 while ((id = strchr(id, '%')) != NULL)
359 {
360 if (id[1] == '%')
361 {
362 /*
363 * Skip %%...
364 */
365
366 id += 2;
367 continue;
368 }
369
370 for (bufptr = buf; *id && bufptr < (buf + sizeof(buf) - 1); id ++)
371 {
372 *bufptr++ = *id;
373
374 if (strchr("CDEFGIOSUXcdeifgopsux", *id))
375 {
376 id ++;
377 break;
378 }
379 }
380
381 *bufptr = '\0';
382 cupsArrayAdd(fmts, strdup(buf));
383 }
384
385 return (fmts);
386}
387
388
389/*
390 * 'free_formats()' - Free all of the format strings.
391 */
392
393static void
394free_formats(cups_array_t *fmts) /* I - Array of format strings */
395{
396 char *s; /* Current string */
397
398
399 for (s = (char *)cupsArrayFirst(fmts); s; s = (char *)cupsArrayNext(fmts))
400 free(s);
401
402 cupsArrayDelete(fmts);
403}