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