#pragma prototyped
#include "lclib.h"
#include <cdt.h>
#include <error.h>
#include <mc.h>
#include <nl_types.h>
#ifndef DEBUG_trace
#define DEBUG_trace 0
#endif
#define NOCAT ((nl_catd)-1)
#define GAP 100
typedef struct
{
Dtlink_t link;
Dt_t* messages;
nl_catd cat;
int debug;
const char* locale;
const char* nlspath;
char name[1];
} Catalog_t;
typedef struct
{
Dtlink_t link;
Catalog_t* cat;
int set;
int seq;
char text[1];
} Message_t;
typedef struct
{
Sfio_t* sp;
int off;
} Temp_t;
typedef struct
{
Dtdisc_t message_disc;
Dtdisc_t catalog_disc;
Dt_t* catalogs;
Sfio_t* tmp;
int error;
char null[1];
} State_t;
static State_t state =
{
{ offsetof(Message_t, text), 0, 0 },
{ offsetof(Catalog_t, name), 0, 0 },
};
static int
tempget(Sfio_t* sp)
{
if (sfstrtell(sp) > sfstrsize(sp) / 2)
sfstrseek(sp, 0, SEEK_SET);
return sfstrtell(sp);
}
static char*
tempuse(Sfio_t* sp, int off)
{
sfputc(sp, 0);
return sfstrbase(sp) + off;
}
static int
entry(Dt_t* dict, int set, int seq, const char* msg)
{
Message_t* mp;
if (!(mp = newof(0, Message_t, 1, strlen(msg))))
return 0;
strcpy(mp->text, msg);
mp->set = set;
mp->seq = seq;
if (!dtinsert(dict, mp))
{
free(mp);
return 0;
}
#if DEBUG_trace > 1
sfprintf(sfstderr, "AHA#%d:%s set %d seq %d msg `%s'\n", __LINE__, __FILE__, set, seq, msg);
#endif
return 1;
}
static nl_catd
find(const char* locale, const char* catalog)
{
char* o;
nl_catd d;
char path[PATH_MAX];
if (!mcfind(locale, catalog, LC_MESSAGES, 0, path, sizeof(path)) || (d = catopen(path, NL_CAT_LOCALE)) == NOCAT)
{
if (locale == (const char*)lc_categories[AST_LC_MESSAGES].prev)
o = 0;
else if (o = setlocale(LC_MESSAGES, NiL))
{
ast.locale.set |= AST_LC_internal;
setlocale(LC_MESSAGES, locale);
}
d = catopen(catalog, NL_CAT_LOCALE);
if (o)
{
setlocale(LC_MESSAGES, o);
ast.locale.set &= ~AST_LC_internal;
}
}
return d;
}
static Catalog_t*
init(register char* s)
{
register Catalog_t* cp;
register char* u;
register int n;
register int m;
register int set;
nl_catd d;
static const int sets[] = { AST_MESSAGE_SET, 1 };
if (!(cp = newof(0, Catalog_t, 1, strlen(s))))
return 0;
strcpy(cp->name, s);
if (!dtinsert(state.catalogs, cp))
{
free(cp);
return 0;
}
cp->cat = NOCAT;
if ((d = find("C", s)) != NOCAT)
{
if (cp->messages = dtopen(&state.message_disc, Dtset))
{
n = m = 0;
for (;;)
{
n++;
if (((s = catgets(d, set = AST_MESSAGE_SET, n, state.null)) && *s || (s = catgets(d, set = 1, n, state.null)) && *s) && entry(cp->messages, set, n, s))
m = n;
else if ((n - m) > GAP)
break;
}
if (!m)
{
dtclose(cp->messages);
cp->messages = 0;
}
}
catclose(d);
}
return cp;
}
static Message_t*
match(const char* cat, const char* msg)
{
register char* s;
register char* t;
Catalog_t* cp;
Message_t* mp;
size_t n;
char buf[1024];
s = (char*)cat;
for (;;)
{
if (t = strchr(s, ':'))
{
if (s == (char*)cat)
{
if ((n = strlen(s)) >= sizeof(buf))
n = sizeof(buf) - 1;
s = (char*)memcpy(buf, s, n);
s[n] = 0;
t = strchr(s, ':');
}
*t = 0;
}
if (*s && ((cp = (Catalog_t*)dtmatch(state.catalogs, s)) || (cp = init(s))) && cp->messages && (mp = (Message_t*)dtmatch(cp->messages, msg)))
{
mp->cat = cp;
return mp;
}
if (!t)
break;
s = t + 1;
}
return 0;
}
char*
translate(const char* loc, const char* cmd, const char* cat, const char* msg)
{
register char* r;
char* t;
int p;
int oerrno;
Catalog_t* cp;
Message_t* mp;
static uint32_t serial;
static char* nlspath;
oerrno = errno;
r = (char*)msg;
if (!cmd && !cat)
goto done;
if (cmd && (t = strrchr(cmd, '/')))
cmd = (const char*)(t + 1);
if (!state.catalogs)
{
if (state.error)
goto done;
if (!(state.tmp = sfstropen()))
{
state.error = 1;
goto done;
}
if (!(state.catalogs = dtopen(&state.catalog_disc, Dtset)))
{
sfclose(state.tmp);
state.error = 1;
goto done;
}
}
if ((!cmd || !(mp = match(cmd, msg))) &&
(!cat || !(mp = match(cat, msg))) &&
(!error_info.catalog || !(mp = match(error_info.catalog, msg))) &&
(!ast.id || !(mp = match(ast.id, msg))) ||
!(cp = mp->cat))
{
#if DEBUG_trace > 1
sfprintf(sfstderr, "AHA#%d:%s cmd %s cat %s:%s id %s msg `%s'\n", __LINE__, __FILE__, cmd, cat, error_info.catalog, ast.id, msg);
#endif
cp = 0;
goto done;
}
#if DEBUG_trace
sfprintf(sfstderr, "AHA#%d:%s cp->locale `%s' %p loc `%s' %p\n", __LINE__, __FILE__, cp->locale, cp->locale, loc, loc);
#endif
if (serial != ast.env_serial)
{
serial = ast.env_serial;
nlspath = getenv("NLSPATH");
}
if (cp->locale != loc || cp->nlspath != nlspath)
{
cp->locale = loc;
cp->nlspath = nlspath;
if (cp->cat != NOCAT)
catclose(cp->cat);
if ((cp->cat = find(cp->locale, cp->name)) == NOCAT)
cp->debug = streq(cp->locale, "debug");
else
cp->debug = 0;
#if DEBUG_trace
sfprintf(sfstderr, "AHA#%d:%s cp->cat %p cp->debug %d NOCAT %p\n", __LINE__, __FILE__, cp->cat, cp->debug, NOCAT);
#endif
}
if (cp->cat == NOCAT)
{
if (cp->debug)
{
p = tempget(state.tmp);
sfprintf(state.tmp, "(%s,%d,%d)", cp->name, mp->set, mp->seq);
r = tempuse(state.tmp, p);
}
else if (ast.locale.set & AST_LC_debug)
{
p = tempget(state.tmp);
sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r);
r = tempuse(state.tmp, p);
}
}
else
{
r = catgets(cp->cat, mp->set, mp->seq, msg);
if (r != (char*)msg)
{
if (streq(r, (char*)msg))
r = (char*)msg;
else if (strcmp(fmtfmt(r), fmtfmt(msg)))
{
sfprintf(sfstderr, "locale %s catalog %s message %d.%d \"%s\" does not match \"%s\"\n", cp->locale, cp->name, mp->set, mp->seq, r, msg);
r = (char*)msg;
}
}
if (ast.locale.set & AST_LC_debug)
{
p = tempget(state.tmp);
sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r);
r = tempuse(state.tmp, p);
}
}
if (ast.locale.set & AST_LC_translate)
sfprintf(sfstderr, "translate locale=%s catalog=%s set=%d seq=%d \"%s\" => \"%s\"\n", cp->locale, cp->name, mp->set, mp->seq, msg, r == (char*)msg ? "NOPE" : r);
done:
if (r == (char*)msg && (!cp && streq(loc, "debug") || cp && cp->debug))
{
p = tempget(state.tmp);
sfprintf(state.tmp, "(%s,%s,%s,%s)", loc, cmd, cat, r);
r = tempuse(state.tmp, p);
}
errno = oerrno;
return r;
}