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