/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1985-2008 AT&T Intellectual Property * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.opensource.org/licenses/cpl1.0.txt * * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * Glenn Fowler * * David Korn * * Phong Vo * * * ***********************************************************************/ #pragma prototyped /* * AT&T Research and SCO * ast i18n message translation */ #include "lclib.h" #include #include #include #include #ifndef DEBUG_trace #define DEBUG_trace 0 #endif #define NOCAT ((nl_catd)-1) #define GAP 100 typedef struct { Dtlink_t link; /* dictionary link */ Dt_t* messages; /* message dictionary handle */ nl_catd cat; /* message catalog handle */ int debug; /* special debug locale */ const char* locale; /* message catalog locale */ char name[1]; /* catalog name */ } Catalog_t; typedef struct { Dtlink_t link; /* dictionary link */ Catalog_t* cat; /* current catalog pointer */ int set; /* set number */ int seq; /* sequence number */ char text[1]; /* message text */ } Message_t; typedef struct { Sfio_t* sp; /* temp string stream */ int off; /* string base offset */ } Temp_t; typedef struct { Dtdisc_t message_disc; /* message dict discipline */ Dtdisc_t catalog_disc; /* catalog dict discipline */ Dt_t* catalogs; /* catalog dictionary handle */ Sfio_t* tmp; /* temporary string stream */ const char* debug; /* debug locale name */ int error; /* no dictionaries! */ char null[1]; /* null string */ } 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; } /* * add msg to dict */ 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; } /* * find catalog in locale and return catopen() descriptor */ static nl_catd find(const char* locale, const char* catalog) { char path[PATH_MAX]; #if DEBUG_trace const char* ocatalog = catalog; #endif if (mcfind(path, locale, catalog, LC_MESSAGES, 0)) catalog = (const char*)path; #if DEBUG_trace sfprintf(sfstderr, "AHA#%d:%s %s %s %s\n", __LINE__, __FILE__, locale, ocatalog, catalog); #endif return catopen(catalog, NL_CAT_LOCALE); } /* * initialize the catalog s by loading in the default locale messages */ static Catalog_t* init(register char* s) { register Catalog_t* cp; register char* u; register int n; register int m; nl_catd d; /* * insert into the catalog dictionary */ 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; /* * locate the default locale catalog */ u = setlocale(LC_MESSAGES, NiL); setlocale(LC_MESSAGES, "C"); if ((d = find("C", s)) != NOCAT) { /* * load the default locale messages * this assumes one mesage set for ast (AST_MESSAGE_SET) * different packages can share the same message catalog * name by using different message set numbers * see mcindex() * * this method requires a scan of each catalog, and the * catalogs do not advertize the max message number, so * we assume there are no messages after a gap of GAP * missing messages */ if (cp->messages = dtopen(&state.message_disc, Dtset)) { n = m = 0; for (;;) { n++; if ((s = catgets(d, AST_MESSAGE_SET, n, state.null)) != state.null && entry(cp->messages, AST_MESSAGE_SET, n, s)) m = n; else if ((n - m) > GAP) break; } if (!m) { dtclose(cp->messages); cp->messages = 0; } } catclose(d); } setlocale(LC_MESSAGES, u); return cp; } /* * return the C locale message pointer for msg in cat * cat may be a : separated list of candidate names */ 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; } /* * translate() is called with four arguments: * * loc the LC_MESSAGES locale name * cmd the calling command name * cat the catalog name, possibly a : separated list * "libFOO" FOO library messages * "libshell" ksh command messages * "SCRIPT" script SCRIPT application messages * msg message text to be translated * * the translated message text is returned on success * otherwise the original msg is returned * * The first time translate() is called (for a non-C locale) * it creates the state.catalogs dictionary. A dictionary entry * (Catalog_t) is made each time translate() is called with a new * cmd:cat argument. * * The X/Open interface catgets() is used to obtain a translated * message. Its arguments include the message catalog name * and the set/sequence numbers within the catalog. An additional * dictionary, with entries of type Message_t, is needed for * mapping untranslated message strings to the set/sequence numbers * needed by catgets(). A separate Message_t dictionary is maintained * for each Catalog_t. */ 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; oerrno = errno; r = (char*)msg; /* * quick out */ if (!cmd && !cat) goto done; if (cmd && (t = strrchr(cmd, '/'))) cmd = (const char*)(t + 1); /* * initialize the catalogs dictionary */ 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 (streq(loc, "debug")) state.debug = loc; } /* * get the message * or do we have to spell it out for you */ 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 goto done; } /* * adjust for the current locale */ #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 (cp->locale != loc) { cp->locale = loc; 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); } goto done; } /* * get the translated message */ r = catgets(cp->cat, mp->set, mp->seq, msg); 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); 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); } done: if (r == (char*)msg && loc == state.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; }