1da2e3ebdSchin /***********************************************************************
2da2e3ebdSchin * *
3da2e3ebdSchin * This software is part of the ast package *
4*3e14f97fSRoger A. Faulkner * Copyright (c) 1985-2010 AT&T Intellectual Property *
5da2e3ebdSchin * and is licensed under the *
6da2e3ebdSchin * Common Public License, Version 1.0 *
77c2fbfb3SApril Chin * by AT&T Intellectual Property *
8da2e3ebdSchin * *
9da2e3ebdSchin * A copy of the License is available at *
10da2e3ebdSchin * http://www.opensource.org/licenses/cpl1.0.txt *
11da2e3ebdSchin * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
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 */
50da2e3ebdSchin char name[1]; /* catalog name */
51da2e3ebdSchin } Catalog_t;
52da2e3ebdSchin
53da2e3ebdSchin typedef struct
54da2e3ebdSchin {
55da2e3ebdSchin Dtlink_t link; /* dictionary link */
56da2e3ebdSchin Catalog_t* cat; /* current catalog pointer */
57da2e3ebdSchin int set; /* set number */
58da2e3ebdSchin int seq; /* sequence number */
59da2e3ebdSchin char text[1]; /* message text */
60da2e3ebdSchin } Message_t;
61da2e3ebdSchin
62da2e3ebdSchin typedef struct
63da2e3ebdSchin {
64da2e3ebdSchin Sfio_t* sp; /* temp string stream */
65da2e3ebdSchin int off; /* string base offset */
66da2e3ebdSchin } Temp_t;
67da2e3ebdSchin
68da2e3ebdSchin typedef struct
69da2e3ebdSchin {
70da2e3ebdSchin Dtdisc_t message_disc; /* message dict discipline */
71da2e3ebdSchin Dtdisc_t catalog_disc; /* catalog dict discipline */
72da2e3ebdSchin Dt_t* catalogs; /* catalog dictionary handle */
73da2e3ebdSchin Sfio_t* tmp; /* temporary string stream */
74da2e3ebdSchin const char* debug; /* debug locale name */
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 {
132da2e3ebdSchin char path[PATH_MAX];
133da2e3ebdSchin #if DEBUG_trace
134da2e3ebdSchin const char* ocatalog = catalog;
135da2e3ebdSchin #endif
136da2e3ebdSchin
137da2e3ebdSchin if (mcfind(path, locale, catalog, LC_MESSAGES, 0))
138da2e3ebdSchin catalog = (const char*)path;
139da2e3ebdSchin #if DEBUG_trace
140da2e3ebdSchin sfprintf(sfstderr, "AHA#%d:%s %s %s %s\n", __LINE__, __FILE__, locale, ocatalog, catalog);
141da2e3ebdSchin #endif
142da2e3ebdSchin return catopen(catalog, NL_CAT_LOCALE);
143da2e3ebdSchin }
144da2e3ebdSchin
145da2e3ebdSchin /*
146da2e3ebdSchin * initialize the catalog s by loading in the default locale messages
147da2e3ebdSchin */
148da2e3ebdSchin
149da2e3ebdSchin static Catalog_t*
init(register char * s)150da2e3ebdSchin init(register char* s)
151da2e3ebdSchin {
152da2e3ebdSchin register Catalog_t* cp;
153da2e3ebdSchin register char* u;
154da2e3ebdSchin register int n;
155da2e3ebdSchin register int m;
156da2e3ebdSchin nl_catd d;
157da2e3ebdSchin
158da2e3ebdSchin /*
159da2e3ebdSchin * insert into the catalog dictionary
160da2e3ebdSchin */
161da2e3ebdSchin
162da2e3ebdSchin if (!(cp = newof(0, Catalog_t, 1, strlen(s))))
163da2e3ebdSchin return 0;
164da2e3ebdSchin strcpy(cp->name, s);
165da2e3ebdSchin if (!dtinsert(state.catalogs, cp))
166da2e3ebdSchin {
167da2e3ebdSchin free(cp);
168da2e3ebdSchin return 0;
169da2e3ebdSchin }
170da2e3ebdSchin cp->cat = NOCAT;
171da2e3ebdSchin
172da2e3ebdSchin /*
173da2e3ebdSchin * locate the default locale catalog
174da2e3ebdSchin */
175da2e3ebdSchin
176*3e14f97fSRoger A. Faulkner ast.locale.set |= AST_LC_internal;
177da2e3ebdSchin u = setlocale(LC_MESSAGES, NiL);
178da2e3ebdSchin setlocale(LC_MESSAGES, "C");
179da2e3ebdSchin if ((d = find("C", s)) != NOCAT)
180da2e3ebdSchin {
181da2e3ebdSchin /*
182da2e3ebdSchin * load the default locale messages
183da2e3ebdSchin * this assumes one mesage set for ast (AST_MESSAGE_SET)
184da2e3ebdSchin * different packages can share the same message catalog
185da2e3ebdSchin * name by using different message set numbers
186da2e3ebdSchin * see <mc.h> mcindex()
187da2e3ebdSchin *
188da2e3ebdSchin * this method requires a scan of each catalog, and the
189da2e3ebdSchin * catalogs do not advertize the max message number, so
190da2e3ebdSchin * we assume there are no messages after a gap of GAP
191da2e3ebdSchin * missing messages
192da2e3ebdSchin */
193da2e3ebdSchin
194da2e3ebdSchin if (cp->messages = dtopen(&state.message_disc, Dtset))
195da2e3ebdSchin {
196da2e3ebdSchin n = m = 0;
197da2e3ebdSchin for (;;)
198da2e3ebdSchin {
199da2e3ebdSchin n++;
200da2e3ebdSchin if ((s = catgets(d, AST_MESSAGE_SET, n, state.null)) != state.null && entry(cp->messages, AST_MESSAGE_SET, n, s))
201da2e3ebdSchin m = n;
202da2e3ebdSchin else if ((n - m) > GAP)
203da2e3ebdSchin break;
204da2e3ebdSchin }
205da2e3ebdSchin if (!m)
206da2e3ebdSchin {
207da2e3ebdSchin dtclose(cp->messages);
208da2e3ebdSchin cp->messages = 0;
209da2e3ebdSchin }
210da2e3ebdSchin }
211da2e3ebdSchin catclose(d);
212da2e3ebdSchin }
213da2e3ebdSchin setlocale(LC_MESSAGES, u);
214*3e14f97fSRoger A. Faulkner ast.locale.set &= ~AST_LC_internal;
215da2e3ebdSchin return cp;
216da2e3ebdSchin }
217da2e3ebdSchin
218da2e3ebdSchin /*
219da2e3ebdSchin * return the C locale message pointer for msg in cat
220da2e3ebdSchin * cat may be a : separated list of candidate names
221da2e3ebdSchin */
222da2e3ebdSchin
223da2e3ebdSchin static Message_t*
match(const char * cat,const char * msg)224da2e3ebdSchin match(const char* cat, const char* msg)
225da2e3ebdSchin {
226da2e3ebdSchin register char* s;
227da2e3ebdSchin register char* t;
228da2e3ebdSchin Catalog_t* cp;
229da2e3ebdSchin Message_t* mp;
230da2e3ebdSchin size_t n;
231da2e3ebdSchin
232da2e3ebdSchin char buf[1024];
233da2e3ebdSchin
234da2e3ebdSchin s = (char*)cat;
235da2e3ebdSchin for (;;)
236da2e3ebdSchin {
237da2e3ebdSchin if (t = strchr(s, ':'))
238da2e3ebdSchin {
239da2e3ebdSchin if (s == (char*)cat)
240da2e3ebdSchin {
241da2e3ebdSchin if ((n = strlen(s)) >= sizeof(buf))
242da2e3ebdSchin n = sizeof(buf) - 1;
243da2e3ebdSchin s = (char*)memcpy(buf, s, n);
244da2e3ebdSchin s[n] = 0;
245da2e3ebdSchin t = strchr(s, ':');
246da2e3ebdSchin }
247da2e3ebdSchin *t = 0;
248da2e3ebdSchin }
249da2e3ebdSchin if (*s && ((cp = (Catalog_t*)dtmatch(state.catalogs, s)) || (cp = init(s))) && cp->messages && (mp = (Message_t*)dtmatch(cp->messages, msg)))
250da2e3ebdSchin {
251da2e3ebdSchin mp->cat = cp;
252da2e3ebdSchin return mp;
253da2e3ebdSchin }
254da2e3ebdSchin if (!t)
255da2e3ebdSchin break;
256da2e3ebdSchin s = t + 1;
257da2e3ebdSchin }
258da2e3ebdSchin return 0;
259da2e3ebdSchin }
260da2e3ebdSchin
261da2e3ebdSchin /*
262da2e3ebdSchin * translate() is called with four arguments:
263da2e3ebdSchin *
264da2e3ebdSchin * loc the LC_MESSAGES locale name
265da2e3ebdSchin * cmd the calling command name
266da2e3ebdSchin * cat the catalog name, possibly a : separated list
267da2e3ebdSchin * "libFOO" FOO library messages
268da2e3ebdSchin * "libshell" ksh command messages
269da2e3ebdSchin * "SCRIPT" script SCRIPT application messages
270da2e3ebdSchin * msg message text to be translated
271da2e3ebdSchin *
272da2e3ebdSchin * the translated message text is returned on success
273da2e3ebdSchin * otherwise the original msg is returned
274da2e3ebdSchin *
275da2e3ebdSchin * The first time translate() is called (for a non-C locale)
276da2e3ebdSchin * it creates the state.catalogs dictionary. A dictionary entry
277da2e3ebdSchin * (Catalog_t) is made each time translate() is called with a new
278da2e3ebdSchin * cmd:cat argument.
279da2e3ebdSchin *
280da2e3ebdSchin * The X/Open interface catgets() is used to obtain a translated
281da2e3ebdSchin * message. Its arguments include the message catalog name
282da2e3ebdSchin * and the set/sequence numbers within the catalog. An additional
283da2e3ebdSchin * dictionary, with entries of type Message_t, is needed for
284da2e3ebdSchin * mapping untranslated message strings to the set/sequence numbers
285da2e3ebdSchin * needed by catgets(). A separate Message_t dictionary is maintained
286da2e3ebdSchin * for each Catalog_t.
287da2e3ebdSchin */
288da2e3ebdSchin
289da2e3ebdSchin char*
translate(const char * loc,const char * cmd,const char * cat,const char * msg)290da2e3ebdSchin translate(const char* loc, const char* cmd, const char* cat, const char* msg)
291da2e3ebdSchin {
292da2e3ebdSchin register char* r;
293da2e3ebdSchin char* t;
294da2e3ebdSchin int p;
295da2e3ebdSchin int oerrno;
296da2e3ebdSchin Catalog_t* cp;
297da2e3ebdSchin Message_t* mp;
298da2e3ebdSchin
299da2e3ebdSchin oerrno = errno;
300da2e3ebdSchin r = (char*)msg;
301da2e3ebdSchin
302da2e3ebdSchin /*
303da2e3ebdSchin * quick out
304da2e3ebdSchin */
305da2e3ebdSchin
306da2e3ebdSchin if (!cmd && !cat)
307da2e3ebdSchin goto done;
308da2e3ebdSchin if (cmd && (t = strrchr(cmd, '/')))
309da2e3ebdSchin cmd = (const char*)(t + 1);
310da2e3ebdSchin
311da2e3ebdSchin /*
312da2e3ebdSchin * initialize the catalogs dictionary
313da2e3ebdSchin */
314da2e3ebdSchin
315da2e3ebdSchin if (!state.catalogs)
316da2e3ebdSchin {
317da2e3ebdSchin if (state.error)
318da2e3ebdSchin goto done;
319da2e3ebdSchin if (!(state.tmp = sfstropen()))
320da2e3ebdSchin {
321da2e3ebdSchin state.error = 1;
322da2e3ebdSchin goto done;
323da2e3ebdSchin }
324da2e3ebdSchin if (!(state.catalogs = dtopen(&state.catalog_disc, Dtset)))
325da2e3ebdSchin {
326da2e3ebdSchin sfclose(state.tmp);
327da2e3ebdSchin state.error = 1;
328da2e3ebdSchin goto done;
329da2e3ebdSchin }
330da2e3ebdSchin if (streq(loc, "debug"))
331da2e3ebdSchin state.debug = loc;
332da2e3ebdSchin }
333da2e3ebdSchin
334da2e3ebdSchin /*
335da2e3ebdSchin * get the message
336da2e3ebdSchin * or do we have to spell it out for you
337da2e3ebdSchin */
338da2e3ebdSchin
339da2e3ebdSchin if ((!cmd || !(mp = match(cmd, msg))) &&
340da2e3ebdSchin (!cat || !(mp = match(cat, msg))) &&
341da2e3ebdSchin (!error_info.catalog || !(mp = match(error_info.catalog, msg))) &&
342da2e3ebdSchin (!ast.id || !(mp = match(ast.id, msg))) ||
343da2e3ebdSchin !(cp = mp->cat))
344da2e3ebdSchin {
345da2e3ebdSchin #if DEBUG_trace > 1
346da2e3ebdSchin 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);
347da2e3ebdSchin #endif
348da2e3ebdSchin goto done;
349da2e3ebdSchin }
350da2e3ebdSchin
351da2e3ebdSchin /*
352da2e3ebdSchin * adjust for the current locale
353da2e3ebdSchin */
354da2e3ebdSchin
355da2e3ebdSchin #if DEBUG_trace
356da2e3ebdSchin sfprintf(sfstderr, "AHA#%d:%s cp->locale `%s' %p loc `%s' %p\n", __LINE__, __FILE__, cp->locale, cp->locale, loc, loc);
357da2e3ebdSchin #endif
358da2e3ebdSchin if (cp->locale != loc)
359da2e3ebdSchin {
360da2e3ebdSchin cp->locale = loc;
361da2e3ebdSchin if (cp->cat != NOCAT)
362da2e3ebdSchin catclose(cp->cat);
363da2e3ebdSchin if ((cp->cat = find(cp->locale, cp->name)) == NOCAT)
364da2e3ebdSchin cp->debug = streq(cp->locale, "debug");
365da2e3ebdSchin else
366da2e3ebdSchin cp->debug = 0;
367da2e3ebdSchin #if DEBUG_trace
368da2e3ebdSchin sfprintf(sfstderr, "AHA#%d:%s cp->cat %p cp->debug %d NOCAT %p\n", __LINE__, __FILE__, cp->cat, cp->debug, NOCAT);
369da2e3ebdSchin #endif
370da2e3ebdSchin }
371da2e3ebdSchin if (cp->cat == NOCAT)
372da2e3ebdSchin {
373da2e3ebdSchin if (cp->debug)
374da2e3ebdSchin {
375da2e3ebdSchin p = tempget(state.tmp);
376da2e3ebdSchin sfprintf(state.tmp, "(%s,%d,%d)", cp->name, mp->set, mp->seq);
377da2e3ebdSchin r = tempuse(state.tmp, p);
378da2e3ebdSchin }
379da2e3ebdSchin else if (ast.locale.set & AST_LC_debug)
380da2e3ebdSchin {
381da2e3ebdSchin p = tempget(state.tmp);
382da2e3ebdSchin sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r);
383da2e3ebdSchin r = tempuse(state.tmp, p);
384da2e3ebdSchin }
385da2e3ebdSchin goto done;
386da2e3ebdSchin }
387da2e3ebdSchin
388da2e3ebdSchin /*
389da2e3ebdSchin * get the translated message
390da2e3ebdSchin */
391da2e3ebdSchin
392da2e3ebdSchin r = catgets(cp->cat, mp->set, mp->seq, msg);
393da2e3ebdSchin if (ast.locale.set & AST_LC_translate)
394da2e3ebdSchin 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);
395da2e3ebdSchin if (r != (char*)msg)
396da2e3ebdSchin {
397da2e3ebdSchin if (streq(r, (char*)msg))
398da2e3ebdSchin r = (char*)msg;
399da2e3ebdSchin else if (strcmp(fmtfmt(r), fmtfmt(msg)))
400da2e3ebdSchin {
401da2e3ebdSchin 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);
402da2e3ebdSchin r = (char*)msg;
403da2e3ebdSchin }
404da2e3ebdSchin }
405da2e3ebdSchin if (ast.locale.set & AST_LC_debug)
406da2e3ebdSchin {
407da2e3ebdSchin p = tempget(state.tmp);
408da2e3ebdSchin sfprintf(state.tmp, "(%s,%d,%d)%s", cp->name, mp->set, mp->seq, r);
409da2e3ebdSchin r = tempuse(state.tmp, p);
410da2e3ebdSchin }
411da2e3ebdSchin done:
412da2e3ebdSchin if (r == (char*)msg && loc == state.debug)
413da2e3ebdSchin {
414da2e3ebdSchin p = tempget(state.tmp);
415da2e3ebdSchin sfprintf(state.tmp, "(%s,%s,%s,\"%s\")", loc, cmd, cat, r);
416da2e3ebdSchin r = tempuse(state.tmp, p);
417da2e3ebdSchin }
418da2e3ebdSchin errno = oerrno;
419da2e3ebdSchin return r;
420da2e3ebdSchin }
421