xref: /onnv-gate/usr/src/lib/libast/common/misc/translate.c (revision 8462:6e341f5569ba)
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