14887Schin /*********************************************************************** 24887Schin * * 34887Schin * This software is part of the ast package * 4*8462SApril.Chin@Sun.COM * Copyright (c) 1985-2008 AT&T Intellectual Property * 54887Schin * and is licensed under the * 64887Schin * Common Public License, Version 1.0 * 7*8462SApril.Chin@Sun.COM * by AT&T Intellectual Property * 84887Schin * * 94887Schin * A copy of the License is available at * 104887Schin * http://www.opensource.org/licenses/cpl1.0.txt * 114887Schin * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 124887Schin * * 134887Schin * Information and Software Systems Research * 144887Schin * AT&T Research * 154887Schin * Florham Park NJ * 164887Schin * * 174887Schin * Glenn Fowler <gsf@research.att.com> * 184887Schin * David Korn <dgk@research.att.com> * 194887Schin * Phong Vo <kpv@research.att.com> * 204887Schin * * 214887Schin ***********************************************************************/ 224887Schin #pragma prototyped 234887Schin 244887Schin /* 254887Schin * AT&T Research and SCO 264887Schin * ast i18n message translation 274887Schin */ 284887Schin 294887Schin #include "lclib.h" 304887Schin 314887Schin #include <cdt.h> 324887Schin #include <error.h> 334887Schin #include <mc.h> 344887Schin #include <nl_types.h> 354887Schin 364887Schin #ifndef DEBUG_trace 374887Schin #define DEBUG_trace 0 384887Schin #endif 394887Schin 404887Schin #define NOCAT ((nl_catd)-1) 414887Schin #define GAP 100 424887Schin 434887Schin typedef struct 444887Schin { 454887Schin Dtlink_t link; /* dictionary link */ 464887Schin Dt_t* messages; /* message dictionary handle */ 474887Schin nl_catd cat; /* message catalog handle */ 484887Schin int debug; /* special debug locale */ 494887Schin const char* locale; /* message catalog locale */ 504887Schin char name[1]; /* catalog name */ 514887Schin } Catalog_t; 524887Schin 534887Schin typedef struct 544887Schin { 554887Schin Dtlink_t link; /* dictionary link */ 564887Schin Catalog_t* cat; /* current catalog pointer */ 574887Schin int set; /* set number */ 584887Schin int seq; /* sequence number */ 594887Schin char text[1]; /* message text */ 604887Schin } Message_t; 614887Schin 624887Schin typedef struct 634887Schin { 644887Schin Sfio_t* sp; /* temp string stream */ 654887Schin int off; /* string base offset */ 664887Schin } Temp_t; 674887Schin 684887Schin typedef struct 694887Schin { 704887Schin Dtdisc_t message_disc; /* message dict discipline */ 714887Schin Dtdisc_t catalog_disc; /* catalog dict discipline */ 724887Schin Dt_t* catalogs; /* catalog dictionary handle */ 734887Schin Sfio_t* tmp; /* temporary string stream */ 744887Schin const char* debug; /* debug locale name */ 754887Schin int error; /* no dictionaries! */ 764887Schin char null[1]; /* null string */ 774887Schin } State_t; 784887Schin 794887Schin static State_t state = 804887Schin { 814887Schin { offsetof(Message_t, text), 0, 0 }, 824887Schin { offsetof(Catalog_t, name), 0, 0 }, 834887Schin }; 844887Schin 854887Schin static int 864887Schin tempget(Sfio_t* sp) 874887Schin { 884887Schin if (sfstrtell(sp) > sfstrsize(sp) / 2) 894887Schin sfstrseek(sp, 0, SEEK_SET); 904887Schin return sfstrtell(sp); 914887Schin } 924887Schin 934887Schin static char* 944887Schin tempuse(Sfio_t* sp, int off) 954887Schin { 964887Schin sfputc(sp, 0); 974887Schin return sfstrbase(sp) + off; 984887Schin } 994887Schin 1004887Schin /* 1014887Schin * add msg to dict 1024887Schin */ 1034887Schin 1044887Schin static int 1054887Schin entry(Dt_t* dict, int set, int seq, const char* msg) 1064887Schin { 1074887Schin Message_t* mp; 1084887Schin 1094887Schin if (!(mp = newof(0, Message_t, 1, strlen(msg)))) 1104887Schin return 0; 1114887Schin strcpy(mp->text, msg); 1124887Schin mp->set = set; 1134887Schin mp->seq = seq; 1144887Schin if (!dtinsert(dict, mp)) 1154887Schin { 1164887Schin free(mp); 1174887Schin return 0; 1184887Schin } 1194887Schin #if DEBUG_trace > 1 1204887Schin sfprintf(sfstderr, "AHA#%d:%s set %d seq %d msg `%s'\n", __LINE__, __FILE__, set, seq, msg); 1214887Schin #endif 1224887Schin return 1; 1234887Schin } 1244887Schin 1254887Schin /* 1264887Schin * find catalog in locale and return catopen() descriptor 1274887Schin */ 1284887Schin 1294887Schin static nl_catd 1304887Schin find(const char* locale, const char* catalog) 1314887Schin { 1324887Schin char path[PATH_MAX]; 1334887Schin #if DEBUG_trace 1344887Schin const char* ocatalog = catalog; 1354887Schin #endif 1364887Schin 1374887Schin if (mcfind(path, locale, catalog, LC_MESSAGES, 0)) 1384887Schin catalog = (const char*)path; 1394887Schin #if DEBUG_trace 1404887Schin sfprintf(sfstderr, "AHA#%d:%s %s %s %s\n", __LINE__, __FILE__, locale, ocatalog, catalog); 1414887Schin #endif 1424887Schin return catopen(catalog, NL_CAT_LOCALE); 1434887Schin } 1444887Schin 1454887Schin /* 1464887Schin * initialize the catalog s by loading in the default locale messages 1474887Schin */ 1484887Schin 1494887Schin static Catalog_t* 1504887Schin init(register char* s) 1514887Schin { 1524887Schin register Catalog_t* cp; 1534887Schin register char* u; 1544887Schin register int n; 1554887Schin register int m; 1564887Schin nl_catd d; 1574887Schin 1584887Schin /* 1594887Schin * insert into the catalog dictionary 1604887Schin */ 1614887Schin 1624887Schin if (!(cp = newof(0, Catalog_t, 1, strlen(s)))) 1634887Schin return 0; 1644887Schin strcpy(cp->name, s); 1654887Schin if (!dtinsert(state.catalogs, cp)) 1664887Schin { 1674887Schin free(cp); 1684887Schin return 0; 1694887Schin } 1704887Schin cp->cat = NOCAT; 1714887Schin 1724887Schin /* 1734887Schin * locate the default locale catalog 1744887Schin */ 1754887Schin 1764887Schin u = setlocale(LC_MESSAGES, NiL); 1774887Schin setlocale(LC_MESSAGES, "C"); 1784887Schin if ((d = find("C", s)) != NOCAT) 1794887Schin { 1804887Schin /* 1814887Schin * load the default locale messages 1824887Schin * this assumes one mesage set for ast (AST_MESSAGE_SET) 1834887Schin * different packages can share the same message catalog 1844887Schin * name by using different message set numbers 1854887Schin * see <mc.h> mcindex() 1864887Schin * 1874887Schin * this method requires a scan of each catalog, and the 1884887Schin * catalogs do not advertize the max message number, so 1894887Schin * we assume there are no messages after a gap of GAP 1904887Schin * missing messages 1914887Schin */ 1924887Schin 1934887Schin if (cp->messages = dtopen(&state.message_disc, Dtset)) 1944887Schin { 1954887Schin n = m = 0; 1964887Schin for (;;) 1974887Schin { 1984887Schin n++; 1994887Schin if ((s = catgets(d, AST_MESSAGE_SET, n, state.null)) != state.null && entry(cp->messages, AST_MESSAGE_SET, n, s)) 2004887Schin m = n; 2014887Schin else if ((n - m) > GAP) 2024887Schin break; 2034887Schin } 2044887Schin if (!m) 2054887Schin { 2064887Schin dtclose(cp->messages); 2074887Schin cp->messages = 0; 2084887Schin } 2094887Schin } 2104887Schin catclose(d); 2114887Schin } 2124887Schin setlocale(LC_MESSAGES, u); 2134887Schin return cp; 2144887Schin } 2154887Schin 2164887Schin /* 2174887Schin * return the C locale message pointer for msg in cat 2184887Schin * cat may be a : separated list of candidate names 2194887Schin */ 2204887Schin 2214887Schin static Message_t* 2224887Schin match(const char* cat, const char* msg) 2234887Schin { 2244887Schin register char* s; 2254887Schin register char* t; 2264887Schin Catalog_t* cp; 2274887Schin Message_t* mp; 2284887Schin size_t n; 2294887Schin 2304887Schin char buf[1024]; 2314887Schin 2324887Schin s = (char*)cat; 2334887Schin for (;;) 2344887Schin { 2354887Schin if (t = strchr(s, ':')) 2364887Schin { 2374887Schin if (s == (char*)cat) 2384887Schin { 2394887Schin if ((n = strlen(s)) >= sizeof(buf)) 2404887Schin n = sizeof(buf) - 1; 2414887Schin s = (char*)memcpy(buf, s, n); 2424887Schin s[n] = 0; 2434887Schin t = strchr(s, ':'); 2444887Schin } 2454887Schin *t = 0; 2464887Schin } 2474887Schin if (*s && ((cp = (Catalog_t*)dtmatch(state.catalogs, s)) || (cp = init(s))) && cp->messages && (mp = (Message_t*)dtmatch(cp->messages, msg))) 2484887Schin { 2494887Schin mp->cat = cp; 2504887Schin return mp; 2514887Schin } 2524887Schin if (!t) 2534887Schin break; 2544887Schin s = t + 1; 2554887Schin } 2564887Schin return 0; 2574887Schin } 2584887Schin 2594887Schin /* 2604887Schin * translate() is called with four arguments: 2614887Schin * 2624887Schin * loc the LC_MESSAGES locale name 2634887Schin * cmd the calling command name 2644887Schin * cat the catalog name, possibly a : separated list 2654887Schin * "libFOO" FOO library messages 2664887Schin * "libshell" ksh command messages 2674887Schin * "SCRIPT" script SCRIPT application messages 2684887Schin * msg message text to be translated 2694887Schin * 2704887Schin * the translated message text is returned on success 2714887Schin * otherwise the original msg is returned 2724887Schin * 2734887Schin * The first time translate() is called (for a non-C locale) 2744887Schin * it creates the state.catalogs dictionary. A dictionary entry 2754887Schin * (Catalog_t) is made each time translate() is called with a new 2764887Schin * cmd:cat argument. 2774887Schin * 2784887Schin * The X/Open interface catgets() is used to obtain a translated 2794887Schin * message. Its arguments include the message catalog name 2804887Schin * and the set/sequence numbers within the catalog. An additional 2814887Schin * dictionary, with entries of type Message_t, is needed for 2824887Schin * mapping untranslated message strings to the set/sequence numbers 2834887Schin * needed by catgets(). A separate Message_t dictionary is maintained 2844887Schin * for each Catalog_t. 2854887Schin */ 2864887Schin 2874887Schin char* 2884887Schin translate(const char* loc, const char* cmd, const char* cat, const char* msg) 2894887Schin { 2904887Schin register char* r; 2914887Schin char* t; 2924887Schin int p; 2934887Schin int oerrno; 2944887Schin Catalog_t* cp; 2954887Schin Message_t* mp; 2964887Schin 2974887Schin oerrno = errno; 2984887Schin r = (char*)msg; 2994887Schin 3004887Schin /* 3014887Schin * quick out 3024887Schin */ 3034887Schin 3044887Schin if (!cmd && !cat) 3054887Schin goto done; 3064887Schin if (cmd && (t = strrchr(cmd, '/'))) 3074887Schin cmd = (const char*)(t + 1); 3084887Schin 3094887Schin /* 3104887Schin * initialize the catalogs dictionary 3114887Schin */ 3124887Schin 3134887Schin if (!state.catalogs) 3144887Schin { 3154887Schin if (state.error) 3164887Schin goto done; 3174887Schin if (!(state.tmp = sfstropen())) 3184887Schin { 3194887Schin state.error = 1; 3204887Schin goto done; 3214887Schin } 3224887Schin if (!(state.catalogs = dtopen(&state.catalog_disc, Dtset))) 3234887Schin { 3244887Schin sfclose(state.tmp); 3254887Schin state.error = 1; 3264887Schin goto done; 3274887Schin } 3284887Schin if (streq(loc, "debug")) 3294887Schin state.debug = loc; 3304887Schin } 3314887Schin 3324887Schin /* 3334887Schin * get the message 3344887Schin * or do we have to spell it out for you 3354887Schin */ 3364887Schin 3374887Schin if ((!cmd || !(mp = match(cmd, msg))) && 3384887Schin (!cat || !(mp = match(cat, msg))) && 3394887Schin (!error_info.catalog || !(mp = match(error_info.catalog, msg))) && 3404887Schin (!ast.id || !(mp = match(ast.id, msg))) || 3414887Schin !(cp = mp->cat)) 3424887Schin { 3434887Schin #if DEBUG_trace > 1 3444887Schin 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); 3454887Schin #endif 3464887Schin goto done; 3474887Schin } 3484887Schin 3494887Schin /* 3504887Schin * adjust for the current locale 3514887Schin */ 3524887Schin 3534887Schin #if DEBUG_trace 3544887Schin sfprintf(sfstderr, "AHA#%d:%s cp->locale `%s' %p loc `%s' %p\n", __LINE__, __FILE__, cp->locale, cp->locale, loc, loc); 3554887Schin #endif 3564887Schin if (cp->locale != loc) 3574887Schin { 3584887Schin cp->locale = loc; 3594887Schin if (cp->cat != NOCAT) 3604887Schin catclose(cp->cat); 3614887Schin if ((cp->cat = find(cp->locale, cp->name)) == NOCAT) 3624887Schin cp->debug = streq(cp->locale, "debug"); 3634887Schin else 3644887Schin cp->debug = 0; 3654887Schin #if DEBUG_trace 3664887Schin sfprintf(sfstderr, "AHA#%d:%s cp->cat %p cp->debug %d NOCAT %p\n", __LINE__, __FILE__, cp->cat, cp->debug, NOCAT); 3674887Schin #endif 3684887Schin } 3694887Schin if (cp->cat == NOCAT) 3704887Schin { 3714887Schin if (cp->debug) 3724887Schin { 3734887Schin p = tempget(state.tmp); 3744887Schin sfprintf(state.tmp, "(%s,%d,%d)", cp->name, mp->set, mp->seq); 3754887Schin r = tempuse(state.tmp, p); 3764887Schin } 3774887Schin else if (ast.locale.set & AST_LC_debug) 3784887Schin { 3794887Schin p = tempget(state.tmp); 3804887Schin sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r); 3814887Schin r = tempuse(state.tmp, p); 3824887Schin } 3834887Schin goto done; 3844887Schin } 3854887Schin 3864887Schin /* 3874887Schin * get the translated message 3884887Schin */ 3894887Schin 3904887Schin r = catgets(cp->cat, mp->set, mp->seq, msg); 3914887Schin if (ast.locale.set & AST_LC_translate) 3924887Schin 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); 3934887Schin if (r != (char*)msg) 3944887Schin { 3954887Schin if (streq(r, (char*)msg)) 3964887Schin r = (char*)msg; 3974887Schin else if (strcmp(fmtfmt(r), fmtfmt(msg))) 3984887Schin { 3994887Schin 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); 4004887Schin r = (char*)msg; 4014887Schin } 4024887Schin } 4034887Schin if (ast.locale.set & AST_LC_debug) 4044887Schin { 4054887Schin p = tempget(state.tmp); 4064887Schin sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r); 4074887Schin r = tempuse(state.tmp, p); 4084887Schin } 4094887Schin done: 4104887Schin if (r == (char*)msg && loc == state.debug) 4114887Schin { 4124887Schin p = tempget(state.tmp); 4134887Schin sfprintf(state.tmp, "(%s,%s,%s,\"%s\")", loc, cmd, cat, r); 4144887Schin r = tempuse(state.tmp, p); 4154887Schin } 4164887Schin errno = oerrno; 4174887Schin return r; 4184887Schin } 419