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