xref: /titanic_44/usr/src/lib/libast/common/misc/translate.c (revision 8339b41da2395f0525c46ceedfeb01961893ec44)
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