1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1985-2010 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 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 char name[1]; /* catalog name */ 51 } Catalog_t; 52 53 typedef struct 54 { 55 Dtlink_t link; /* dictionary link */ 56 Catalog_t* cat; /* current catalog pointer */ 57 int set; /* set number */ 58 int seq; /* sequence number */ 59 char text[1]; /* message text */ 60 } Message_t; 61 62 typedef struct 63 { 64 Sfio_t* sp; /* temp string stream */ 65 int off; /* string base offset */ 66 } Temp_t; 67 68 typedef struct 69 { 70 Dtdisc_t message_disc; /* message dict discipline */ 71 Dtdisc_t catalog_disc; /* catalog dict discipline */ 72 Dt_t* catalogs; /* catalog dictionary handle */ 73 Sfio_t* tmp; /* temporary string stream */ 74 const char* debug; /* debug locale name */ 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 path[PATH_MAX]; 133 #if DEBUG_trace 134 const char* ocatalog = catalog; 135 #endif 136 137 if (mcfind(path, locale, catalog, LC_MESSAGES, 0)) 138 catalog = (const char*)path; 139 #if DEBUG_trace 140 sfprintf(sfstderr, "AHA#%d:%s %s %s %s\n", __LINE__, __FILE__, locale, ocatalog, catalog); 141 #endif 142 return catopen(catalog, NL_CAT_LOCALE); 143 } 144 145 /* 146 * initialize the catalog s by loading in the default locale messages 147 */ 148 149 static Catalog_t* 150 init(register char* s) 151 { 152 register Catalog_t* cp; 153 register char* u; 154 register int n; 155 register int m; 156 nl_catd d; 157 158 /* 159 * insert into the catalog dictionary 160 */ 161 162 if (!(cp = newof(0, Catalog_t, 1, strlen(s)))) 163 return 0; 164 strcpy(cp->name, s); 165 if (!dtinsert(state.catalogs, cp)) 166 { 167 free(cp); 168 return 0; 169 } 170 cp->cat = NOCAT; 171 172 /* 173 * locate the default locale catalog 174 */ 175 176 ast.locale.set |= AST_LC_internal; 177 u = setlocale(LC_MESSAGES, NiL); 178 setlocale(LC_MESSAGES, "C"); 179 if ((d = find("C", s)) != NOCAT) 180 { 181 /* 182 * load the default locale messages 183 * this assumes one mesage set for ast (AST_MESSAGE_SET) 184 * different packages can share the same message catalog 185 * name by using different message set numbers 186 * see <mc.h> mcindex() 187 * 188 * this method requires a scan of each catalog, and the 189 * catalogs do not advertize the max message number, so 190 * we assume there are no messages after a gap of GAP 191 * missing messages 192 */ 193 194 if (cp->messages = dtopen(&state.message_disc, Dtset)) 195 { 196 n = m = 0; 197 for (;;) 198 { 199 n++; 200 if ((s = catgets(d, AST_MESSAGE_SET, n, state.null)) != state.null && entry(cp->messages, AST_MESSAGE_SET, n, s)) 201 m = n; 202 else if ((n - m) > GAP) 203 break; 204 } 205 if (!m) 206 { 207 dtclose(cp->messages); 208 cp->messages = 0; 209 } 210 } 211 catclose(d); 212 } 213 setlocale(LC_MESSAGES, u); 214 ast.locale.set &= ~AST_LC_internal; 215 return cp; 216 } 217 218 /* 219 * return the C locale message pointer for msg in cat 220 * cat may be a : separated list of candidate names 221 */ 222 223 static Message_t* 224 match(const char* cat, const char* msg) 225 { 226 register char* s; 227 register char* t; 228 Catalog_t* cp; 229 Message_t* mp; 230 size_t n; 231 232 char buf[1024]; 233 234 s = (char*)cat; 235 for (;;) 236 { 237 if (t = strchr(s, ':')) 238 { 239 if (s == (char*)cat) 240 { 241 if ((n = strlen(s)) >= sizeof(buf)) 242 n = sizeof(buf) - 1; 243 s = (char*)memcpy(buf, s, n); 244 s[n] = 0; 245 t = strchr(s, ':'); 246 } 247 *t = 0; 248 } 249 if (*s && ((cp = (Catalog_t*)dtmatch(state.catalogs, s)) || (cp = init(s))) && cp->messages && (mp = (Message_t*)dtmatch(cp->messages, msg))) 250 { 251 mp->cat = cp; 252 return mp; 253 } 254 if (!t) 255 break; 256 s = t + 1; 257 } 258 return 0; 259 } 260 261 /* 262 * translate() is called with four arguments: 263 * 264 * loc the LC_MESSAGES locale name 265 * cmd the calling command name 266 * cat the catalog name, possibly a : separated list 267 * "libFOO" FOO library messages 268 * "libshell" ksh command messages 269 * "SCRIPT" script SCRIPT application messages 270 * msg message text to be translated 271 * 272 * the translated message text is returned on success 273 * otherwise the original msg is returned 274 * 275 * The first time translate() is called (for a non-C locale) 276 * it creates the state.catalogs dictionary. A dictionary entry 277 * (Catalog_t) is made each time translate() is called with a new 278 * cmd:cat argument. 279 * 280 * The X/Open interface catgets() is used to obtain a translated 281 * message. Its arguments include the message catalog name 282 * and the set/sequence numbers within the catalog. An additional 283 * dictionary, with entries of type Message_t, is needed for 284 * mapping untranslated message strings to the set/sequence numbers 285 * needed by catgets(). A separate Message_t dictionary is maintained 286 * for each Catalog_t. 287 */ 288 289 char* 290 translate(const char* loc, const char* cmd, const char* cat, const char* msg) 291 { 292 register char* r; 293 char* t; 294 int p; 295 int oerrno; 296 Catalog_t* cp; 297 Message_t* mp; 298 299 oerrno = errno; 300 r = (char*)msg; 301 302 /* 303 * quick out 304 */ 305 306 if (!cmd && !cat) 307 goto done; 308 if (cmd && (t = strrchr(cmd, '/'))) 309 cmd = (const char*)(t + 1); 310 311 /* 312 * initialize the catalogs dictionary 313 */ 314 315 if (!state.catalogs) 316 { 317 if (state.error) 318 goto done; 319 if (!(state.tmp = sfstropen())) 320 { 321 state.error = 1; 322 goto done; 323 } 324 if (!(state.catalogs = dtopen(&state.catalog_disc, Dtset))) 325 { 326 sfclose(state.tmp); 327 state.error = 1; 328 goto done; 329 } 330 if (streq(loc, "debug")) 331 state.debug = loc; 332 } 333 334 /* 335 * get the message 336 * or do we have to spell it out for you 337 */ 338 339 if ((!cmd || !(mp = match(cmd, msg))) && 340 (!cat || !(mp = match(cat, msg))) && 341 (!error_info.catalog || !(mp = match(error_info.catalog, msg))) && 342 (!ast.id || !(mp = match(ast.id, msg))) || 343 !(cp = mp->cat)) 344 { 345 #if DEBUG_trace > 1 346 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); 347 #endif 348 goto done; 349 } 350 351 /* 352 * adjust for the current locale 353 */ 354 355 #if DEBUG_trace 356 sfprintf(sfstderr, "AHA#%d:%s cp->locale `%s' %p loc `%s' %p\n", __LINE__, __FILE__, cp->locale, cp->locale, loc, loc); 357 #endif 358 if (cp->locale != loc) 359 { 360 cp->locale = loc; 361 if (cp->cat != NOCAT) 362 catclose(cp->cat); 363 if ((cp->cat = find(cp->locale, cp->name)) == NOCAT) 364 cp->debug = streq(cp->locale, "debug"); 365 else 366 cp->debug = 0; 367 #if DEBUG_trace 368 sfprintf(sfstderr, "AHA#%d:%s cp->cat %p cp->debug %d NOCAT %p\n", __LINE__, __FILE__, cp->cat, cp->debug, NOCAT); 369 #endif 370 } 371 if (cp->cat == NOCAT) 372 { 373 if (cp->debug) 374 { 375 p = tempget(state.tmp); 376 sfprintf(state.tmp, "(%s,%d,%d)", cp->name, mp->set, mp->seq); 377 r = tempuse(state.tmp, p); 378 } 379 else if (ast.locale.set & AST_LC_debug) 380 { 381 p = tempget(state.tmp); 382 sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r); 383 r = tempuse(state.tmp, p); 384 } 385 goto done; 386 } 387 388 /* 389 * get the translated message 390 */ 391 392 r = catgets(cp->cat, mp->set, mp->seq, msg); 393 if (ast.locale.set & AST_LC_translate) 394 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); 395 if (r != (char*)msg) 396 { 397 if (streq(r, (char*)msg)) 398 r = (char*)msg; 399 else if (strcmp(fmtfmt(r), fmtfmt(msg))) 400 { 401 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); 402 r = (char*)msg; 403 } 404 } 405 if (ast.locale.set & AST_LC_debug) 406 { 407 p = tempget(state.tmp); 408 sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r); 409 r = tempuse(state.tmp, p); 410 } 411 done: 412 if (r == (char*)msg && loc == state.debug) 413 { 414 p = tempget(state.tmp); 415 sfprintf(state.tmp, "(%s,%s,%s,\"%s\")", loc, cmd, cat, r); 416 r = tempuse(state.tmp, p); 417 } 418 errno = oerrno; 419 return r; 420 } 421