1 /***********************************************************************
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 *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
13 * Information and Software Systems Research *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
19 * Phong Vo <kpv@research.att.com> *
21 ***********************************************************************/
25 * AT&T Research and SCO
26 * ast l10n message translation
40 #define NOCAT ((nl_catd)-1)
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 */
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 */
64 Sfio_t
* sp
; /* temp string stream */
65 int off
; /* string base offset */
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 */
79 static State_t state
=
81 { offsetof(Message_t
, text
), 0, 0 },
82 { offsetof(Catalog_t
, name
), 0, 0 },
88 if (sfstrtell(sp
) > sfstrsize(sp
) / 2)
89 sfstrseek(sp
, 0, SEEK_SET
);
94 tempuse(Sfio_t
* sp
, int off
)
97 return sfstrbase(sp
) + off
;
105 entry(Dt_t
* dict
, int set
, int seq
, const char* msg
)
109 if (!(mp
= newof(0, Message_t
, 1, strlen(msg
))))
111 strcpy(mp
->text
, msg
);
114 if (!dtinsert(dict
, mp
))
120 sfprintf(sfstderr
, "AHA#%d:%s set %d seq %d msg `%s'\n", __LINE__
, __FILE__
, set
, seq
, msg
);
126 * find catalog in locale and return catopen() descriptor
130 find(const char* locale
, const char* catalog
)
134 const char* ocatalog
= catalog
;
137 if (mcfind(path
, locale
, catalog
, LC_MESSAGES
, 0))
138 catalog
= (const char*)path
;
140 sfprintf(sfstderr
, "AHA#%d:%s %s %s %s\n", __LINE__
, __FILE__
, locale
, ocatalog
, catalog
);
142 return catopen(catalog
, NL_CAT_LOCALE
);
146 * initialize the catalog s by loading in the default locale messages
150 init(register char* s
)
152 register Catalog_t
* cp
;
159 * insert into the catalog dictionary
162 if (!(cp
= newof(0, Catalog_t
, 1, strlen(s
))))
165 if (!dtinsert(state
.catalogs
, cp
))
173 * locate the default locale catalog
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
)
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()
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
194 if (cp
->messages
= dtopen(&state
.message_disc
, Dtset
))
200 if ((s
= catgets(d
, AST_MESSAGE_SET
, n
, state
.null
)) != state
.null
&& entry(cp
->messages
, AST_MESSAGE_SET
, n
, s
))
202 else if ((n
- m
) > GAP
)
207 dtclose(cp
->messages
);
213 setlocale(LC_MESSAGES
, u
);
214 ast
.locale
.set
&= ~AST_LC_internal
;
219 * return the C locale message pointer for msg in cat
220 * cat may be a : separated list of candidate names
224 match(const char* cat
, const char* msg
)
237 if (t
= strchr(s
, ':'))
241 if ((n
= strlen(s
)) >= sizeof(buf
))
243 s
= (char*)memcpy(buf
, s
, n
);
249 if (*s
&& ((cp
= (Catalog_t
*)dtmatch(state
.catalogs
, s
)) || (cp
= init(s
))) && cp
->messages
&& (mp
= (Message_t
*)dtmatch(cp
->messages
, msg
)))
262 * translate() is called with four arguments:
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
272 * the translated message text is returned on success
273 * otherwise the original msg is returned
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
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.
290 translate(const char* loc
, const char* cmd
, const char* cat
, const char* msg
)
308 if (cmd
&& (t
= strrchr(cmd
, '/')))
309 cmd
= (const char*)(t
+ 1);
312 * initialize the catalogs dictionary
319 if (!(state
.tmp
= sfstropen()))
324 if (!(state
.catalogs
= dtopen(&state
.catalog_disc
, Dtset
)))
330 if (streq(loc
, "debug"))
336 * or do we have to spell it out for you
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
))) ||
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
);
352 * adjust for the current locale
356 sfprintf(sfstderr
, "AHA#%d:%s cp->locale `%s' %p loc `%s' %p\n", __LINE__
, __FILE__
, cp
->locale
, cp
->locale
, loc
, loc
);
358 if (cp
->locale
!= loc
)
361 if (cp
->cat
!= NOCAT
)
363 if ((cp
->cat
= find(cp
->locale
, cp
->name
)) == NOCAT
)
364 cp
->debug
= streq(cp
->locale
, "debug");
368 sfprintf(sfstderr
, "AHA#%d:%s cp->cat %p cp->debug %d NOCAT %p\n", __LINE__
, __FILE__
, cp
->cat
, cp
->debug
, NOCAT
);
371 if (cp
->cat
== NOCAT
)
375 p
= tempget(state
.tmp
);
376 sfprintf(state
.tmp
, "(%s,%d,%d)", cp
->name
, mp
->set
, mp
->seq
);
377 r
= tempuse(state
.tmp
, p
);
379 else if (ast
.locale
.set
& AST_LC_debug
)
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
);
389 * get the translated message
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
);
397 if (streq(r
, (char*)msg
))
399 else if (strcmp(fmtfmt(r
), fmtfmt(msg
)))
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
);
405 if (ast
.locale
.set
& AST_LC_debug
)
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
);
412 if (r
== (char*)msg
&& loc
== state
.debug
)
414 p
= tempget(state
.tmp
);
415 sfprintf(state
.tmp
, "(%s,%s,%s,\"%s\")", loc
, cmd
, cat
, r
);
416 r
= tempuse(state
.tmp
, p
);