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