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