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