]>
git.ipfire.org Git - thirdparty/cups.git/blob - locale/checkpo.c
2 * Verify that translations in the .po file have the same number and type of
3 * printf-style format strings.
5 * Copyright 2007-2017 by Apple Inc.
6 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
12 * checkpo filename.{po,strings} [... filenameN.{po,strings}]
16 * gcc -o checkpo checkpo.c `cups-config --libs`
19 #include <cups/cups-private.h>
26 static char *abbreviate(const char *s
, char *buf
, int bufsize
);
27 static cups_array_t
*collect_formats(const char *id
);
28 static cups_array_t
*cups_load_strings(const char *filename
);
29 static int cups_read_strings(cups_file_t
*fp
, char *buffer
, size_t bufsize
, char **id
, char **str
);
30 static char *cups_scan_strings(char *buffer
);
31 static void free_formats(cups_array_t
*fmts
);
35 * 'main()' - Validate .po and .strings files.
38 int /* O - Exit code */
39 main(int argc
, /* I - Number of command-line args */
40 char *argv
[]) /* I - Command-line arguments */
42 int i
; /* Looping var */
43 cups_array_t
*po
; /* .po file */
44 _cups_message_t
*msg
; /* Current message */
45 cups_array_t
*idfmts
, /* Format strings in msgid */
46 *strfmts
; /* Format strings in msgstr */
47 char *idfmt
, /* Current msgid format string */
48 *strfmt
; /* Current msgstr format string */
49 int fmtidx
; /* Format index */
50 int status
, /* Exit status */
51 pass
, /* Pass/fail status */
52 untranslated
; /* Untranslated messages */
53 char idbuf
[80], /* Abbreviated msgid */
54 strbuf
[80]; /* Abbreviated msgstr */
59 puts("Usage: checkpo filename.{po,strings} [... filenameN.{po,strings}]");
64 * Check every .po or .strings file on the command-line...
67 for (i
= 1, status
= 0; i
< argc
; i
++)
70 * Use the CUPS .po loader to get the message strings...
73 if (strstr(argv
[i
], ".strings"))
74 po
= cups_load_strings(argv
[i
]);
76 po
= _cupsMessageLoad(argv
[i
], 1);
86 printf("%s: ", argv
[i
]);
90 * Scan every message for a % string and then match them up with
91 * the corresponding string in the translation...
97 for (msg
= (_cups_message_t
*)cupsArrayFirst(po
);
99 msg
= (_cups_message_t
*)cupsArrayNext(po
))
102 * Make sure filter message prefixes are not translated...
105 if (!strncmp(msg
->id
, "ALERT:", 6) || !strncmp(msg
->id
, "CRIT:", 5) ||
106 !strncmp(msg
->id
, "DEBUG:", 6) || !strncmp(msg
->id
, "DEBUG2:", 7) ||
107 !strncmp(msg
->id
, "EMERG:", 6) || !strncmp(msg
->id
, "ERROR:", 6) ||
108 !strncmp(msg
->id
, "INFO:", 5) || !strncmp(msg
->id
, "NOTICE:", 7) ||
109 !strncmp(msg
->id
, "WARNING:", 8))
117 printf(" Bad prefix on filter message \"%s\"\n",
118 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
121 idfmt
= msg
->id
+ strlen(msg
->id
) - 1;
122 if (idfmt
>= msg
->id
&& *idfmt
== '\n')
130 printf(" Trailing newline in message \"%s\"\n",
131 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
134 for (; idfmt
>= msg
->id
; idfmt
--)
135 if (!isspace(*idfmt
& 255))
138 if (idfmt
>= msg
->id
&& *idfmt
== '!')
146 printf(" Exclamation in message \"%s\"\n",
147 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
150 if ((idfmt
- 2) >= msg
->id
&& !strncmp(idfmt
- 2, "...", 3))
158 printf(" Ellipsis in message \"%s\"\n",
159 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
163 if (!msg
->str
|| !msg
->str
[0])
168 else if (strchr(msg
->id
, '%'))
170 idfmts
= collect_formats(msg
->id
);
171 strfmts
= collect_formats(msg
->str
);
174 for (strfmt
= (char *)cupsArrayFirst(strfmts
);
176 strfmt
= (char *)cupsArrayNext(strfmts
))
178 if (isdigit(strfmt
[1] & 255) && strfmt
[2] == '$')
181 * Handle positioned format stuff...
184 fmtidx
= strfmt
[1] - '1';
186 if ((idfmt
= (char *)cupsArrayIndex(idfmts
, fmtidx
)) != NULL
)
192 * Compare against the current format...
195 idfmt
= (char *)cupsArrayIndex(idfmts
, fmtidx
);
200 if (!idfmt
|| strcmp(strfmt
, idfmt
))
204 if (cupsArrayCount(strfmts
) != cupsArrayCount(idfmts
) || strfmt
)
212 printf(" Bad translation string \"%s\"\n for \"%s\"\n",
213 abbreviate(msg
->str
, strbuf
, sizeof(strbuf
)),
214 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
215 fputs(" Translation formats:", stdout
);
216 for (strfmt
= (char *)cupsArrayFirst(strfmts
);
218 strfmt
= (char *)cupsArrayNext(strfmts
))
219 printf(" %s", strfmt
);
220 fputs("\n Original formats:", stdout
);
221 for (idfmt
= (char *)cupsArrayFirst(idfmts
);
223 idfmt
= (char *)cupsArrayNext(idfmts
))
224 printf(" %s", idfmt
);
229 free_formats(idfmts
);
230 free_formats(strfmts
);
234 * Only allow \\, \n, \r, \t, \", and \### character escapes...
237 for (strfmt
= msg
->str
; *strfmt
; strfmt
++)
238 if (*strfmt
== '\\' &&
239 strfmt
[1] != '\\' && strfmt
[1] != 'n' && strfmt
[1] != 'r' &&
240 strfmt
[1] != 't' && strfmt
[1] != '\"' && !isdigit(strfmt
[1] & 255))
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
->id
, idbuf
, sizeof(idbuf
)));
258 if ((untranslated
* 10) >= cupsArrayCount(po
) &&
259 strcmp(argv
[i
], "cups.pot"))
262 * Only allow 10% of messages to be untranslated before we fail...
267 printf(" Too many untranslated messages (%d of %d)\n",
268 untranslated
, cupsArrayCount(po
));
270 else if (untranslated
> 0)
271 printf("PASS (%d of %d untranslated)\n", untranslated
,
280 _cupsMessageFree(po
);
288 * 'abbreviate()' - Abbreviate a message string as needed.
291 static char * /* O - Abbreviated string */
292 abbreviate(const char *s
, /* I - String to abbreviate */
293 char *buf
, /* I - Buffer */
294 int bufsize
) /* I - Size of buffer */
296 char *bufptr
; /* Pointer into buffer */
299 for (bufptr
= buf
, bufsize
-= 4; *s
&& bufsize
> 0; s
++)
319 else if (*s
>= 0 && *s
< ' ')
324 sprintf(bufptr
, "\\%03o", *s
);
336 memcpy(bufptr
, "...", 4);
345 * 'collect_formats()' - Collect all of the format strings in the msgid.
348 static cups_array_t
* /* O - Array of format strings */
349 collect_formats(const char *id
) /* I - msgid string */
351 cups_array_t
*fmts
; /* Array of format strings */
352 char buf
[255], /* Format string buffer */
353 *bufptr
; /* Pointer into format string */
356 fmts
= cupsArrayNew(NULL
, NULL
);
358 while ((id
= strchr(id
, '%')) != NULL
)
370 for (bufptr
= buf
; *id
&& bufptr
< (buf
+ sizeof(buf
) - 1); id
++)
374 if (strchr("CDEFGIOSUXcdeifgopsux", *id
))
382 cupsArrayAdd(fmts
, strdup(buf
));
390 * 'cups_load_strings()' - Load a .strings file into a _cups_msg_t array.
393 static cups_array_t
* /* O - CUPS array of _cups_msg_t values */
394 cups_load_strings(const char *filename
) /* I - File to load */
396 cups_file_t
*fp
; /* .strings file */
397 cups_array_t
*po
; /* Localization array */
398 _cups_message_t
*m
; /* Localization message */
399 char buffer
[8192], /* Message buffer */
401 *str
; /* Translated message */
404 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
407 po
= _cupsMessageNew(NULL
);
409 while (cups_read_strings(fp
, buffer
, sizeof(buffer
), &id
, &str
))
411 if ((m
= malloc(sizeof(_cups_message_t
))) == NULL
)
415 m
->str
= strdup(str
);
442 * 'cups_read_strings()' - Read a pair of strings from a .strings file.
445 static int /* O - 1 on success, 0 on failure */
446 cups_read_strings(cups_file_t
*strings
, /* I - .strings file */
447 char *buffer
, /* I - Line buffer */
448 size_t bufsize
, /* I - Size of line buffer */
449 char **id
, /* O - Pointer to ID string */
450 char **str
) /* O - Pointer to translation string */
452 char *bufptr
; /* Pointer into buffer */
455 while (cupsFileGets(strings
, buffer
, bufsize
))
457 if (buffer
[0] != '\"')
461 bufptr
= cups_scan_strings(buffer
);
468 while (*bufptr
&& *bufptr
!= '\"')
475 bufptr
= cups_scan_strings(bufptr
);
490 * 'cups_scan_strings()' - Scan a quoted string.
493 static char * /* O - End of string */
494 cups_scan_strings(char *buffer
) /* I - Start of string */
496 char *bufptr
; /* Pointer into string */
499 for (bufptr
= buffer
+ 1; *bufptr
&& *bufptr
!= '\"'; bufptr
++)
503 if (bufptr
[1] >= '0' && bufptr
[1] <= '3' &&
504 bufptr
[2] >= '0' && bufptr
[2] <= '7' &&
505 bufptr
[3] >= '0' && bufptr
[3] <= '7')
508 * Decode \nnn octal escape...
511 *bufptr
= (char)(((((bufptr
[1] - '0') << 3) | (bufptr
[2] - '0')) << 3) | (bufptr
[3] - '0'));
512 _cups_strcpy(bufptr
+ 1, bufptr
+ 4);
517 * Decode \C escape...
520 _cups_strcpy(bufptr
, bufptr
+ 1);
523 else if (*bufptr
== 'r')
525 else if (*bufptr
== 't')
536 * 'free_formats()' - Free all of the format strings.
540 free_formats(cups_array_t
*fmts
) /* I - Array of format strings */
542 char *s
; /* Current string */
545 for (s
= (char *)cupsArrayFirst(fmts
); s
; s
= (char *)cupsArrayNext(fmts
))
548 cupsArrayDelete(fmts
);