1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1985-2012 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Eclipse Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.eclipse.org/org/documents/epl-v10.html * 11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) * 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 43 typedef 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 54 typedef 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 63 typedef struct 64 { 65 Sfio_t* sp; /* temp string stream */ 66 int off; /* string base offset */ 67 } Temp_t; 68 69 typedef 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 79 static State_t state = 80 { 81 { offsetof(Message_t, text), 0, 0 }, 82 { offsetof(Catalog_t, name), 0, 0 }, 83 }; 84 85 static int 86 tempget(Sfio_t* sp) 87 { 88 if (sfstrtell(sp) > sfstrsize(sp) / 2) 89 sfstrseek(sp, 0, SEEK_SET); 90 return sfstrtell(sp); 91 } 92 93 static char* 94 tempuse(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 104 static int 105 entry(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 120 sfprintf(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 129 static nl_catd 130 find(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 159 static Catalog_t* 160 init(register char* s) 161 { 162 register Catalog_t* cp; 163 register int n; 164 register int m; 165 register int set; 166 nl_catd d; 167 168 /* 169 * insert into the catalog dictionary 170 */ 171 172 if (!(cp = newof(0, Catalog_t, 1, strlen(s)))) 173 return 0; 174 strcpy(cp->name, s); 175 if (!dtinsert(state.catalogs, cp)) 176 { 177 free(cp); 178 return 0; 179 } 180 cp->cat = NOCAT; 181 182 /* 183 * locate the default locale catalog 184 */ 185 186 if ((d = find("C", s)) != NOCAT) 187 { 188 /* 189 * load the default locale messages 190 * this assumes one mesage set for ast (AST_MESSAGE_SET or fallback to 1) 191 * different packages can share the same message catalog 192 * name by using different message set numbers 193 * see <mc.h> mcindex() 194 * 195 * this method requires a scan of each catalog, and the 196 * catalogs do not advertise the max message number, so 197 * we assume there are no messages after a gap of GAP 198 * missing messages 199 */ 200 201 if (cp->messages = dtopen(&state.message_disc, Dtset)) 202 { 203 n = m = 0; 204 for (;;) 205 { 206 n++; 207 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)) 208 m = n; 209 else if ((n - m) > GAP) 210 break; 211 } 212 if (!m) 213 { 214 dtclose(cp->messages); 215 cp->messages = 0; 216 } 217 } 218 catclose(d); 219 } 220 return cp; 221 } 222 223 /* 224 * return the C locale message pointer for msg in cat 225 * cat may be a : separated list of candidate names 226 */ 227 228 static Message_t* 229 match(const char* cat, const char* msg) 230 { 231 register char* s; 232 register char* t; 233 Catalog_t* cp; 234 Message_t* mp; 235 size_t n; 236 237 char buf[1024]; 238 239 s = (char*)cat; 240 for (;;) 241 { 242 if (t = strchr(s, ':')) 243 { 244 if (s == (char*)cat) 245 { 246 if ((n = strlen(s)) >= sizeof(buf)) 247 n = sizeof(buf) - 1; 248 s = (char*)memcpy(buf, s, n); 249 s[n] = 0; 250 t = strchr(s, ':'); 251 } 252 *t = 0; 253 } 254 if (*s && ((cp = (Catalog_t*)dtmatch(state.catalogs, s)) || (cp = init(s))) && cp->messages && (mp = (Message_t*)dtmatch(cp->messages, msg))) 255 { 256 mp->cat = cp; 257 return mp; 258 } 259 if (!t) 260 break; 261 s = t + 1; 262 } 263 return 0; 264 } 265 266 /* 267 * translate() is called with four arguments: 268 * 269 * loc the LC_MESSAGES locale name 270 * cmd the calling command name 271 * cat the catalog name, possibly a : separated list 272 * "libFOO" FOO library messages 273 * "libshell" ksh command messages 274 * "SCRIPT" script SCRIPT application messages 275 * msg message text to be translated 276 * 277 * the translated message text is returned on success 278 * otherwise the original msg is returned 279 * 280 * The first time translate() is called (for a non-C locale) 281 * it creates the state.catalogs dictionary. A dictionary entry 282 * (Catalog_t) is made each time translate() is called with a new 283 * cmd:cat argument. 284 * 285 * The X/Open interface catgets() is used to obtain a translated 286 * message. Its arguments include the message catalog name 287 * and the set/sequence numbers within the catalog. An additional 288 * dictionary, with entries of type Message_t, is needed for 289 * mapping untranslated message strings to the set/sequence numbers 290 * needed by catgets(). A separate Message_t dictionary is maintained 291 * for each Catalog_t. 292 */ 293 294 char* 295 translate(const char* loc, const char* cmd, const char* cat, const char* msg) 296 { 297 register char* r; 298 char* t; 299 int p; 300 int oerrno; 301 Catalog_t* cp; 302 Message_t* mp; 303 304 static uint32_t serial; 305 static char* nlspath; 306 307 oerrno = errno; 308 r = (char*)msg; 309 310 /* 311 * quick out 312 */ 313 314 if (!cmd && !cat) 315 goto done; 316 if (cmd && (t = strrchr(cmd, '/'))) 317 cmd = (const char*)(t + 1); 318 319 /* 320 * initialize the catalogs dictionary 321 */ 322 323 if (!state.catalogs) 324 { 325 if (state.error) 326 goto done; 327 if (!(state.tmp = sfstropen())) 328 { 329 state.error = 1; 330 goto done; 331 } 332 if (!(state.catalogs = dtopen(&state.catalog_disc, Dtset))) 333 { 334 sfclose(state.tmp); 335 state.error = 1; 336 goto done; 337 } 338 } 339 340 /* 341 * get the message 342 * or do we have to spell it out for you 343 */ 344 345 if ((!cmd || !(mp = match(cmd, msg))) && 346 (!cat || !(mp = match(cat, msg))) && 347 (!error_info.catalog || !(mp = match(error_info.catalog, msg))) && 348 (!ast.id || !(mp = match(ast.id, msg))) || 349 !(cp = mp->cat)) 350 { 351 #if DEBUG_trace > 1 352 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); 353 #endif 354 cp = 0; 355 goto done; 356 } 357 358 /* 359 * adjust for the current locale 360 */ 361 362 #if DEBUG_trace 363 sfprintf(sfstderr, "AHA#%d:%s cp->locale `%s' %p loc `%s' %p\n", __LINE__, __FILE__, cp->locale, cp->locale, loc, loc); 364 #endif 365 if (serial != ast.env_serial) 366 { 367 serial = ast.env_serial; 368 nlspath = getenv("NLSPATH"); 369 } 370 if (cp->locale != loc || cp->nlspath != nlspath) 371 { 372 cp->locale = loc; 373 cp->nlspath = nlspath; 374 if (cp->cat != NOCAT) 375 catclose(cp->cat); 376 if ((cp->cat = find(cp->locale, cp->name)) == NOCAT) 377 cp->debug = streq(cp->locale, "debug"); 378 else 379 cp->debug = 0; 380 #if DEBUG_trace 381 sfprintf(sfstderr, "AHA#%d:%s cp->cat %p cp->debug %d NOCAT %p\n", __LINE__, __FILE__, cp->cat, cp->debug, NOCAT); 382 #endif 383 } 384 if (cp->cat == NOCAT) 385 { 386 if (cp->debug) 387 { 388 p = tempget(state.tmp); 389 sfprintf(state.tmp, "(%s,%d,%d)", cp->name, mp->set, mp->seq); 390 r = tempuse(state.tmp, p); 391 } 392 else if (ast.locale.set & AST_LC_debug) 393 { 394 p = tempget(state.tmp); 395 sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r); 396 r = tempuse(state.tmp, p); 397 } 398 } 399 else 400 { 401 /* 402 * get the translated message 403 */ 404 405 r = catgets(cp->cat, mp->set, mp->seq, msg); 406 if (r != (char*)msg) 407 { 408 if (streq(r, (char*)msg)) 409 r = (char*)msg; 410 else if (strcmp(fmtfmt(r), fmtfmt(msg))) 411 { 412 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); 413 r = (char*)msg; 414 } 415 } 416 if (ast.locale.set & AST_LC_debug) 417 { 418 p = tempget(state.tmp); 419 sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r); 420 r = tempuse(state.tmp, p); 421 } 422 } 423 if (ast.locale.set & AST_LC_translate) 424 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); 425 done: 426 if (r == (char*)msg && (!cp && streq(loc, "debug") || cp && cp->debug)) 427 { 428 p = tempget(state.tmp); 429 sfprintf(state.tmp, "(%s,%s,%s,%s)", loc, cmd, cat, r); 430 r = tempuse(state.tmp, p); 431 } 432 errno = oerrno; 433 return r; 434 } 435