/*
* language.c -- handles:
* language support code
*
* $Id: language.c,v 1.26 2006-03-28 02:35:50 wcc Exp $
*/
/*
* Copyright (C) 1997 Robey Pointer
* Copyright (C) 1999 - 2006 Eggheads Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* DOES:
* Nothing <- typical BB code :)
*
* ENVIRONMENT VARIABLES:
* EGG_LANG - language to use (default: "english")
* EGG_LANGDIR - directory with all lang files
* (default: "./language")
* WILL DO:
* Upon loading:
* o default loads section core, if possible.
* Commands:
* DCC .+lang <language>
* DCC .-lang <language>
* DCC .+lsec <section>
* DCC .-lsec <section>
* DCC .relang
* DCC .ldump
* DCC .lstat
*
* FILE FORMAT: language.lang
* <textidx>,<text>
* TEXT MESSAGE USAGE:
* get_language(<textidx> [,<PARMS>])
*
* ADDING LANGUAGES:
* o Copy an existing <section>.<oldlanguage>.lang to a
* new .lang file and modify as needed.
* Use %s or %d where necessary, for plug-in
* insertions of parameters (see core.english.lang).
* o Ensure <section>.<newlanguage>.lang is in the lang
* directory.
* o .+lang <newlanguage>
* ADDING SECTIONS:
* o Create a <newsection>.english.lang file.
* o Add add_lang_section("<newsection>"); to your module
* startup function.
*
*/
#include "main.h"
extern struct dcc_t *dcc;
typedef struct lang_st {
struct lang_st *next;
char *lang;
char *section;
} lang_sec;
typedef struct lang_pr {
struct lang_pr *next;
char *lang;
} lang_pri;
typedef struct lang_t {
int idx;
char *text;
struct lang_t *next;
} lang_tab;
static lang_tab *langtab[64];
static lang_sec *langsection = NULL;
static lang_pri *langpriority = NULL;
static int del_lang(char *);
static int add_message(int, char *);
static void recheck_lang_sections(void);
static void read_lang(char *);
void add_lang_section(char *);
int del_lang_section(char *);
int exist_lang_section(char *);
static char *get_specific_langfile(char *, lang_sec *);
static char *get_langfile(lang_sec *);
static int split_lang(char *, char **, char **);
int cmd_loadlanguage(struct userrec *, int, char *);
/* Add a new preferred language to the list of languages. Newly added
* languages get the highest priority.
*/
void add_lang(char *lang)
{
lang_pri *lp = langpriority, *lpo = NULL;
while (lp) {
/* The language already exists, moving to the beginning */
if (!strcmp(lang, lp->lang)) {
/* Already at the front? */
if (!lpo)
return;
lpo->next = lp->next;
lp->next = lpo;
langpriority = lp;
return;
}
lpo = lp;
lp = lp->next;
}
/* No existing entry, create a new one */
lp = nmalloc(sizeof(lang_pri));
lp->lang = nmalloc(strlen(lang) + 1);
strcpy(lp->lang, lang);
lp->next = NULL;
/* If we have other entries, point to the beginning of the old list */
if (langpriority)
lp->next = langpriority;
langpriority = lp;
debug1("LANG: Language loaded: %s", lang);
}
/* Remove a language from the list of preferred languages.
*/
static int del_lang(char *lang)
{
lang_pri *lp = langpriority, *lpo = NULL;
while (lp) {
/* Found the language? */
if (!strcmp(lang, lp->lang)) {
if (lpo)
lpo->next = lp->next;
else
langpriority = lp->next;
if (lp->lang)
nfree(lp->lang);
nfree(lp);
debug1("LANG: Language unloaded: %s", lang);
return 1;
}
lpo = lp;
lp = lp->next;
}
/* Language not found */
return 0;
}
static int add_message(int lidx, char *ltext)
{
lang_tab *l = langtab[lidx & 63];
while (l) {
if (l->idx && (l->idx == lidx)) {
nfree(l->text);
l->text = nmalloc(strlen(ltext) + 1);
strcpy(l->text, ltext);
return 1;
}
if (!l->next)
break;
l = l->next;
}
if (l) {
l->next = nmalloc(sizeof(lang_tab));
l = l->next;
} else
l = langtab[lidx & 63] = nmalloc(sizeof(lang_tab));
l->idx = lidx;
l->text = nmalloc(strlen(ltext) + 1);
strcpy(l->text, ltext);
l->next = 0;
return 0;
}
/* Recheck all sections and check if any language files are available
* which match the preferred language(s) more closely
*/
static void recheck_lang_sections(void)
{
lang_sec *ls;
char *langfile;
for (ls = langsection; ls && ls->section; ls = ls->next) {
langfile = get_langfile(ls);
/* Found a language with a more preferred language? */
if (langfile) {
read_lang(langfile);
nfree(langfile);
}
}
}
/* Parse a language file
*/
static void read_lang(char *langfile)
{
FILE *FLANG;
char lbuf[512];
char *ltext = NULL;
char *ctmp, *ctmp1;
int lidx;
int lline = 0;
int lskip;
int ltexts = 0;
int ladd = 0, lupdate = 0;
FLANG = fopen(langfile, "r");
if (FLANG == NULL) {
putlog(LOG_MISC, "*", "LANG: unexpected: reading from file %s failed.",
langfile);
return;
}
lskip = 0;
while (fgets(lbuf, 511, FLANG)) {
lline++;
if (lbuf[0] != '#' || lskip) {
ltext = nrealloc(ltext, 512);
if (sscanf(lbuf, "%s", ltext) != EOF) {
#ifdef LIBSAFE_HACKS
if (sscanf(lbuf, "0x%x,%500c", &lidx, ltext) != 1) {
#else
if (sscanf(lbuf, "0x%x,%500c", &lidx, ltext) != 2) {
#endif
putlog(LOG_MISC, "*", "Malformed text line in %s at %d.",
langfile, lline);
} else {
ltexts++;
ctmp = strchr(ltext, '\n');
*ctmp = 0;
while (ltext[strlen(ltext) - 1] == '\\') {
ltext[strlen(ltext) - 1] = 0;
if (fgets(lbuf, 511, FLANG)) {
lline++;
ctmp = strchr(lbuf, '\n');
*ctmp = 0;
ltext = nrealloc(ltext, strlen(lbuf) + strlen(ltext) + 1);
strcpy(strchr(ltext, 0), lbuf);
}
}
}
/* We gotta fix \n's here as, being arguments to sprintf(),
* they won't get translated.
*/
ctmp = ltext;
ctmp1 = ltext;
while (*ctmp1) {
if ((*ctmp1 == '\\') && (*(ctmp1 + 1) == 'n')) {
*ctmp = '\n';
ctmp1++;
} else if ((*ctmp1 == '\\') && (*(ctmp1 + 1) == 't')) {
*ctmp = '\t';
ctmp1++;
} else
*ctmp = *ctmp1;
ctmp++;
ctmp1++;
}
*ctmp = '\0';
if (add_message(lidx, ltext)) {
lupdate++;
} else
ladd++;
}
} else {
ctmp = strchr(lbuf, '\n');
if (lskip && (strlen(lbuf) == 1 || *(ctmp - 1) != '\\'))
lskip = 0;
}
}
nfree(ltext);
fclose(FLANG);
debug3("LANG: %d messages of %d lines loaded from %s", ltexts, lline,
langfile);
debug2("LANG: %d adds, %d updates to message table", ladd, lupdate);
}
/* Returns 1 if the section exists, otherwise 0.
*/
int exist_lang_section(char *section)
{
lang_sec *ls;
for (ls = langsection; ls; ls = ls->next)
if (!strcmp(section, ls->section))
return 1;
return 0;
}
/* Add a new language section. e.g. section "core"
* Load an apropriate language file for the specified section.
*/
void add_lang_section(char *section)
{
char *langfile = NULL;
lang_sec *ls, *ols = NULL;
int ok = 0;
for (ls = langsection; ls; ols = ls, ls = ls->next)
/* Already know of that section? */
if (!strcmp(section, ls->section))
return;
/* Create new section entry */
ls = nmalloc(sizeof(lang_sec));
ls->section = nmalloc(strlen(section) + 1);
strcpy(ls->section, section);
ls->lang = NULL;
ls->next = NULL;
/* Connect to existing list of sections */
if (ols)
ols->next = ls;
else
langsection = ls;
debug1("LANG: Section loaded: %s", section);
/* Always load base language */
langfile = get_specific_langfile(BASELANG, ls);
if (langfile) {
read_lang(langfile);
nfree(langfile);
ok = 1;
}
/* Now overwrite base language with a more preferred one */
langfile = get_langfile(ls);
if (!langfile) {
if (!ok)
putlog(LOG_MISC, "*", "LANG: No lang files found for section %s.",
section);
return;
}
read_lang(langfile);
nfree(langfile);
}
int del_lang_section(char *section)
{
lang_sec *ls, *ols;
for (ls = langsection, ols = NULL; ls; ols = ls, ls = ls->next)
if (ls->section && !strcmp(ls->section, section)) {
if (ols)
ols->next = ls->next;
else
langsection = ls->next;
nfree(ls->section);
if (ls->lang)
nfree(ls->lang);
nfree(ls);
debug1("LANG: Section unloaded: %s", section);
return 1;
}
return 0;
}
static char *get_specific_langfile(char *language, lang_sec *sec)
{
char *ldir = getenv("EGG_LANGDIR");
char *langfile;
if (!ldir)
ldir = LANGDIR;
langfile = nmalloc(strlen(ldir) + strlen(sec->section) + strlen(language) +
8);
sprintf(langfile, "%s/%s.%s.lang", ldir, sec->section, language);
if (file_readable(langfile)) {
/* Save language used for this section */
sec->lang = nrealloc(sec->lang, strlen(language) + 1);
strcpy(sec->lang, language);
return langfile;
}
nfree(langfile);
return NULL;
}
/* Searches for available language files and returns the file with the
* most preferred language.
*/
static char *get_langfile(lang_sec *sec)
{
char *langfile;
lang_pri *lp;
for (lp = langpriority; lp; lp = lp->next) {
/* There is no need to reload the same language */
if (sec->lang && !strcmp(sec->lang, lp->lang))
return NULL;
langfile = get_specific_langfile(lp->lang, sec);
if (langfile)
return langfile;
}
/* We did not find any files, clear the language field */
if (sec->lang)
nfree(sec->lang);
sec->lang = NULL;
return NULL;
}
/* Split up a string /path/<section>.<language>.lang into the
* needed information for the new language system.
* Only needed for compability functions.
*/
static int split_lang(char *par, char **lang, char **section)
{
char *p;
p = strrchr(par, '/');
/* path attached? */
if (p)
*section = p + 1;
else
*section = par;
p = strchr(*section, '.');
if (p)
p[0] = 0;
else
return 0;
*lang = p + 1;
p = strstr(*lang, ".lang");
if (p)
p[0] = 0;
return 1;
}
/* Compability function to allow users/modules to use the old command.
*/
int cmd_loadlanguage(struct userrec *u, int idx, char *par)
{
char *section, *lang, *buf;
dprintf(idx, "Note: This command is obsoleted by +lang.\n");
if (!par || !par[0]) {
dprintf(idx, "Usage: language <section>.<language>\n");
return 0;
}
if (idx != DP_LOG)
putlog(LOG_CMDS, "*", "#%s# language %s", dcc[idx].nick, par);
buf = nmalloc(strlen(par) + 1);
strcpy(buf, par);
if (!split_lang(buf, &lang, §ion)) {
nfree(buf);
dprintf(idx, "Invalid parameter %s.\n", par);
return 0;
}
add_lang(lang);
add_lang_section(section);
nfree(buf);
recheck_lang_sections();
return 0;
}
static int cmd_plslang(struct userrec *u, int idx, char *par)
{
if (!par || !par[0]) {
dprintf(idx, "Usage: +lang <language>\n");
return 0;
}
putlog(LOG_CMDS, "*", "#%s# +lang %s", dcc[idx].nick, par);
add_lang(par);
recheck_lang_sections();
return 0;
}
static int cmd_mnslang(struct userrec *u, int idx, char *par)
{
if (!par || !par[0]) {
dprintf(idx, "Usage: -lang <language>\n");
return 0;
}
putlog(LOG_CMDS, "*", "#%s# -lang %s", dcc[idx].nick, par);
if (!del_lang(par))
dprintf(idx, "Language %s not found.\n", par);
else
recheck_lang_sections();
return 0;
}
static int cmd_plslsec(struct userrec *u, int idx, char *par)
{
if (!par || !par[0]) {
dprintf(idx, "Usage: +lsec <section>\n");
return 0;
}
putlog(LOG_CMDS, "*", "#%s# +lsec %s", dcc[idx].nick, par);
add_lang_section(par);
return 0;
}
static int cmd_mnslsec(struct userrec *u, int idx, char *par)
{
if (!par || !par[0]) {
dprintf(idx, "Usage: -lsec <section>\n");
return 0;
}
putlog(LOG_CMDS, "*", "#%s# -lsec %s", dcc[idx].nick, par);
if (!del_lang_section(par))
dprintf(idx, "Section %s not found.\n", par);
return 0;
}
static int cmd_relang(struct userrec *u, int idx, char *par)
{
dprintf(idx, "Rechecking language sections...\n");
recheck_lang_sections();
return 0;
}
static int cmd_languagedump(struct userrec *u, int idx, char *par)
{
lang_tab *l;
char ltext2[512];
int idx2, i;
putlog(LOG_CMDS, "*", "#%s# ldump %s", dcc[idx].nick, par);
if (par[0]) {
/* atoi (hence strtol) don't work right here for hex */
if (strlen(par) > 2 && par[0] == '0' && par[1] == 'x')
sscanf(par, "%x", &idx2);
else
idx2 = (int) strtol(par, (char **) NULL, 10);
strcpy(ltext2, get_language(idx2));
dprintf(idx, "0x%x: %s\n", idx2, ltext2);
return 0;
}
dprintf(idx, " LANGIDX TEXT\n");
for (i = 0; i < 64; i++)
for (l = langtab[i]; l; l = l->next)
dprintf(idx, "0x%x %s\n", l->idx, l->text);
return 0;
}
static char text[512];
char *get_language(int idx)
{
lang_tab *l;
if (!idx)
return "MSG-0-";
for (l = langtab[idx & 63]; l; l = l->next)
if (idx == l->idx)
return l->text;
egg_snprintf(text, sizeof text, "MSG%03X", idx);
return text;
}
int expmem_language()
{
lang_tab *l;
lang_sec *ls;
lang_pri *lp;
int i, size = 0;
for (i = 0; i < 64; i++)
for (l = langtab[i]; l; l = l->next) {
size += sizeof(lang_tab);
size += (strlen(l->text) + 1);
}
for (ls = langsection; ls; ls = ls->next) {
size += sizeof(lang_sec);
if (ls->section)
size += strlen(ls->section) + 1;
if (ls->lang)
size += strlen(ls->lang) + 1;
}
for (lp = langpriority; lp; lp = lp->next) {
size += sizeof(lang_pri);
if (lp->lang)
size += strlen(lp->lang) + 1;
}
return size;
}
/* A report on the module status - only for debugging purposes
*/
static int cmd_languagestatus(struct userrec *u, int idx, char *par)
{
int ltexts = 0;
register int i, c, maxdepth = 0, used = 0, empty = 0;
lang_tab *l;
lang_sec *ls = langsection;
lang_pri *lp = langpriority;
putlog(LOG_CMDS, "*", "#%s# lstat %s", dcc[idx].nick, par);
for (i = 0; i < 64; i++) {
c = 0;
for (l = langtab[i]; l; l = l->next)
c++;
if (c > maxdepth)
maxdepth = c;
if (c)
used++;
else
empty++;
ltexts += c;
}
dprintf(idx, "Language code report:\n");
dprintf(idx, " Table size : %d bytes\n", expmem_language());
dprintf(idx, " Text messages: %d\n", ltexts);
dprintf(idx, " %d used, %d unused, maxdepth %d, avg %f\n",
used, empty, maxdepth, (float) ltexts / 64.0);
if (lp) {
int c = 0;
dprintf(idx, " Supported languages:");
for (; lp; lp = lp->next) {
dprintf(idx, "%s %s", c ? "," : "", lp->lang);
c = 1;
}
dprintf(idx, "\n");
}
if (ls) {
dprintf(idx, "\n SECTION LANG\n");
dprintf(idx, " ==============================\n");
for (; ls; ls = ls->next)
dprintf(idx, " %-20s %s\n", ls->section,
ls->lang ? ls->lang : "<none>");
}
return 0;
}
/* Compability function to allow scripts to use the old command.
*/
static int tcl_language STDVAR
{
char *lang, *section, *buf;
putlog(LOG_MISC, "*", "The Tcl command 'language' is obsolete. Use "
"'addlang' instead.");
BADARGS(2, 2, " language");
buf = nmalloc(strlen(argv[1]) + 1);
strcpy(buf, argv[1]);
if (!split_lang(buf, &lang, §ion)) {
Tcl_AppendResult(irp, "Invalid parameter", NULL);
nfree(buf);
return TCL_ERROR;
}
add_lang(lang);
add_lang_section(section);
nfree(buf);
recheck_lang_sections();
return TCL_OK;
}
static int tcl_plslang STDVAR
{
BADARGS(2, 2, " language");
add_lang(argv[1]);
recheck_lang_sections();
return TCL_OK;
}
static int tcl_mnslang STDVAR
{
BADARGS(2, 2, " language");
if (!del_lang(argv[1])) {
Tcl_AppendResult(irp, "Language not found.", NULL);
return TCL_ERROR;
}
recheck_lang_sections();
return TCL_OK;
}
static int tcl_addlangsection STDVAR
{
BADARGS(2, 2, " section");
add_lang_section(argv[1]);
return TCL_OK;
}
static int tcl_dellangsection STDVAR
{
BADARGS(2, 2, " section");
if (!del_lang_section(argv[1])) {
Tcl_AppendResult(irp, "Section not found", NULL);
return TCL_ERROR;
}
return TCL_OK;
}
static int tcl_relang STDVAR
{
recheck_lang_sections();
return TCL_OK;
}
static cmd_t langdcc[] = {
{"language", "n", cmd_loadlanguage, NULL},
{"+lang", "n", cmd_plslang, NULL},
{"-lang", "n", cmd_mnslang, NULL},
{"+lsec", "n", cmd_plslsec, NULL},
{"-lsec", "n", cmd_mnslsec, NULL},
{"ldump", "n", cmd_languagedump, NULL},
{"lstat", "n", cmd_languagestatus, NULL},
{"relang", "n", cmd_relang, NULL},
{NULL, NULL, NULL, NULL}
};
static tcl_cmds langtcls[] = {
{"language", tcl_language},
{"addlang", tcl_plslang},
{"dellang", tcl_mnslang},
{"addlangsection", tcl_addlangsection},
{"dellangsection", tcl_dellangsection},
{"relang", tcl_relang},
{NULL, NULL}
};
void init_language(int flag)
{
int i;
char *deflang;
if (flag) {
for (i = 0; i < 32; i++)
langtab[i] = 0;
/* The default language is always BASELANG as language files are
* gauranteed to exist in that language.
*/
add_lang(BASELANG);
/* Let the user choose a different, preferred language */
deflang = getenv("EGG_LANG");
if (deflang)
add_lang(deflang);
add_lang_section("core");
} else {
add_tcl_commands(langtcls);
add_builtins(H_dcc, langdcc);
}
}