/*
* Internationalization functions for CUPS drivers.
*
* Copyright 2008 Michael Sweet (mike@easysw.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Contents:
*
* stp_i18n_load() - Load a message catalog for a locale.
* stp_i18n_lookup() - Lookup a string in the message catalog...
* stp_i18n_printf() - Send a formatted string to stderr.
* stpi_unquote() - Unquote characters in strings.
*/
/*
* Include necessary files...
*/
#include "i18n.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* GNU gettext uses a simple .po file format:
*
* # comment
* msgid "id"
* "optional continuation"
* msgstr "str"
* "optional continuation"
*
* Both the id and str strings use standard C quoting for special characters
* like newline and the double quote character.
*/
/*
* Cache structure...
*/
typedef struct stpi_i18n_s
{
struct stpi_i18n_s *next; /* Next catalog */
char locale[6]; /* Locale */
stp_string_list_t *po; /* Message catalog */
} stpi_i18n_t;
/*
* Local functions...
*/
static void stpi_unquote(char *s);
/*
* Local globals...
*/
static stpi_i18n_t *stpi_pocache = NULL;
/*
* 'stp_i18n_load()' - Load a message catalog for a locale.
*/
const stp_string_list_t * /* O - Message catalog */
stp_i18n_load(const char *locale) /* I - Locale name */
{
stp_string_list_t *po; /* Message catalog */
char ll_CC[6], /* Locale ID */
poname[1024]; /* .po filename */
stpi_i18n_t *pocache; /* Current cache entry */
FILE *pofile; /* .po file */
const char *stp_localedir; /* STP_LOCALEDIR environment variable */
char line[4096], /* Line buffer */
*ptr, /* Pointer into buffer */
id[4096], /* Translation ID */
str[4096], /* Translation string */
utf8str[4096]; /* UTF-8 translation string */
int in_id, /* Processing "id" string? */
in_str, /* Processing "str" string? */
linenum; /* Line number in .po file */
iconv_t ic; /* Transcoder to UTF-8 */
size_t inbytes, /* Number of input buffer bytes */
outbytes; /* Number of output buffer bytes */
char *inptr, /* Pointer into input buffer */
*outptr; /* Pointer into output buffer */
int fuzzy = 0; /* Fuzzy translation? */
if (!locale)
return (NULL);
/*
* See if the locale is already loaded...
*/
for (pocache = stpi_pocache; pocache; pocache = pocache->next)
if (!strcmp(locale, pocache->locale))
return (pocache->po);
/*
* Find the message catalog for the given locale...
*/
if ((stp_localedir = getenv("STP_LOCALEDIR")) == NULL)
stp_localedir = PACKAGE_LOCALE_DIR;
strncpy(ll_CC, locale, sizeof(ll_CC) - 1);
ll_CC[sizeof(ll_CC) - 1] = '\0';
if ((ptr = strchr(ll_CC, '.')) != NULL)
*ptr = '\0';
snprintf(poname, sizeof(poname), "%s/%s/gutenprint_%s.po", stp_localedir,
ll_CC, ll_CC);
if (access(poname, 0) && strlen(ll_CC) > 2)
{
ll_CC[2] = '\0';
snprintf(poname, sizeof(poname), "%s/%s/gutenprint_%s.po", stp_localedir,
ll_CC, ll_CC);
}
if ((pofile = fopen(poname, "rb")) == NULL)
return (NULL);
/*
* Read the messages and add them to a string list...
*/
if ((po = stp_string_list_create()) == NULL)
{
fclose(pofile);
return (NULL);
}
linenum = 0;
id[0] = '\0';
str[0] = '\0';
in_id = 0;
in_str = 0;
ic = 0;
while (fgets(line, sizeof(line), pofile))
{
linenum ++;
/*
* Skip blank and comment lines...
*/
if (line[0] == '#')
{
if (line[1] == ':')
fuzzy = 0;
if (strstr(line, "fuzzy"))
fuzzy = 1;
}
if (fuzzy || line[0] == '#' || line[0] == '\n')
continue;
/*
* Strip the trailing quote...
*/
if ((ptr = (char *)strrchr(line, '\"')) == NULL)
{
fprintf(stderr, "DEBUG: Expected quoted string on line %d of %s!\n",
linenum, poname);
break;
}
*ptr = '\0';
/*
* Find start of value...
*/
if ((ptr = strchr(line, '\"')) == NULL)
{
fprintf(stderr, "DEBUG: Expected quoted string on line %d of %s!\n",
linenum, poname);
break;
}
ptr ++;
/*
* Create or add to a message...
*/
if (!strncmp(line, "msgid", 5))
{
in_id = 1;
in_str = 0;
if (id[0] && str[0])
{
stpi_unquote(id);
if (ic)
{
/*
* Convert string to UTF-8...
*/
inbytes = strlen(str);
inptr = str;
outbytes = sizeof(utf8str);
outptr = utf8str;
iconv(ic, &inptr, &inbytes, &outptr, &outbytes);
*outptr = '\0';
/*
* Add it to the string list...
*/
stpi_unquote(utf8str);
stp_string_list_add_string_unsafe(po, id, utf8str);
}
else
{
stpi_unquote(str);
stp_string_list_add_string_unsafe(po, id, str);
}
}
else if (!id[0] && str[0] && !ic)
{
/*
* Look for the character set...
*/
const char *charset = strstr(str, "charset=");
/* Source character set definition */
char fromcode[255], /* Source character set */
*fromptr; /* Pointer into fromcode */
if (charset)
{
/*
* Extract character set and setup a transcode context...
*/
strncpy(fromcode, charset + 8, sizeof(fromcode) - 1);
fromcode[sizeof(fromcode) - 1] = '\0';
for (fromptr = fromcode; *fromptr; fromptr ++)
if (!isalnum(*fromptr & 255) && *fromptr != '-')
break;
*fromptr = '\0';
if (strcasecmp(fromcode, "utf-8"))
{
if ((ic = iconv_open("UTF-8", fromcode)) == (iconv_t)-1)
{
fprintf(stderr,
"DEBUG: Unable to convert character set \"%s\": %s\n",
fromcode, strerror(errno));
ic = 0;
}
}
}
}
strncpy(id, ptr, sizeof(id) - 1);
id[sizeof(id) - 1] = '\0';
str[0] = '\0';
}
else if (!strncmp(line, "msgstr", 6))
{
in_id = 0;
in_str = 1;
strncpy(str, ptr, sizeof(str) - 1);
str[sizeof(str) - 1] = '\0';
}
else if (line[0] == '\"' && in_str)
{
int str_len = strlen(str),
ptr_len = strlen(ptr);
if ((str_len + ptr_len + 1) > sizeof(str))
ptr_len = sizeof(str) - str_len - 1;
if (ptr_len > 0)
{
memcpy(str + str_len, ptr, ptr_len);
str[str_len + ptr_len] = '\0';
}
}
else if (line[0] == '\"' && in_id)
{
int id_len = strlen(id),
ptr_len = strlen(ptr);
if ((id_len + ptr_len + 1) > sizeof(id))
ptr_len = sizeof(id) - id_len - 1;
if (ptr_len > 0)
{
memcpy(id + id_len, ptr, ptr_len);
id[id_len + ptr_len] = '\0';
}
}
else
{
fprintf(stderr, "DEBUG: Unexpected text on line %d of %s!\n",
linenum, poname);
break;
}
}
if (id[0] && str[0])
{
stpi_unquote(id);
if (ic)
{
/*
* Convert string to UTF-8...
*/
inbytes = strlen(str);
inptr = str;
outbytes = sizeof(utf8str);
outptr = utf8str;
iconv(ic, &inptr, &inbytes, &outptr, &outbytes);
*outptr = '\0';
/*
* Add it to the string list...
*/
stpi_unquote(utf8str);
stp_string_list_add_string_unsafe(po, id, utf8str);
}
else
{
stpi_unquote(str);
stp_string_list_add_string_unsafe(po, id, str);
}
}
fclose(pofile);
/*
* Add this to the cache...
*/
if ((pocache = calloc(1, sizeof(stpi_i18n_t))) != NULL)
{
strncpy(pocache->locale, locale, sizeof(pocache->locale) - 1);
pocache->po = po;
pocache->next = stpi_pocache;
stpi_pocache = pocache;
}
if (ic)
iconv_close(ic);
return (po);
}
/*
* 'stp_i18n_lookup()' - Lookup a string in the message catalog...
*/
const char * /* O - Localized message */
stp_i18n_lookup(
const stp_string_list_t *po, /* I - Message catalog */
const char *message) /* I - Message */
{
stp_param_string_t *param; /* Matching message */
if (po && (param = stp_string_list_find(po, message)) != NULL && param->text)
return (param->text);
else
return (message);
}
/*
* 'stp_i18n_printf()' - Send a formatted string to stderr.
*/
void
stp_i18n_printf(
const stp_string_list_t *po, /* I - Message catalog */
const char *message, /* I - Printf-style message */
...) /* I - Additional arguments as needed */
{
va_list ap; /* Argument pointer */
va_start(ap, message);
vfprintf(stderr, stp_i18n_lookup(po, message), ap);
va_end(ap);
}
/*
* 'stpi_unquote()' - Unquote characters in strings.
*/
static void
stpi_unquote(char *s) /* IO - Original string */
{
char *d = s; /* Destination pointer */
while (*s)
{
if (*s == '\\')
{
s ++;
if (isdigit(*s))
{
*d = 0;
while (isdigit(*s))
{
*d = *d * 8 + *s - '0';
s ++;
}
d ++;
}
else
{
if (*s == 'n')
*d ++ = '\n';
else if (*s == 'r')
*d ++ = '\r';
else if (*s == 't')
*d ++ = '\t';
else
*d++ = *s;
s ++;
}
}
else
*d++ = *s++;
}
*d = '\0';
}