/*
- * "$Id: checkpo.c 7223 2008-01-16 23:41:19Z mike $"
- *
* Verify that translations in the .po file have the same number and type of
* printf-style format strings.
*
- * Copyright 2007-2009 by Apple Inc.
+ * Copyright 2007-2017 by Apple Inc.
* Copyright 1997-2007 by Easy Software Products, all rights reserved.
*
- * These coded instructions, statements, and computer programs are the
- * property of Apple Inc. and are protected by Federal copyright
- * law. Distribution and use rights are outlined in the file "LICENSE.txt"
- * which should have been included with this file. If this file is
- * file is missing or damaged, see the license at "http://www.cups.org/".
+ * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
*
* Usage:
*
- * checkpo filename.po [... filenameN.po]
+ * checkpo filename.{po,strings} [... filenameN.{po,strings}]
*
* Compile with:
*
* gcc -o checkpo checkpo.c `cups-config --libs`
- *
- * Contents:
- *
- * main() - Validate .po files.
- * abbreviate() - Abbreviate a message string as needed.
- * collect_formats() - Collect all of the format strings in the msgid.
- * free_formats() - Free all of the format strings.
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <cups/string.h>
-#include <cups/i18n.h>
+#include <cups/cups-private.h>
/*
static char *abbreviate(const char *s, char *buf, int bufsize);
static cups_array_t *collect_formats(const char *id);
+static cups_array_t *cups_load_strings(const char *filename);
+static int cups_read_strings(cups_file_t *fp, char *buffer, size_t bufsize, char **id, char **str);
+static char *cups_scan_strings(char *buffer);
static void free_formats(cups_array_t *fmts);
/*
- * 'main()' - Validate .po files.
+ * 'main()' - Validate .po and .strings files.
*/
int /* O - Exit code */
if (argc < 2)
{
- puts("Usage: checkpo filename.po [... filenameN.po]");
+ puts("Usage: checkpo filename.{po,strings} [... filenameN.{po,strings}]");
return (1);
}
/*
- * Check every .po file on the command-line...
+ * Check every .po or .strings file on the command-line...
*/
for (i = 1, status = 0; i < argc; i ++)
* Use the CUPS .po loader to get the message strings...
*/
- if ((po = _cupsMessageLoad(argv[i])) == NULL)
+ if (strstr(argv[i], ".strings"))
+ po = cups_load_strings(argv[i]);
+ else
+ po = _cupsMessageLoad(argv[i], 1);
+
+ if (!po)
{
perror(argv[i]);
return (1);
}
+ if (i > 1)
+ putchar('\n');
printf("%s: ", argv[i]);
fflush(stdout);
msg;
msg = (_cups_message_t *)cupsArrayNext(po))
{
+ /*
+ * Make sure filter message prefixes are not translated...
+ */
+
+ if (!strncmp(msg->id, "ALERT:", 6) || !strncmp(msg->id, "CRIT:", 5) ||
+ !strncmp(msg->id, "DEBUG:", 6) || !strncmp(msg->id, "DEBUG2:", 7) ||
+ !strncmp(msg->id, "EMERG:", 6) || !strncmp(msg->id, "ERROR:", 6) ||
+ !strncmp(msg->id, "INFO:", 5) || !strncmp(msg->id, "NOTICE:", 7) ||
+ !strncmp(msg->id, "WARNING:", 8))
+ {
+ if (pass)
+ {
+ pass = 0;
+ puts("FAIL");
+ }
+
+ printf(" Bad prefix on filter message \"%s\"\n",
+ abbreviate(msg->id, idbuf, sizeof(idbuf)));
+ }
+
+ idfmt = msg->id + strlen(msg->id) - 1;
+ if (idfmt >= msg->id && *idfmt == '\n')
+ {
+ if (pass)
+ {
+ pass = 0;
+ puts("FAIL");
+ }
+
+ printf(" Trailing newline in message \"%s\"\n",
+ abbreviate(msg->id, idbuf, sizeof(idbuf)));
+ }
+
+ for (; idfmt >= msg->id; idfmt --)
+ if (!isspace(*idfmt & 255))
+ break;
+
+ if (idfmt >= msg->id && *idfmt == '!')
+ {
+ if (pass)
+ {
+ pass = 0;
+ puts("FAIL");
+ }
+
+ printf(" Exclamation in message \"%s\"\n",
+ abbreviate(msg->id, idbuf, sizeof(idbuf)));
+ }
+
+ if ((idfmt - 2) >= msg->id && !strncmp(idfmt - 2, "...", 3))
+ {
+ if (pass)
+ {
+ pass = 0;
+ puts("FAIL");
+ }
+
+ printf(" Ellipsis in message \"%s\"\n",
+ abbreviate(msg->id, idbuf, sizeof(idbuf)));
+ }
+
+
if (!msg->str || !msg->str[0])
{
untranslated ++;
free_formats(strfmts);
}
- if ((!strncmp(msg->id, "ALERT:", 6) && strncmp(msg->str, "ALERT:", 6)) ||
- (!strncmp(msg->id, "CRIT:", 5) && strncmp(msg->str, "CRIT:", 5)) ||
- (!strncmp(msg->id, "DEBUG:", 6) && strncmp(msg->str, "DEBUG:", 6)) ||
- (!strncmp(msg->id, "DEBUG2:", 7) && strncmp(msg->str, "DEBUG2:", 7)) ||
- (!strncmp(msg->id, "EMERG:", 6) && strncmp(msg->str, "EMERG:", 6)) ||
- (!strncmp(msg->id, "ERROR:", 6) && strncmp(msg->str, "ERROR:", 6)) ||
- (!strncmp(msg->id, "INFO:", 5) && strncmp(msg->str, "INFO:", 5)) ||
- (!strncmp(msg->id, "NOTICE:", 7) && strncmp(msg->str, "NOTICE:", 7)) ||
- (!strncmp(msg->id, "WARNING:", 8) && strncmp(msg->str, "WARNING:", 8)))
- {
- if (pass)
+ /*
+ * Only allow \\, \n, \r, \t, \", and \### character escapes...
+ */
+
+ for (strfmt = msg->str; *strfmt; strfmt ++)
+ if (*strfmt == '\\' &&
+ strfmt[1] != '\\' && strfmt[1] != 'n' && strfmt[1] != 'r' &&
+ strfmt[1] != 't' && strfmt[1] != '\"' && !isdigit(strfmt[1] & 255))
{
- pass = 0;
- puts("FAIL");
- }
+ if (pass)
+ {
+ pass = 0;
+ puts("FAIL");
+ }
- printf(" Bad prefix on filter message \"%s\"\n for \"%s\"\n\n",
- abbreviate(msg->str, strbuf, sizeof(strbuf)),
- abbreviate(msg->id, idbuf, sizeof(idbuf)));
- }
+ printf(" Bad escape \\%c in filter message \"%s\"\n"
+ " for \"%s\"\n", strfmt[1],
+ abbreviate(msg->str, strbuf, sizeof(strbuf)),
+ abbreviate(msg->id, idbuf, sizeof(idbuf)));
+ break;
+ }
}
if (pass)
{
- if ((untranslated * 10) >= cupsArrayCount(po))
+ if ((untranslated * 10) >= cupsArrayCount(po) &&
+ strcmp(argv[i], "cups.pot"))
{
/*
* Only allow 10% of messages to be untranslated before we fail...
pass = 0;
puts("FAIL");
- printf(" Too many untranslated messages (%d of %d)\n\n",
+ printf(" Too many untranslated messages (%d of %d)\n",
untranslated, cupsArrayCount(po));
}
else if (untranslated > 0)
- printf("PASS (%d of %d untranslated)\n\n", untranslated,
+ printf("PASS (%d of %d untranslated)\n", untranslated,
cupsArrayCount(po));
else
- puts("PASS\n");
+ puts("PASS");
}
if (!pass)
}
if (*s)
- strcpy(bufptr, "...");
+ memcpy(bufptr, "...", 4);
else
*bufptr = '\0';
}
+/*
+ * 'cups_load_strings()' - Load a .strings file into a _cups_msg_t array.
+ */
+
+static cups_array_t * /* O - CUPS array of _cups_msg_t values */
+cups_load_strings(const char *filename) /* I - File to load */
+{
+ cups_file_t *fp; /* .strings file */
+ cups_array_t *po; /* Localization array */
+ _cups_message_t *m; /* Localization message */
+ char buffer[8192], /* Message buffer */
+ *id, /* ID string */
+ *str; /* Translated message */
+
+
+ if ((fp = cupsFileOpen(filename, "r")) == NULL)
+ return (NULL);
+
+ po = _cupsMessageNew(NULL);
+
+ while (cups_read_strings(fp, buffer, sizeof(buffer), &id, &str))
+ {
+ if ((m = malloc(sizeof(_cups_message_t))) == NULL)
+ break;
+
+ m->id = strdup(id);
+ m->str = strdup(str);
+
+ if (m->id && m->str)
+ cupsArrayAdd(po, m);
+ else
+ {
+ if (m->id)
+ free(m->id);
+
+ if (m->str)
+ free(m->str);
+
+ free(m);
+
+ cupsArrayDelete(po);
+ po = NULL;
+ break;
+ }
+ }
+
+ cupsFileClose(fp);
+
+ return (po);
+}
+
+
+/*
+ * 'cups_read_strings()' - Read a pair of strings from a .strings file.
+ */
+
+static int /* O - 1 on success, 0 on failure */
+cups_read_strings(cups_file_t *strings, /* I - .strings file */
+ char *buffer, /* I - Line buffer */
+ size_t bufsize, /* I - Size of line buffer */
+ char **id, /* O - Pointer to ID string */
+ char **str) /* O - Pointer to translation string */
+{
+ char *bufptr; /* Pointer into buffer */
+
+
+ while (cupsFileGets(strings, buffer, bufsize))
+ {
+ if (buffer[0] != '\"')
+ continue;
+
+ *id = buffer + 1;
+ bufptr = cups_scan_strings(buffer);
+
+ if (*bufptr != '\"')
+ continue;
+
+ *bufptr++ = '\0';
+
+ while (*bufptr && *bufptr != '\"')
+ bufptr ++;
+
+ if (!*bufptr)
+ continue;
+
+ *str = bufptr + 1;
+ bufptr = cups_scan_strings(bufptr);
+
+ if (*bufptr != '\"')
+ continue;
+
+ *bufptr = '\0';
+
+ return (1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * 'cups_scan_strings()' - Scan a quoted string.
+ */
+
+static char * /* O - End of string */
+cups_scan_strings(char *buffer) /* I - Start of string */
+{
+ char *bufptr; /* Pointer into string */
+
+
+ for (bufptr = buffer + 1; *bufptr && *bufptr != '\"'; bufptr ++)
+ {
+ if (*bufptr == '\\')
+ {
+ if (bufptr[1] >= '0' && bufptr[1] <= '3' &&
+ bufptr[2] >= '0' && bufptr[2] <= '7' &&
+ bufptr[3] >= '0' && bufptr[3] <= '7')
+ {
+ /*
+ * Decode \nnn octal escape...
+ */
+
+ *bufptr = (char)(((((bufptr[1] - '0') << 3) | (bufptr[2] - '0')) << 3) | (bufptr[3] - '0'));
+ _cups_strcpy(bufptr + 1, bufptr + 4);
+ }
+ else
+ {
+ /*
+ * Decode \C escape...
+ */
+
+ _cups_strcpy(bufptr, bufptr + 1);
+ if (*bufptr == 'n')
+ *bufptr = '\n';
+ else if (*bufptr == 'r')
+ *bufptr = '\r';
+ else if (*bufptr == 't')
+ *bufptr = '\t';
+ }
+ }
+ }
+
+ return (bufptr);
+}
+
+
/*
* 'free_formats()' - Free all of the format strings.
*/
cupsArrayDelete(fmts);
}
-
-
-/*
- * End of "$Id: checkpo.c 7223 2008-01-16 23:41:19Z mike $".
- */