]>
Commit | Line | Data |
---|---|---|
c277e2f8 MS |
1 | /* |
2 | * "$Id: checkpo.c 6926 2007-09-06 14:34:31Z 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 6926 2007-09-06 14:34:31Z mike $". | |
343 | */ |