]>
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 * These coded instructions, statements, and computer programs are the
9 * property of Apple Inc. and are protected by Federal copyright
10 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
11 * which should have been included with this file. If this file is
12 * missing or damaged, see the license at "http://www.cups.org/".
16 * checkpo filename.{po,strings} [... filenameN.{po,strings}]
20 * gcc -o checkpo checkpo.c `cups-config --libs`
23 #include <cups/cups-private.h>
30 static char *abbreviate(const char *s
, char *buf
, int bufsize
);
31 static cups_array_t
*collect_formats(const char *id
);
32 static cups_array_t
*cups_load_strings(const char *filename
);
33 static int cups_read_strings(cups_file_t
*fp
, char *buffer
, size_t bufsize
, char **id
, char **str
);
34 static char *cups_scan_strings(char *buffer
);
35 static void free_formats(cups_array_t
*fmts
);
39 * 'main()' - Validate .po and .strings files.
42 int /* O - Exit code */
43 main(int argc
, /* I - Number of command-line args */
44 char *argv
[]) /* I - Command-line arguments */
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 int status
, /* Exit status */
55 pass
, /* Pass/fail status */
56 untranslated
; /* Untranslated messages */
57 char idbuf
[80], /* Abbreviated msgid */
58 strbuf
[80]; /* Abbreviated msgstr */
63 puts("Usage: checkpo filename.{po,strings} [... filenameN.{po,strings}]");
68 * Check every .po or .strings file on the command-line...
71 for (i
= 1, status
= 0; i
< argc
; i
++)
74 * Use the CUPS .po loader to get the message strings...
77 if (strstr(argv
[i
], ".strings"))
78 po
= cups_load_strings(argv
[i
]);
80 po
= _cupsMessageLoad(argv
[i
], 1);
90 printf("%s: ", argv
[i
]);
94 * Scan every message for a % string and then match them up with
95 * the corresponding string in the translation...
101 for (msg
= (_cups_message_t
*)cupsArrayFirst(po
);
103 msg
= (_cups_message_t
*)cupsArrayNext(po
))
106 * Make sure filter message prefixes are not translated...
109 if (!strncmp(msg
->id
, "ALERT:", 6) || !strncmp(msg
->id
, "CRIT:", 5) ||
110 !strncmp(msg
->id
, "DEBUG:", 6) || !strncmp(msg
->id
, "DEBUG2:", 7) ||
111 !strncmp(msg
->id
, "EMERG:", 6) || !strncmp(msg
->id
, "ERROR:", 6) ||
112 !strncmp(msg
->id
, "INFO:", 5) || !strncmp(msg
->id
, "NOTICE:", 7) ||
113 !strncmp(msg
->id
, "WARNING:", 8))
121 printf(" Bad prefix on filter message \"%s\"\n",
122 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
125 idfmt
= msg
->id
+ strlen(msg
->id
) - 1;
126 if (idfmt
>= msg
->id
&& *idfmt
== '\n')
134 printf(" Trailing newline in message \"%s\"\n",
135 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
138 for (; idfmt
>= msg
->id
; idfmt
--)
139 if (!isspace(*idfmt
& 255))
142 if (idfmt
>= msg
->id
&& *idfmt
== '!')
150 printf(" Exclamation in message \"%s\"\n",
151 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
154 if ((idfmt
- 2) >= msg
->id
&& !strncmp(idfmt
- 2, "...", 3))
162 printf(" Ellipsis in message \"%s\"\n",
163 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
167 if (!msg
->str
|| !msg
->str
[0])
172 else if (strchr(msg
->id
, '%'))
174 idfmts
= collect_formats(msg
->id
);
175 strfmts
= collect_formats(msg
->str
);
178 for (strfmt
= (char *)cupsArrayFirst(strfmts
);
180 strfmt
= (char *)cupsArrayNext(strfmts
))
182 if (isdigit(strfmt
[1] & 255) && strfmt
[2] == '$')
185 * Handle positioned format stuff...
188 fmtidx
= strfmt
[1] - '1';
190 if ((idfmt
= (char *)cupsArrayIndex(idfmts
, fmtidx
)) != NULL
)
196 * Compare against the current format...
199 idfmt
= (char *)cupsArrayIndex(idfmts
, fmtidx
);
204 if (!idfmt
|| strcmp(strfmt
, idfmt
))
208 if (cupsArrayCount(strfmts
) != cupsArrayCount(idfmts
) || strfmt
)
216 printf(" Bad translation string \"%s\"\n for \"%s\"\n",
217 abbreviate(msg
->str
, strbuf
, sizeof(strbuf
)),
218 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
219 fputs(" Translation formats:", stdout
);
220 for (strfmt
= (char *)cupsArrayFirst(strfmts
);
222 strfmt
= (char *)cupsArrayNext(strfmts
))
223 printf(" %s", strfmt
);
224 fputs("\n Original formats:", stdout
);
225 for (idfmt
= (char *)cupsArrayFirst(idfmts
);
227 idfmt
= (char *)cupsArrayNext(idfmts
))
228 printf(" %s", idfmt
);
233 free_formats(idfmts
);
234 free_formats(strfmts
);
238 * Only allow \\, \n, \r, \t, \", and \### character escapes...
241 for (strfmt
= msg
->str
; *strfmt
; strfmt
++)
242 if (*strfmt
== '\\' &&
243 strfmt
[1] != '\\' && strfmt
[1] != 'n' && strfmt
[1] != 'r' &&
244 strfmt
[1] != 't' && strfmt
[1] != '\"' && !isdigit(strfmt
[1] & 255))
252 printf(" Bad escape \\%c in filter message \"%s\"\n"
253 " for \"%s\"\n", strfmt
[1],
254 abbreviate(msg
->str
, strbuf
, sizeof(strbuf
)),
255 abbreviate(msg
->id
, idbuf
, sizeof(idbuf
)));
262 if ((untranslated
* 10) >= cupsArrayCount(po
) &&
263 strcmp(argv
[i
], "cups.pot"))
266 * Only allow 10% of messages to be untranslated before we fail...
271 printf(" Too many untranslated messages (%d of %d)\n",
272 untranslated
, cupsArrayCount(po
));
274 else if (untranslated
> 0)
275 printf("PASS (%d of %d untranslated)\n", untranslated
,
284 _cupsMessageFree(po
);
292 * 'abbreviate()' - Abbreviate a message string as needed.
295 static char * /* O - Abbreviated string */
296 abbreviate(const char *s
, /* I - String to abbreviate */
297 char *buf
, /* I - Buffer */
298 int bufsize
) /* I - Size of buffer */
300 char *bufptr
; /* Pointer into buffer */
303 for (bufptr
= buf
, bufsize
-= 4; *s
&& bufsize
> 0; s
++)
323 else if (*s
>= 0 && *s
< ' ')
328 sprintf(bufptr
, "\\%03o", *s
);
340 memcpy(bufptr
, "...", 4);
349 * 'collect_formats()' - Collect all of the format strings in the msgid.
352 static cups_array_t
* /* O - Array of format strings */
353 collect_formats(const char *id
) /* I - msgid string */
355 cups_array_t
*fmts
; /* Array of format strings */
356 char buf
[255], /* Format string buffer */
357 *bufptr
; /* Pointer into format string */
360 fmts
= cupsArrayNew(NULL
, NULL
);
362 while ((id
= strchr(id
, '%')) != NULL
)
374 for (bufptr
= buf
; *id
&& bufptr
< (buf
+ sizeof(buf
) - 1); id
++)
378 if (strchr("CDEFGIOSUXcdeifgopsux", *id
))
386 cupsArrayAdd(fmts
, strdup(buf
));
394 * 'cups_load_strings()' - Load a .strings file into a _cups_msg_t array.
397 static cups_array_t
* /* O - CUPS array of _cups_msg_t values */
398 cups_load_strings(const char *filename
) /* I - File to load */
400 cups_file_t
*fp
; /* .strings file */
401 cups_array_t
*po
; /* Localization array */
402 _cups_message_t
*m
; /* Localization message */
403 char buffer
[8192], /* Message buffer */
405 *str
; /* Translated message */
408 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
411 po
= _cupsMessageNew(NULL
);
413 while (cups_read_strings(fp
, buffer
, sizeof(buffer
), &id
, &str
))
415 if ((m
= malloc(sizeof(_cups_message_t
))) == NULL
)
419 m
->str
= strdup(str
);
446 * 'cups_read_strings()' - Read a pair of strings from a .strings file.
449 static int /* O - 1 on success, 0 on failure */
450 cups_read_strings(cups_file_t
*strings
, /* I - .strings file */
451 char *buffer
, /* I - Line buffer */
452 size_t bufsize
, /* I - Size of line buffer */
453 char **id
, /* O - Pointer to ID string */
454 char **str
) /* O - Pointer to translation string */
456 char *bufptr
; /* Pointer into buffer */
459 while (cupsFileGets(strings
, buffer
, bufsize
))
461 if (buffer
[0] != '\"')
465 bufptr
= cups_scan_strings(buffer
);
472 while (*bufptr
&& *bufptr
!= '\"')
479 bufptr
= cups_scan_strings(bufptr
);
494 * 'cups_scan_strings()' - Scan a quoted string.
497 static char * /* O - End of string */
498 cups_scan_strings(char *buffer
) /* I - Start of string */
500 char *bufptr
; /* Pointer into string */
503 for (bufptr
= buffer
+ 1; *bufptr
&& *bufptr
!= '\"'; bufptr
++)
507 if (bufptr
[1] >= '0' && bufptr
[1] <= '3' &&
508 bufptr
[2] >= '0' && bufptr
[2] <= '7' &&
509 bufptr
[3] >= '0' && bufptr
[3] <= '7')
512 * Decode \nnn octal escape...
515 *bufptr
= (char)(((((bufptr
[1] - '0') << 3) | (bufptr
[2] - '0')) << 3) | (bufptr
[3] - '0'));
516 _cups_strcpy(bufptr
+ 1, bufptr
+ 4);
521 * Decode \C escape...
524 _cups_strcpy(bufptr
, bufptr
+ 1);
527 else if (*bufptr
== 'r')
529 else if (*bufptr
== 't')
540 * 'free_formats()' - Free all of the format strings.
544 free_formats(cups_array_t
*fmts
) /* I - Array of format strings */
546 char *s
; /* Current string */
549 for (s
= (char *)cupsArrayFirst(fmts
); s
; s
= (char *)cupsArrayNext(fmts
))
552 cupsArrayDelete(fmts
);