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
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 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*
init(register char * s)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*
match(const char * cat,const char * msg)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*
translate(const char * loc,const char * cmd,const char * cat,const char * msg)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