locale.c revision 133013
1/*-
2 * Copyright (c) 2002, 2003 Alexey Zelkin <phantom@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: head/usr.bin/locale/locale.c 133013 2004-08-02 12:28:28Z tjr $
27 */
28
29/*
30 * XXX: implement missing era_* (LC_TIME) keywords (require libc &
31 *	nl_langinfo(3) extensions)
32 *
33 * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
34 *      localedef(1) implementation).  Currently it's handled via
35 *	nl_langinfo(CODESET).
36 */
37
38#include <sys/types.h>
39#include <dirent.h>
40#include <err.h>
41#include <locale.h>
42#include <langinfo.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <stringlist.h>
47#include <unistd.h>
48#include "setlocale.h"
49
50/* Local prototypes */
51void	init_locales_list(void);
52void	list_charmaps(void);
53void	list_locales(void);
54const char *lookup_localecat(int);
55char	*kwval_lconv(int);
56int	kwval_lookup(char *, char **, int *, int *);
57void	showdetails(char *);
58void	showkeywordslist(void);
59void	showlocale(void);
60void	usage(void);
61
62/* Global variables */
63static StringList *locales = NULL;
64
65int	all_locales = 0;
66int	all_charmaps = 0;
67int	prt_categories = 0;
68int	prt_keywords = 0;
69int	more_params = 0;
70
71struct _lcinfo {
72	const char	*name;
73	int		id;
74} lcinfo [] = {
75	{ "LC_CTYPE",		LC_CTYPE },
76	{ "LC_COLLATE",		LC_COLLATE },
77	{ "LC_TIME",		LC_TIME },
78	{ "LC_NUMERIC",		LC_NUMERIC },
79	{ "LC_MONETARY",	LC_MONETARY },
80	{ "LC_MESSAGES",	LC_MESSAGES }
81};
82#define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
83
84/* ids for values not referenced by nl_langinfo() */
85#define	KW_ZERO			10000
86#define	KW_GROUPING		(KW_ZERO+1)
87#define KW_INT_CURR_SYMBOL 	(KW_ZERO+2)
88#define KW_CURRENCY_SYMBOL 	(KW_ZERO+3)
89#define KW_MON_DECIMAL_POINT 	(KW_ZERO+4)
90#define KW_MON_THOUSANDS_SEP 	(KW_ZERO+5)
91#define KW_MON_GROUPING 	(KW_ZERO+6)
92#define KW_POSITIVE_SIGN 	(KW_ZERO+7)
93#define KW_NEGATIVE_SIGN 	(KW_ZERO+8)
94#define KW_INT_FRAC_DIGITS 	(KW_ZERO+9)
95#define KW_FRAC_DIGITS 		(KW_ZERO+10)
96#define KW_P_CS_PRECEDES 	(KW_ZERO+11)
97#define KW_P_SEP_BY_SPACE 	(KW_ZERO+12)
98#define KW_N_CS_PRECEDES 	(KW_ZERO+13)
99#define KW_N_SEP_BY_SPACE 	(KW_ZERO+14)
100#define KW_P_SIGN_POSN 		(KW_ZERO+15)
101#define KW_N_SIGN_POSN 		(KW_ZERO+16)
102#define KW_INT_P_CS_PRECEDES 	(KW_ZERO+17)
103#define KW_INT_P_SEP_BY_SPACE 	(KW_ZERO+18)
104#define KW_INT_N_CS_PRECEDES 	(KW_ZERO+19)
105#define KW_INT_N_SEP_BY_SPACE 	(KW_ZERO+20)
106#define KW_INT_P_SIGN_POSN 	(KW_ZERO+21)
107#define KW_INT_N_SIGN_POSN 	(KW_ZERO+22)
108
109struct _kwinfo {
110	const char	*name;
111	int		isstr;		/* true - string, false - number */
112	int		catid;		/* LC_* */
113	int		value_ref;
114	const char	*comment;
115} kwinfo [] = {
116	{ "charmap",		1, LC_CTYPE,	CODESET, "" },	/* hack */
117
118	{ "decimal_point",	1, LC_NUMERIC,	RADIXCHAR, "" },
119	{ "thousands_sep",	1, LC_NUMERIC,	THOUSEP, "" },
120	{ "grouping",		1, LC_NUMERIC,	KW_GROUPING, "" },
121	{ "radixchar",		1, LC_NUMERIC,	RADIXCHAR,
122	  "Same as decimal_point (FreeBSD only)" },		/* compat */
123	{ "thousep",		1, LC_NUMERIC,	THOUSEP,
124	  "Same as thousands_sep (FreeBSD only)" },		/* compat */
125
126	{ "int_curr_symbol",	1, LC_MONETARY,	KW_INT_CURR_SYMBOL, "" },
127	{ "currency_symbol",	1, LC_MONETARY,	KW_CURRENCY_SYMBOL, "" },
128	{ "mon_decimal_point",	1, LC_MONETARY,	KW_MON_DECIMAL_POINT, "" },
129	{ "mon_thousands_sep",	1, LC_MONETARY,	KW_MON_THOUSANDS_SEP, "" },
130	{ "mon_grouping",	1, LC_MONETARY,	KW_MON_GROUPING, "" },
131	{ "positive_sign",	1, LC_MONETARY,	KW_POSITIVE_SIGN, "" },
132	{ "negative_sign",	1, LC_MONETARY,	KW_NEGATIVE_SIGN, "" },
133
134	{ "int_frac_digits",	0, LC_MONETARY,	KW_INT_FRAC_DIGITS, "" },
135	{ "frac_digits",	0, LC_MONETARY,	KW_FRAC_DIGITS, "" },
136	{ "p_cs_precedes",	0, LC_MONETARY,	KW_P_CS_PRECEDES, "" },
137	{ "p_sep_by_space",	0, LC_MONETARY,	KW_P_SEP_BY_SPACE, "" },
138	{ "n_cs_precedes",	0, LC_MONETARY,	KW_N_CS_PRECEDES, "" },
139	{ "n_sep_by_space",	0, LC_MONETARY,	KW_N_SEP_BY_SPACE, "" },
140	{ "p_sign_posn",	0, LC_MONETARY,	KW_P_SIGN_POSN, "" },
141	{ "n_sign_posn",	0, LC_MONETARY,	KW_N_SIGN_POSN, "" },
142	{ "int_p_cs_precedes",	0, LC_MONETARY,	KW_INT_P_CS_PRECEDES, "" },
143	{ "int_p_sep_by_space",	0, LC_MONETARY,	KW_INT_P_SEP_BY_SPACE, "" },
144	{ "int_n_cs_precedes",	0, LC_MONETARY,	KW_INT_N_CS_PRECEDES, "" },
145	{ "int_n_sep_by_space",	0, LC_MONETARY,	KW_INT_N_SEP_BY_SPACE, "" },
146	{ "int_p_sign_posn",	0, LC_MONETARY,	KW_INT_P_SIGN_POSN, "" },
147	{ "int_n_sign_posn",	0, LC_MONETARY,	KW_INT_N_SIGN_POSN, "" },
148
149	{ "d_t_fmt",		1, LC_TIME,	D_T_FMT, "" },
150	{ "d_fmt",		1, LC_TIME,	D_FMT, "" },
151	{ "t_fmt",		1, LC_TIME,	T_FMT, "" },
152	{ "am_str",		1, LC_TIME,	AM_STR, "" },
153	{ "pm_str",		1, LC_TIME,	PM_STR, "" },
154	{ "t_fmt_ampm",		1, LC_TIME,	T_FMT_AMPM, "" },
155	{ "day_1",		1, LC_TIME,	DAY_1, "" },
156	{ "day_2",		1, LC_TIME,	DAY_2, "" },
157	{ "day_3",		1, LC_TIME,	DAY_3, "" },
158	{ "day_4",		1, LC_TIME,	DAY_4, "" },
159	{ "day_5",		1, LC_TIME,	DAY_5, "" },
160	{ "day_6",		1, LC_TIME,	DAY_6, "" },
161	{ "day_7",		1, LC_TIME,	DAY_7, "" },
162	{ "abday_1",		1, LC_TIME,	ABDAY_1, "" },
163	{ "abday_2",		1, LC_TIME,	ABDAY_2, "" },
164	{ "abday_3",		1, LC_TIME,	ABDAY_3, "" },
165	{ "abday_4",		1, LC_TIME,	ABDAY_4, "" },
166	{ "abday_5",		1, LC_TIME,	ABDAY_5, "" },
167	{ "abday_6",		1, LC_TIME,	ABDAY_6, "" },
168	{ "abday_7",		1, LC_TIME,	ABDAY_7, "" },
169	{ "mon_1",		1, LC_TIME,	MON_1, "" },
170	{ "mon_2",		1, LC_TIME,	MON_2, "" },
171	{ "mon_3",		1, LC_TIME,	MON_3, "" },
172	{ "mon_4",		1, LC_TIME,	MON_4, "" },
173	{ "mon_5",		1, LC_TIME,	MON_5, "" },
174	{ "mon_6",		1, LC_TIME,	MON_6, "" },
175	{ "mon_7",		1, LC_TIME,	MON_7, "" },
176	{ "mon_8",		1, LC_TIME,	MON_8, "" },
177	{ "mon_9",		1, LC_TIME,	MON_9, "" },
178	{ "mon_10",		1, LC_TIME,	MON_10, "" },
179	{ "mon_11",		1, LC_TIME,	MON_11, "" },
180	{ "mon_12",		1, LC_TIME,	MON_12, "" },
181	{ "abmon_1",		1, LC_TIME,	ABMON_1, "" },
182	{ "abmon_2",		1, LC_TIME,	ABMON_2, "" },
183	{ "abmon_3",		1, LC_TIME,	ABMON_3, "" },
184	{ "abmon_4",		1, LC_TIME,	ABMON_4, "" },
185	{ "abmon_5",		1, LC_TIME,	ABMON_5, "" },
186	{ "abmon_6",		1, LC_TIME,	ABMON_6, "" },
187	{ "abmon_7",		1, LC_TIME,	ABMON_7, "" },
188	{ "abmon_8",		1, LC_TIME,	ABMON_8, "" },
189	{ "abmon_9",		1, LC_TIME,	ABMON_9, "" },
190	{ "abmon_10",		1, LC_TIME,	ABMON_10, "" },
191	{ "abmon_11",		1, LC_TIME,	ABMON_11, "" },
192	{ "abmon_12",		1, LC_TIME,	ABMON_12, "" },
193	{ "era",		1, LC_TIME,	ERA, "(unavailable)" },
194	{ "era_d_fmt",		1, LC_TIME,	ERA_D_FMT, "(unavailable)" },
195	{ "era_d_t_fmt",	1, LC_TIME,	ERA_D_T_FMT, "(unavailable)" },
196	{ "era_t_fmt",		1, LC_TIME,	ERA_T_FMT, "(unavailable)" },
197	{ "alt_digits",		1, LC_TIME,	ALT_DIGITS, "" },
198	{ "d_md_order",		1, LC_TIME,	D_MD_ORDER,
199	  "(FreeBSD only)"				},	/* local */
200
201	{ "yesexpr",		1, LC_MESSAGES, YESEXPR, "" },
202	{ "noexpr",		1, LC_MESSAGES, NOEXPR, "" },
203	{ "yesstr",		1, LC_MESSAGES, YESSTR,
204	  "(POSIX legacy)" },					/* compat */
205	{ "nostr",		1, LC_MESSAGES, NOSTR,
206	  "(POSIX legacy)" }					/* compat */
207
208};
209#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
210
211const char *boguslocales[] = { "UTF-8", "la_LN." };
212#define	NBOGUS	(sizeof(boguslocales)/sizeof(boguslocales[0]))
213
214int
215main(int argc, char *argv[])
216{
217	int	ch;
218	int	tmp;
219
220	while ((ch = getopt(argc, argv, "ackm")) != -1) {
221		switch (ch) {
222		case 'a':
223			all_locales = 1;
224			break;
225		case 'c':
226			prt_categories = 1;
227			break;
228		case 'k':
229			prt_keywords = 1;
230			break;
231		case 'm':
232			all_charmaps = 1;
233			break;
234		default:
235			usage();
236		}
237	}
238	argc -= optind;
239	argv += optind;
240
241	/* validate arguments */
242	if (all_locales && all_charmaps)
243		usage();
244	if ((all_locales || all_charmaps) && argc > 0)
245		usage();
246	if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
247		usage();
248	if ((prt_categories || prt_keywords) && argc <= 0)
249		usage();
250
251	/* process '-a' */
252	if (all_locales) {
253		list_locales();
254		exit(0);
255	}
256
257	/* process '-m' */
258	if (all_charmaps) {
259		list_charmaps();
260		exit(0);
261	}
262
263	/* check for special case '-k list' */
264	tmp = 0;
265	if (prt_keywords && argc > 0)
266		while (tmp < argc)
267			if (strcasecmp(argv[tmp++], "list") == 0) {
268				showkeywordslist();
269				exit(0);
270			}
271
272	/* process '-c' and/or '-k' */
273	if (prt_categories || prt_keywords || argc > 0) {
274		setlocale(LC_ALL, "");
275		while (argc > 0) {
276			showdetails(*argv);
277			argv++;
278			argc--;
279		}
280		exit(0);
281	}
282
283	/* no arguments, show current locale state */
284	showlocale();
285
286	return (0);
287}
288
289void
290usage(void)
291{
292	printf("Usage: locale [ -a | -m ]\n"
293               "       locale [ -ck ] name ...\n");
294	exit(1);
295}
296
297/*
298 * Output information about all available locales
299 *
300 * XXX actually output of this function does not guarantee that locale
301 *     is really available to application, since it can be broken or
302 *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
303 *     also validate these locales?
304 */
305void
306list_locales(void)
307{
308	size_t i;
309
310	init_locales_list();
311	for (i = 0; i < locales->sl_cur; i++) {
312		printf("%s\n", locales->sl_str[i]);
313	}
314}
315
316/*
317 * qsort() helper function
318 */
319static int
320scmp(const void *s1, const void *s2)
321{
322	return strcmp(*(const char **)s1, *(const char **)s2);
323}
324
325/*
326 * Output information about all available charmaps
327 *
328 * XXX this function is doing a task in hackish way, i.e. by scaning
329 *     list of locales, spliting their codeset part and building list of
330 *     them.
331 */
332void
333list_charmaps(void)
334{
335	size_t i;
336	char *s, *cs;
337	StringList *charmaps;
338
339	/* initialize StringList */
340	charmaps = sl_init();
341	if (charmaps == NULL)
342		err(1, "could not allocate memory");
343
344	/* fetch locales list */
345	init_locales_list();
346
347	/* split codesets and build their list */
348	for (i = 0; i < locales->sl_cur; i++) {
349		s = locales->sl_str[i];
350		if ((cs = strchr(s, '.')) != NULL) {
351			cs++;
352			if (sl_find(charmaps, cs) == NULL)
353				sl_add(charmaps, cs);
354		}
355	}
356
357	/* add US-ASCII, if not yet added */
358	if (sl_find(charmaps, "US-ASCII") == NULL)
359		sl_add(charmaps, "US-ASCII");
360
361	/* sort the list */
362	qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
363
364	/* print results */
365	for (i = 0; i < charmaps->sl_cur; i++) {
366		printf("%s\n", charmaps->sl_str[i]);
367	}
368}
369
370/*
371 * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
372 * environment variable is set)
373 */
374void
375init_locales_list(void)
376{
377	DIR *dirp;
378	struct dirent *dp;
379	size_t i;
380	int bogus;
381
382	/* why call this function twice ? */
383	if (locales != NULL)
384		return;
385
386	/* initialize StringList */
387	locales = sl_init();
388	if (locales == NULL)
389		err(1, "could not allocate memory");
390
391	/* get actual locales directory name */
392	if (__detect_path_locale() != 0)
393		err(1, "unable to find locales storage");
394
395	/* open locales directory */
396	dirp = opendir(_PathLocale);
397	if (dirp == NULL)
398		err(1, "could not open directory '%s'", _PathLocale);
399
400	/* scan directory and store its contents except "." and ".." */
401	while ((dp = readdir(dirp)) != NULL) {
402		if (*(dp->d_name) == '.')
403			continue;		/* exclude "." and ".." */
404		for (bogus = i = 0; i < NBOGUS; i++)
405			if (strncmp(dp->d_name, boguslocales[i],
406			    strlen(boguslocales[i])) == 0)
407				bogus = 1;
408		if (!bogus)
409			sl_add(locales, strdup(dp->d_name));
410	}
411	closedir(dirp);
412
413        /* make sure that 'POSIX' and 'C' locales are present in the list.
414	 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
415         * we also list 'C' for constistency
416         */
417	if (sl_find(locales, "POSIX") == NULL)
418		sl_add(locales, "POSIX");
419
420	if (sl_find(locales, "C") == NULL)
421		sl_add(locales, "C");
422
423	/* make output nicer, sort the list */
424	qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
425}
426
427/*
428 * Show current locale status, depending on environment variables
429 */
430void
431showlocale(void)
432{
433	size_t	i;
434	const char *lang, *vval, *eval;
435
436	setlocale(LC_ALL, "");
437
438	lang = getenv("LANG");
439	if (lang == NULL) {
440		lang = "";
441	}
442	printf("LANG=%s\n", lang);
443	/* XXX: if LANG is null, then set it to "C" to get implied values? */
444
445	for (i = 0; i < NLCINFO; i++) {
446		vval = setlocale(lcinfo[i].id, NULL);
447		eval = getenv(lcinfo[i].name);
448		if (eval != NULL && !strcmp(eval, vval)
449				&& strcmp(lang, vval)) {
450			/*
451			 * Appropriate environment variable set, its value
452			 * is valid and not overriden by LC_ALL
453			 *
454			 * XXX: possible side effect: if both LANG and
455			 * overriden environment variable are set into same
456			 * value, then it'll be assumed as 'implied'
457			 */
458			printf("%s=%s\n", lcinfo[i].name, vval);
459		} else {
460			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
461		}
462	}
463
464	vval = getenv("LC_ALL");
465	if (vval == NULL) {
466		vval = "";
467	}
468	printf("LC_ALL=%s\n", vval);
469}
470
471/*
472 * keyword value lookup helper (via localeconv())
473 */
474char *
475kwval_lconv(int id)
476{
477	struct lconv *lc;
478	char *rval;
479
480	rval = NULL;
481	lc = localeconv();
482	switch (id) {
483		case KW_GROUPING:
484			rval = lc->grouping;
485			break;
486		case KW_INT_CURR_SYMBOL:
487			rval = lc->int_curr_symbol;
488			break;
489		case KW_CURRENCY_SYMBOL:
490			rval = lc->currency_symbol;
491			break;
492		case KW_MON_DECIMAL_POINT:
493			rval = lc->mon_decimal_point;
494			break;
495		case KW_MON_THOUSANDS_SEP:
496			rval = lc->mon_thousands_sep;
497			break;
498		case KW_MON_GROUPING:
499			rval = lc->mon_grouping;
500			break;
501		case KW_POSITIVE_SIGN:
502			rval = lc->positive_sign;
503			break;
504		case KW_NEGATIVE_SIGN:
505			rval = lc->negative_sign;
506			break;
507		case KW_INT_FRAC_DIGITS:
508			rval = &(lc->int_frac_digits);
509			break;
510		case KW_FRAC_DIGITS:
511			rval = &(lc->frac_digits);
512			break;
513		case KW_P_CS_PRECEDES:
514			rval = &(lc->p_cs_precedes);
515			break;
516		case KW_P_SEP_BY_SPACE:
517			rval = &(lc->p_sep_by_space);
518			break;
519		case KW_N_CS_PRECEDES:
520			rval = &(lc->n_cs_precedes);
521			break;
522		case KW_N_SEP_BY_SPACE:
523			rval = &(lc->n_sep_by_space);
524			break;
525		case KW_P_SIGN_POSN:
526			rval = &(lc->p_sign_posn);
527			break;
528		case KW_N_SIGN_POSN:
529			rval = &(lc->n_sign_posn);
530			break;
531		case KW_INT_P_CS_PRECEDES:
532			rval = &(lc->int_p_cs_precedes);
533			break;
534		case KW_INT_P_SEP_BY_SPACE:
535			rval = &(lc->int_p_sep_by_space);
536			break;
537		case KW_INT_N_CS_PRECEDES:
538			rval = &(lc->int_n_cs_precedes);
539			break;
540		case KW_INT_N_SEP_BY_SPACE:
541			rval = &(lc->int_n_sep_by_space);
542			break;
543		case KW_INT_P_SIGN_POSN:
544			rval = &(lc->int_p_sign_posn);
545			break;
546		case KW_INT_N_SIGN_POSN:
547			rval = &(lc->int_n_sign_posn);
548			break;
549		default:
550			break;
551	}
552	return (rval);
553}
554
555/*
556 * keyword value and properties lookup
557 */
558int
559kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
560{
561	int	rval;
562	size_t	i;
563
564	rval = 0;
565	for (i = 0; i < NKWINFO; i++) {
566		if (strcasecmp(kwname, kwinfo[i].name) == 0) {
567			rval = 1;
568			*cat = kwinfo[i].catid;
569			*isstr = kwinfo[i].isstr;
570			if (kwinfo[i].value_ref < KW_ZERO) {
571				*kwval = nl_langinfo(kwinfo[i].value_ref);
572			} else {
573				*kwval = kwval_lconv(kwinfo[i].value_ref);
574			}
575			break;
576		}
577	}
578
579	return (rval);
580}
581
582/*
583 * Show details about requested keyword according to '-k' and/or '-c'
584 * command line options specified.
585 */
586void
587showdetails(char *kw)
588{
589	int	isstr, cat, tmpval;
590	char	*kwval;
591
592	if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
593		/*
594		 * invalid keyword specified.
595		 * XXX: any actions?
596		 */
597		return;
598	}
599
600	if (prt_categories) {
601		printf("%s\n", lookup_localecat(cat));
602	}
603
604	if (prt_keywords) {
605		if (isstr) {
606			printf("%s=\"%s\"\n", kw, kwval);
607		} else {
608			tmpval = (char) *kwval;
609			printf("%s=%d\n", kw, tmpval);
610		}
611	}
612
613	if (!prt_categories && !prt_keywords) {
614		if (isstr) {
615			printf("%s\n", kwval);
616		} else {
617			tmpval = (char) *kwval;
618			printf("%d\n", tmpval);
619		}
620	}
621}
622
623/*
624 * Convert locale category id into string
625 */
626const char *
627lookup_localecat(int cat)
628{
629	size_t	i;
630
631	for (i = 0; i < NLCINFO; i++)
632		if (lcinfo[i].id == cat) {
633			return (lcinfo[i].name);
634		}
635	return ("UNKNOWN");
636}
637
638/*
639 * Show list of keywords
640 */
641void
642showkeywordslist(void)
643{
644	size_t	i;
645
646#define FMT "%-20s %-12s %-7s %-20s\n"
647
648	printf("List of available keywords\n\n");
649	printf(FMT, "Keyword", "Category", "Type", "Comment");
650	printf("-------------------- ------------ ------- --------------------\n");
651	for (i = 0; i < NKWINFO; i++) {
652		printf(FMT,
653			kwinfo[i].name,
654			lookup_localecat(kwinfo[i].catid),
655			(kwinfo[i].isstr == 0) ? "number" : "string",
656			kwinfo[i].comment);
657	}
658}
659