1*4887Schin /*********************************************************************** 2*4887Schin * * 3*4887Schin * This software is part of the ast package * 4*4887Schin * Copyright (c) 1985-2007 AT&T Knowledge Ventures * 5*4887Schin * and is licensed under the * 6*4887Schin * Common Public License, Version 1.0 * 7*4887Schin * by AT&T Knowledge Ventures * 8*4887Schin * * 9*4887Schin * A copy of the License is available at * 10*4887Schin * http://www.opensource.org/licenses/cpl1.0.txt * 11*4887Schin * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 12*4887Schin * * 13*4887Schin * Information and Software Systems Research * 14*4887Schin * AT&T Research * 15*4887Schin * Florham Park NJ * 16*4887Schin * * 17*4887Schin * Glenn Fowler <gsf@research.att.com> * 18*4887Schin * David Korn <dgk@research.att.com> * 19*4887Schin * Phong Vo <kpv@research.att.com> * 20*4887Schin * * 21*4887Schin ***********************************************************************/ 22*4887Schin #pragma prototyped 23*4887Schin 24*4887Schin /* 25*4887Schin * AT&T Research and SCO 26*4887Schin * ast i18n message translation 27*4887Schin */ 28*4887Schin 29*4887Schin #include "lclib.h" 30*4887Schin 31*4887Schin #include <cdt.h> 32*4887Schin #include <error.h> 33*4887Schin #include <mc.h> 34*4887Schin #include <nl_types.h> 35*4887Schin 36*4887Schin #ifndef DEBUG_trace 37*4887Schin #define DEBUG_trace 0 38*4887Schin #endif 39*4887Schin 40*4887Schin #define NOCAT ((nl_catd)-1) 41*4887Schin #define GAP 100 42*4887Schin 43*4887Schin typedef struct 44*4887Schin { 45*4887Schin Dtlink_t link; /* dictionary link */ 46*4887Schin Dt_t* messages; /* message dictionary handle */ 47*4887Schin nl_catd cat; /* message catalog handle */ 48*4887Schin int debug; /* special debug locale */ 49*4887Schin const char* locale; /* message catalog locale */ 50*4887Schin char name[1]; /* catalog name */ 51*4887Schin } Catalog_t; 52*4887Schin 53*4887Schin typedef struct 54*4887Schin { 55*4887Schin Dtlink_t link; /* dictionary link */ 56*4887Schin Catalog_t* cat; /* current catalog pointer */ 57*4887Schin int set; /* set number */ 58*4887Schin int seq; /* sequence number */ 59*4887Schin char text[1]; /* message text */ 60*4887Schin } Message_t; 61*4887Schin 62*4887Schin typedef struct 63*4887Schin { 64*4887Schin Sfio_t* sp; /* temp string stream */ 65*4887Schin int off; /* string base offset */ 66*4887Schin } Temp_t; 67*4887Schin 68*4887Schin typedef struct 69*4887Schin { 70*4887Schin Dtdisc_t message_disc; /* message dict discipline */ 71*4887Schin Dtdisc_t catalog_disc; /* catalog dict discipline */ 72*4887Schin Dt_t* catalogs; /* catalog dictionary handle */ 73*4887Schin Sfio_t* tmp; /* temporary string stream */ 74*4887Schin const char* debug; /* debug locale name */ 75*4887Schin int error; /* no dictionaries! */ 76*4887Schin char null[1]; /* null string */ 77*4887Schin } State_t; 78*4887Schin 79*4887Schin static State_t state = 80*4887Schin { 81*4887Schin { offsetof(Message_t, text), 0, 0 }, 82*4887Schin { offsetof(Catalog_t, name), 0, 0 }, 83*4887Schin }; 84*4887Schin 85*4887Schin static int 86*4887Schin tempget(Sfio_t* sp) 87*4887Schin { 88*4887Schin if (sfstrtell(sp) > sfstrsize(sp) / 2) 89*4887Schin sfstrseek(sp, 0, SEEK_SET); 90*4887Schin return sfstrtell(sp); 91*4887Schin } 92*4887Schin 93*4887Schin static char* 94*4887Schin tempuse(Sfio_t* sp, int off) 95*4887Schin { 96*4887Schin sfputc(sp, 0); 97*4887Schin return sfstrbase(sp) + off; 98*4887Schin } 99*4887Schin 100*4887Schin /* 101*4887Schin * add msg to dict 102*4887Schin */ 103*4887Schin 104*4887Schin static int 105*4887Schin entry(Dt_t* dict, int set, int seq, const char* msg) 106*4887Schin { 107*4887Schin Message_t* mp; 108*4887Schin 109*4887Schin if (!(mp = newof(0, Message_t, 1, strlen(msg)))) 110*4887Schin return 0; 111*4887Schin strcpy(mp->text, msg); 112*4887Schin mp->set = set; 113*4887Schin mp->seq = seq; 114*4887Schin if (!dtinsert(dict, mp)) 115*4887Schin { 116*4887Schin free(mp); 117*4887Schin return 0; 118*4887Schin } 119*4887Schin #if DEBUG_trace > 1 120*4887Schin sfprintf(sfstderr, "AHA#%d:%s set %d seq %d msg `%s'\n", __LINE__, __FILE__, set, seq, msg); 121*4887Schin #endif 122*4887Schin return 1; 123*4887Schin } 124*4887Schin 125*4887Schin /* 126*4887Schin * find catalog in locale and return catopen() descriptor 127*4887Schin */ 128*4887Schin 129*4887Schin static nl_catd 130*4887Schin find(const char* locale, const char* catalog) 131*4887Schin { 132*4887Schin char path[PATH_MAX]; 133*4887Schin #if DEBUG_trace 134*4887Schin const char* ocatalog = catalog; 135*4887Schin #endif 136*4887Schin 137*4887Schin if (mcfind(path, locale, catalog, LC_MESSAGES, 0)) 138*4887Schin catalog = (const char*)path; 139*4887Schin #if DEBUG_trace 140*4887Schin sfprintf(sfstderr, "AHA#%d:%s %s %s %s\n", __LINE__, __FILE__, locale, ocatalog, catalog); 141*4887Schin #endif 142*4887Schin return catopen(catalog, NL_CAT_LOCALE); 143*4887Schin } 144*4887Schin 145*4887Schin /* 146*4887Schin * initialize the catalog s by loading in the default locale messages 147*4887Schin */ 148*4887Schin 149*4887Schin static Catalog_t* 150*4887Schin init(register char* s) 151*4887Schin { 152*4887Schin register Catalog_t* cp; 153*4887Schin register char* u; 154*4887Schin register int n; 155*4887Schin register int m; 156*4887Schin nl_catd d; 157*4887Schin 158*4887Schin /* 159*4887Schin * insert into the catalog dictionary 160*4887Schin */ 161*4887Schin 162*4887Schin if (!(cp = newof(0, Catalog_t, 1, strlen(s)))) 163*4887Schin return 0; 164*4887Schin strcpy(cp->name, s); 165*4887Schin if (!dtinsert(state.catalogs, cp)) 166*4887Schin { 167*4887Schin free(cp); 168*4887Schin return 0; 169*4887Schin } 170*4887Schin cp->cat = NOCAT; 171*4887Schin 172*4887Schin /* 173*4887Schin * locate the default locale catalog 174*4887Schin */ 175*4887Schin 176*4887Schin u = setlocale(LC_MESSAGES, NiL); 177*4887Schin setlocale(LC_MESSAGES, "C"); 178*4887Schin if ((d = find("C", s)) != NOCAT) 179*4887Schin { 180*4887Schin /* 181*4887Schin * load the default locale messages 182*4887Schin * this assumes one mesage set for ast (AST_MESSAGE_SET) 183*4887Schin * different packages can share the same message catalog 184*4887Schin * name by using different message set numbers 185*4887Schin * see <mc.h> mcindex() 186*4887Schin * 187*4887Schin * this method requires a scan of each catalog, and the 188*4887Schin * catalogs do not advertize the max message number, so 189*4887Schin * we assume there are no messages after a gap of GAP 190*4887Schin * missing messages 191*4887Schin */ 192*4887Schin 193*4887Schin if (cp->messages = dtopen(&state.message_disc, Dtset)) 194*4887Schin { 195*4887Schin n = m = 0; 196*4887Schin for (;;) 197*4887Schin { 198*4887Schin n++; 199*4887Schin if ((s = catgets(d, AST_MESSAGE_SET, n, state.null)) != state.null && entry(cp->messages, AST_MESSAGE_SET, n, s)) 200*4887Schin m = n; 201*4887Schin else if ((n - m) > GAP) 202*4887Schin break; 203*4887Schin } 204*4887Schin if (!m) 205*4887Schin { 206*4887Schin dtclose(cp->messages); 207*4887Schin cp->messages = 0; 208*4887Schin } 209*4887Schin } 210*4887Schin catclose(d); 211*4887Schin } 212*4887Schin setlocale(LC_MESSAGES, u); 213*4887Schin return cp; 214*4887Schin } 215*4887Schin 216*4887Schin /* 217*4887Schin * return the C locale message pointer for msg in cat 218*4887Schin * cat may be a : separated list of candidate names 219*4887Schin */ 220*4887Schin 221*4887Schin static Message_t* 222*4887Schin match(const char* cat, const char* msg) 223*4887Schin { 224*4887Schin register char* s; 225*4887Schin register char* t; 226*4887Schin Catalog_t* cp; 227*4887Schin Message_t* mp; 228*4887Schin size_t n; 229*4887Schin 230*4887Schin char buf[1024]; 231*4887Schin 232*4887Schin s = (char*)cat; 233*4887Schin for (;;) 234*4887Schin { 235*4887Schin if (t = strchr(s, ':')) 236*4887Schin { 237*4887Schin if (s == (char*)cat) 238*4887Schin { 239*4887Schin if ((n = strlen(s)) >= sizeof(buf)) 240*4887Schin n = sizeof(buf) - 1; 241*4887Schin s = (char*)memcpy(buf, s, n); 242*4887Schin s[n] = 0; 243*4887Schin t = strchr(s, ':'); 244*4887Schin } 245*4887Schin *t = 0; 246*4887Schin } 247*4887Schin if (*s && ((cp = (Catalog_t*)dtmatch(state.catalogs, s)) || (cp = init(s))) && cp->messages && (mp = (Message_t*)dtmatch(cp->messages, msg))) 248*4887Schin { 249*4887Schin mp->cat = cp; 250*4887Schin return mp; 251*4887Schin } 252*4887Schin if (!t) 253*4887Schin break; 254*4887Schin s = t + 1; 255*4887Schin } 256*4887Schin return 0; 257*4887Schin } 258*4887Schin 259*4887Schin /* 260*4887Schin * translate() is called with four arguments: 261*4887Schin * 262*4887Schin * loc the LC_MESSAGES locale name 263*4887Schin * cmd the calling command name 264*4887Schin * cat the catalog name, possibly a : separated list 265*4887Schin * "libFOO" FOO library messages 266*4887Schin * "libshell" ksh command messages 267*4887Schin * "SCRIPT" script SCRIPT application messages 268*4887Schin * msg message text to be translated 269*4887Schin * 270*4887Schin * the translated message text is returned on success 271*4887Schin * otherwise the original msg is returned 272*4887Schin * 273*4887Schin * The first time translate() is called (for a non-C locale) 274*4887Schin * it creates the state.catalogs dictionary. A dictionary entry 275*4887Schin * (Catalog_t) is made each time translate() is called with a new 276*4887Schin * cmd:cat argument. 277*4887Schin * 278*4887Schin * The X/Open interface catgets() is used to obtain a translated 279*4887Schin * message. Its arguments include the message catalog name 280*4887Schin * and the set/sequence numbers within the catalog. An additional 281*4887Schin * dictionary, with entries of type Message_t, is needed for 282*4887Schin * mapping untranslated message strings to the set/sequence numbers 283*4887Schin * needed by catgets(). A separate Message_t dictionary is maintained 284*4887Schin * for each Catalog_t. 285*4887Schin */ 286*4887Schin 287*4887Schin char* 288*4887Schin translate(const char* loc, const char* cmd, const char* cat, const char* msg) 289*4887Schin { 290*4887Schin register char* r; 291*4887Schin char* t; 292*4887Schin int p; 293*4887Schin int oerrno; 294*4887Schin Catalog_t* cp; 295*4887Schin Message_t* mp; 296*4887Schin 297*4887Schin oerrno = errno; 298*4887Schin r = (char*)msg; 299*4887Schin 300*4887Schin /* 301*4887Schin * quick out 302*4887Schin */ 303*4887Schin 304*4887Schin if (!cmd && !cat) 305*4887Schin goto done; 306*4887Schin if (cmd && (t = strrchr(cmd, '/'))) 307*4887Schin cmd = (const char*)(t + 1); 308*4887Schin 309*4887Schin /* 310*4887Schin * initialize the catalogs dictionary 311*4887Schin */ 312*4887Schin 313*4887Schin if (!state.catalogs) 314*4887Schin { 315*4887Schin if (state.error) 316*4887Schin goto done; 317*4887Schin if (!(state.tmp = sfstropen())) 318*4887Schin { 319*4887Schin state.error = 1; 320*4887Schin goto done; 321*4887Schin } 322*4887Schin if (!(state.catalogs = dtopen(&state.catalog_disc, Dtset))) 323*4887Schin { 324*4887Schin sfclose(state.tmp); 325*4887Schin state.error = 1; 326*4887Schin goto done; 327*4887Schin } 328*4887Schin if (streq(loc, "debug")) 329*4887Schin state.debug = loc; 330*4887Schin } 331*4887Schin 332*4887Schin /* 333*4887Schin * get the message 334*4887Schin * or do we have to spell it out for you 335*4887Schin */ 336*4887Schin 337*4887Schin if ((!cmd || !(mp = match(cmd, msg))) && 338*4887Schin (!cat || !(mp = match(cat, msg))) && 339*4887Schin (!error_info.catalog || !(mp = match(error_info.catalog, msg))) && 340*4887Schin (!ast.id || !(mp = match(ast.id, msg))) || 341*4887Schin !(cp = mp->cat)) 342*4887Schin { 343*4887Schin #if DEBUG_trace > 1 344*4887Schin 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); 345*4887Schin #endif 346*4887Schin goto done; 347*4887Schin } 348*4887Schin 349*4887Schin /* 350*4887Schin * adjust for the current locale 351*4887Schin */ 352*4887Schin 353*4887Schin #if DEBUG_trace 354*4887Schin sfprintf(sfstderr, "AHA#%d:%s cp->locale `%s' %p loc `%s' %p\n", __LINE__, __FILE__, cp->locale, cp->locale, loc, loc); 355*4887Schin #endif 356*4887Schin if (cp->locale != loc) 357*4887Schin { 358*4887Schin cp->locale = loc; 359*4887Schin if (cp->cat != NOCAT) 360*4887Schin catclose(cp->cat); 361*4887Schin if ((cp->cat = find(cp->locale, cp->name)) == NOCAT) 362*4887Schin cp->debug = streq(cp->locale, "debug"); 363*4887Schin else 364*4887Schin cp->debug = 0; 365*4887Schin #if DEBUG_trace 366*4887Schin sfprintf(sfstderr, "AHA#%d:%s cp->cat %p cp->debug %d NOCAT %p\n", __LINE__, __FILE__, cp->cat, cp->debug, NOCAT); 367*4887Schin #endif 368*4887Schin } 369*4887Schin if (cp->cat == NOCAT) 370*4887Schin { 371*4887Schin if (cp->debug) 372*4887Schin { 373*4887Schin p = tempget(state.tmp); 374*4887Schin sfprintf(state.tmp, "(%s,%d,%d)", cp->name, mp->set, mp->seq); 375*4887Schin r = tempuse(state.tmp, p); 376*4887Schin } 377*4887Schin else if (ast.locale.set & AST_LC_debug) 378*4887Schin { 379*4887Schin p = tempget(state.tmp); 380*4887Schin sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r); 381*4887Schin r = tempuse(state.tmp, p); 382*4887Schin } 383*4887Schin goto done; 384*4887Schin } 385*4887Schin 386*4887Schin /* 387*4887Schin * get the translated message 388*4887Schin */ 389*4887Schin 390*4887Schin r = catgets(cp->cat, mp->set, mp->seq, msg); 391*4887Schin if (ast.locale.set & AST_LC_translate) 392*4887Schin 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); 393*4887Schin if (r != (char*)msg) 394*4887Schin { 395*4887Schin if (streq(r, (char*)msg)) 396*4887Schin r = (char*)msg; 397*4887Schin else if (strcmp(fmtfmt(r), fmtfmt(msg))) 398*4887Schin { 399*4887Schin 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); 400*4887Schin r = (char*)msg; 401*4887Schin } 402*4887Schin } 403*4887Schin if (ast.locale.set & AST_LC_debug) 404*4887Schin { 405*4887Schin p = tempget(state.tmp); 406*4887Schin sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r); 407*4887Schin r = tempuse(state.tmp, p); 408*4887Schin } 409*4887Schin done: 410*4887Schin if (r == (char*)msg && loc == state.debug) 411*4887Schin { 412*4887Schin p = tempget(state.tmp); 413*4887Schin sfprintf(state.tmp, "(%s,%s,%s,\"%s\")", loc, cmd, cat, r); 414*4887Schin r = tempuse(state.tmp, p); 415*4887Schin } 416*4887Schin errno = oerrno; 417*4887Schin return r; 418*4887Schin } 419