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