/*-
* Copyright (c) 2000, 2001 Citrus Project,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Citrus: xpg4dl/FreeBSD/lib/libintl/gettext.c,v 1.31 2001/09/27 15:18:45 yamt Exp $
*/
/* GNU gettext added a hack to add some context to messages. If a message is
* used in multiple locations, it needs some amount of context to make the
* translation clear to translators. GNU gettext, rather than modifying the
* message format, concatenates the context, \004 and the message id.
*/
#define MSGCTXT_ID_SEPARATOR '\004'
const char *
npgettext(const char *msgctxt, const char *msgid1, const char *msgid2,
unsigned long int n)
{
return pgettext_impl(NULL, msgctxt, msgid1, msgid2, n, LC_MESSAGES);
}
const char *
dnpgettext(const char *domainname, const char *msgctxt, const char *msgid1,
const char *msgid2, unsigned long int n)
{
return pgettext_impl(domainname, msgctxt, msgid1, msgid2, n, LC_MESSAGES);
}
const char *
dcnpgettext(const char *domainname, const char *msgctxt, const char *msgid1,
const char *msgid2, unsigned long int n, int category)
{
return pgettext_impl(domainname, msgctxt, msgid1, msgid2, n, category);
}
static const char *
pgettext_impl(const char *domainname, const char *msgctxt, const char *msgid1,
const char *msgid2, unsigned long int n, int category)
{
char *msgctxt_id;
char *translation;
char *p;
if ((msgctxt_id = concatenate_ctxt_id(msgctxt, msgid1)) == NULL)
return msgid1;
translation = dcngettext(domainname, msgctxt_id,
msgid2, n, category);
if (translation == msgctxt_id) {
free(msgctxt_id);
return msgid1;
}
free(msgctxt_id);
p = strchr(translation, '\004');
if (p)
return p + 1;
return translation;
}
/*
* dcngettext() -
* lookup internationalized message on database locale/category/domainname
* (like ja_JP.eucJP/LC_MESSAGES/domainname).
* if n equals to 1, internationalized message will be looked up for msgid1.
* otherwise, message will be looked up for msgid2.
* if the lookup fails, the function will return msgid1 or msgid2 as is.
*
* Even though the return type is "char *", caller should not rewrite the
* region pointed to by the return value (should be "const char *", but can't
* change it for compatibility with other implementations).
*
* by default (if domainname == NULL), domainname is taken from the value set
* by textdomain(). usually name of the application (like "ls") is used as
* domainname. category is usually LC_MESSAGES.
*
* the code reads in *.mo files generated by GNU gettext. *.mo is a host-
* endian encoded file. both endians are supported here, as the files are in
* /usr/share/locale! (or we should move those files into /usr/libdata)
*/
strlcpy(buf, lname, sizeof(buf));
m = strrchr(buf, '@');
if (m)
*m++ = '\0';
c = strrchr(buf, '.');
if (c)
*c++ = '\0';
t = strrchr(buf, '_');
if (t)
*t++ = '\0';
l = buf;
if (strlen(l) == 0)
goto fail;
if (c && !t)
goto fail;
/* allocate [ot]table, and convert to normal pointer representation. */
l = sizeof(struct moentry_h) * mohandle->mo.mo_nstring;
mohandle->mo.mo_otable = (struct moentry_h *)malloc(l);
if (!mohandle->mo.mo_otable) {
unmapit(db);
goto fail;
}
mohandle->mo.mo_ttable = (struct moentry_h *)malloc(l);
if (!mohandle->mo.mo_ttable) {
unmapit(db);
goto fail;
}
p = mohandle->mo.mo_otable;
for (i = 0; i < mohandle->mo.mo_nstring; i++) {
p[i].len = flip(otable[i].len, magic);
p[i].off = base + flip(otable[i].off, magic);
if (!validate(p[i].off, mohandle) ||
!validate(p[i].off + p[i].len + 1, mohandle)) {
unmapit(db);
goto fail;
}
}
p = mohandle->mo.mo_ttable;
for (i = 0; i < mohandle->mo.mo_nstring; i++) {
p[i].len = flip(ttable[i].len, magic);
p[i].off = base + flip(ttable[i].off, magic);
if (!validate(p[i].off, mohandle) ||
!validate(p[i].off + p[i].len + 1, mohandle)) {
unmapit(db);
goto fail;
}
}
/* allocate htable, and convert it to the host order. */
if (mohandle->mo.mo_hsize > 2) {
l = sizeof(uint32_t) * mohandle->mo.mo_hsize;
mohandle->mo.mo_htable = (uint32_t *)malloc(l);
if (!mohandle->mo.mo_htable) {
unmapit(db);
goto fail;
}
/* LINTED: ignore the alignment problem. */
htable = (const uint32_t *)(base+flip(mo->mo_hoffset, magic));
for (i=0; i < mohandle->mo.mo_hsize; i++) {
mohandle->mo.mo_htable[i] = flip(htable[i], magic);
if (mohandle->mo.mo_htable[i] >=
mohandle->mo.mo_nstring+1) {
/* illegal string number. */
unmapit(db);
goto fail;
}
}
}
/* grab MIME-header and charset field */
mohandle->mo.mo_header = lookup("", db, &headerlen);
if (mohandle->mo.mo_header)
value = strstr(mohandle->mo.mo_header, "charset=");
else
value = NULL;
if (value != NULL) {
mohandle->mo.mo_charset = strdup(value + 8);
if (!mohandle->mo.mo_charset)
goto fail;
char *newline = strchr(mohandle->mo.mo_charset, '\n');
if (newline != NULL)
*newline = '\0';
}
if (!mohandle->mo.mo_header ||
_gettext_parse_plural(&mohandle->mo.mo_plural,
&mohandle->mo.mo_nplurals,
mohandle->mo.mo_header, headerlen))
mohandle->mo.mo_plural = NULL;
/*
* XXX check charset, reject it if we are unable to support the charset
* with the current locale.
* for example, if we are using euc-jp locale and we are looking at
* *.mo file encoded by euc-kr (charset=euc-kr), we should reject
* the *.mo file as we cannot support it.
*/
/* system dependent string support */
if ((mohandle->mo.mo_flags & MO_F_SYSDEP) != 0) {
if (setup_sysdep_stuffs(mo, mohandle, base)) {
unmapit(db);
goto fail;
}
}
top = 0;
bottom = mohandle->mo.mo_nstring;
omiddle = -1;
/* CONSTCOND */
while (1) {
if (top > bottom)
break;
middle = (top + bottom) / 2;
/* avoid possible infinite loop, when the data is not sorted */
if (omiddle == middle)
break;
if ((size_t)middle >= mohandle->mo.mo_nstring)
break;
n = strcmp(msgid, mohandle->mo.mo_otable[middle].off);
if (n == 0) {
if (rlen)
*rlen = mohandle->mo.mo_ttable[middle].len;
return (const char *)mohandle->mo.mo_ttable[middle].off;
}
else if (n < 0)
bottom = middle;
else
top = middle;
omiddle = middle;
}
/*
* 1. see LANGUAGE variable first.
*
* LANGUAGE is a GNU extension.
* It's a colon separated list of locale names.
*/
lang = getenv("LANGUAGE");
if (lang)
return lang;
/*
* 2. if LANGUAGE isn't set, see LC_ALL, LC_xxx, LANG.
*
* It's essentially setlocale(LC_xxx, NULL).
*/
lang = getenv("LC_ALL");
if (!lang)
lang = getenv(category_name);
if (!lang)
lang = getenv("LANG");
if (!lang)
return 0; /* error */
return split_locale(lang);
}
static const char *
get_indexed_string(const char *str, size_t len, unsigned long idx)
{
while (idx > 0) {
if (len <= 1)
return str;
if (*str == '\0')
idx--;
if (len > 0) {
str++;
len--;
}
}
return str;
}
if (!domainname)
domainname = __current_domainname;
cname = lookup_category(category);
if (!domainname || !cname)
goto fail;
lpath = get_lang_env(cname);
if (!lpath)
goto fail;
for (db = __bindings; db; db = db->next)
if (strcmp(db->domainname, domainname) == 0)
break;
if (!db) {
if (!bindtextdomain(domainname, _PATH_TEXTDOMAIN))
goto fail;
db = __bindings;
}
/* resolve relative path */
/* XXX not necessary? */
if (db->path[0] != '/') {
char buf[PATH_MAX];
if (getcwd(buf, sizeof(buf)) == 0)
goto fail;
if (strlcat(buf, "/", sizeof(buf)) >= sizeof(buf))
goto fail;
if (strlcat(buf, db->path, sizeof(buf)) >= sizeof(buf))
goto fail;
strlcpy(db->path, buf, sizeof(db->path));
}
/* don't bother looking it up if the values are the same */
if (odomainname && strcmp(domainname, odomainname) == 0 &&
ocname && strcmp(cname, ocname) == 0 && strcmp(lpath, olpath) == 0 &&
db->mohandle.mo.mo_magic)
goto found;
/* try to find appropriate file, from $LANGUAGE */
if (lookup_mofile(path, sizeof(path), db->path, lpath, cname,
domainname, db) == NULL)
goto fail;
v = lookup(msgid, db, &len);
if (v) {
if (db->mohandle.mo.mo_plural)
v = get_indexed_string(v, len, plural_index);
/*
* convert the translated message's encoding.
*
* special case:
* a result of gettext("") shouldn't need any conversion.
*/
if (msgid[0])
v = __gettext_iconv(v, db);
/*
* Given the amount of printf-format security issues, it may
* be a good idea to validate if the original msgid and the
* translated message format string carry the same printf-like
* format identifiers.
*/