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