1 /*
2  * language.c -- handles:
3  *   language support code
4  *
5  * $Id: language.c,v 1.23 (1.0.0) 2004/04/29 12:31:04 [Xp-AvR] Exp $
6  */
7 
8 #include "main.h"
9 
10 extern struct dcc_t *dcc;
11 
12 typedef struct lang_st {
13   struct lang_st *next;
14   char *lang;
15   char *section;
16 } lang_sec;
17 
18 typedef struct lang_pr {
19   struct lang_pr *next;
20   char *lang;
21 } lang_pri;
22 
23 typedef struct lang_t {
24   int idx;
25   char *text;
26   struct lang_t *next;
27 } lang_tab;
28 
29 static lang_tab *langtab[64];
30 static lang_sec *langsection = NULL;
31 static lang_pri *langpriority = NULL;
32 
33 static int del_lang(char *);
34 static int add_message(int, char *);
35 static void recheck_lang_sections(void);
36 static void read_lang(char *);
37 void add_lang_section(char *);
38 int del_lang_section(char *);
39 int exist_lang_section(char *);
40 static char *get_specific_langfile(char *, lang_sec *);
41 static char *get_langfile(lang_sec *);
42 static int split_lang(char *, char **, char **);
43 int cmd_loadlanguage(struct userrec *, int, char *);
44 
45 
46 /* Add a new preferred language to the list of languages. Newly added
47  * languages get the highest priority. */
add_lang(char * lang)48 void add_lang(char *lang)
49 {
50   lang_pri *lp = langpriority, *lpo = NULL;
51 
52   while (lp) {
53     /* The language already exists, moving to the beginning */
54     if (!strcmp(lang, lp->lang)) {
55       /* Already at the front? */
56       if (!lpo)
57         return;
58       lpo->next = lp->next;
59       lp->next = lpo;
60       langpriority = lp;
61       return;
62     }
63     lpo = lp;
64     lp = lp->next;
65   }
66 
67   /* No existing entry, create a new one */
68   lp = nmalloc(sizeof(lang_pri));
69   lp->lang = nmalloc(strlen(lang) + 1);
70   strcpy(lp->lang, lang);
71   lp->next = NULL;
72 
73   /* If we have other entries, point to the beginning of the old list */
74   if (langpriority)
75     lp->next = langpriority;
76   langpriority = lp;
77   debug1("(language.c->add_lang) LANG: Language loaded: %s", lang);
78 }
79 
80 /* Remove a language from the list of preferred languages.
81  */
del_lang(char * lang)82 static int del_lang(char *lang)
83 {
84   lang_pri *lp = langpriority, *lpo = NULL;
85 
86   while (lp) {
87     /* Found the language? */
88     if (!strcmp(lang, lp->lang)) {
89       if (lpo)
90         lpo->next = lp->next;
91       else
92         langpriority = lp->next;
93       if (lp->lang)
94         nfree(lp->lang);
95       nfree(lp);
96       debug1("(language.c->del_lang) LANG: Language unloaded: %s", lang);
97       return 1;
98     }
99     lpo = lp;
100     lp = lp->next;
101   }
102   /* Language not found */
103   return 0;
104 }
105 
add_message(int lidx,char * ltext)106 static int add_message(int lidx, char *ltext)
107 {
108   lang_tab *l = langtab[lidx & 63];
109 
110   while (l) {
111     if (l->idx && (l->idx == lidx)) {
112       nfree(l->text);
113       l->text = nmalloc(strlen(ltext) + 1);
114       strcpy(l->text, ltext);
115       return 1;
116     }
117     if (!l->next)
118       break;
119     l = l->next;
120   }
121   if (l) {
122     l->next = nmalloc(sizeof(lang_tab));
123     l = l->next;
124   } else
125     l = langtab[lidx & 63] = nmalloc(sizeof(lang_tab));
126   l->idx = lidx;
127   l->text = nmalloc(strlen(ltext) + 1);
128   strcpy(l->text, ltext);
129   l->next = 0;
130   return 0;
131 }
132 
133 /* Recheck all sections and check if any language files are available
134  * which match the preferred language(s) more closely
135  */
recheck_lang_sections(void)136 static void recheck_lang_sections(void)
137 {
138   lang_sec *ls;
139   char *langfile;
140 
141   for (ls = langsection; ls && ls->section; ls = ls->next) {
142     langfile = get_langfile(ls);
143     /* Found a language with a more preferred language? */
144     if (langfile) {
145       read_lang(langfile);
146       nfree(langfile);
147     }
148   }
149 }
150 
151 /* Parse a language file
152  */
read_lang(char * langfile)153 static void read_lang(char *langfile)
154 {
155   FILE *FLANG;
156   char lbuf[512];
157   char *ltext = NULL;
158   char *ctmp, *ctmp1;
159   int lidx;
160   int lline = 0;
161   int lskip;
162   int ltexts = 0;
163   int ladd = 0, lupdate = 0;
164 
165   FLANG = fopen(langfile, "r");
166   if (FLANG == NULL) {
167     putlog(LOG_MISC, "*", "LANG: unexpected: reading from file %s failed.",
168            langfile);
169     return;
170   }
171 
172   lskip = 0;
173   while (fgets(lbuf, 511, FLANG)) {
174     lline++;
175     if (lbuf[0] != '#' || lskip) {
176       ltext = nrealloc(ltext, 512);
177       if (sscanf(lbuf, "%s", ltext) != EOF) {
178 #ifdef LIBSAFE_HACKS
179         if (sscanf(lbuf, "0x%x,%500c", &lidx, ltext) != 1) {
180 #else
181         if (sscanf(lbuf, "0x%x,%500c", &lidx, ltext) != 2) {
182 #endif
183           putlog(LOG_MISC, "*", "Malformed text line in %s at %d.",
184                  langfile, lline);
185         } else {
186           ltexts++;
187           ctmp = strchr(ltext, '\n');
188           *ctmp = 0;
189           while (ltext[strlen(ltext) - 1] == '\\') {
190             ltext[strlen(ltext) - 1] = 0;
191             if (fgets(lbuf, 511, FLANG)) {
192               lline++;
193               ctmp = strchr(lbuf, '\n');
194               *ctmp = 0;
195               ltext = nrealloc(ltext, strlen(lbuf) + strlen(ltext) + 1);
196               strcpy(strchr(ltext, 0), lbuf);
197             }
198           }
199         }
200         /* We gotta fix \n's here as, being arguments to sprintf(),
201          * they won't get translated.
202          */
203         ctmp = ltext;
204         ctmp1 = ltext;
205         while (*ctmp1) {
206           if ((*ctmp1 == '\\') && (*(ctmp1 + 1) == 'n')) {
207             *ctmp = '\n';
208             ctmp1++;
209           } else if ((*ctmp1 == '\\') && (*(ctmp1 + 1) == 't')) {
210             *ctmp = '\t';
211             ctmp1++;
212           } else
213             *ctmp = *ctmp1;
214           ctmp++;
215           ctmp1++;
216         }
217         *ctmp = '\0';
218         if (add_message(lidx, ltext)) {
219           lupdate++;
220         } else
221           ladd++;
222       }
223     } else {
224       ctmp = strchr(lbuf, '\n');
225       if (lskip && (strlen(lbuf) == 1 || *(ctmp - 1) != '\\'))
226         lskip = 0;
227     }
228   }
229   nfree(ltext);
230   fclose(FLANG);
231 
232   debug3("(language.c->read_lang) LANG: %d messages of %d lines loaded from %s", ltexts, lline,
233          langfile);
234   debug2("(language.c->read_lang) LANG: %d adds, %d updates to message table", ladd, lupdate);
235 }
236 
237 /* Returns 1 if the section exists, otherwise 0.
238  */
239 int exist_lang_section(char *section)
240 {
241   lang_sec *ls;
242 
243   for (ls = langsection; ls; ls = ls->next)
244     if (!strcmp(section, ls->section))
245       return 1;
246   return 0;
247 }
248 
249 /* Add a new language section. e.g. section "core"
250  * Load an apropriate language file for the specified section.
251  */
252 void add_lang_section(char *section)
253 {
254   char *langfile = NULL;
255   lang_sec *ls, *ols = NULL;
256   int ok = 0;
257 
258   for (ls = langsection; ls; ols = ls, ls = ls->next)
259     /* Already know of that section? */
260     if (!strcmp(section, ls->section))
261       return;
262 
263   /* Create new section entry */
264   ls = nmalloc(sizeof(lang_sec));
265   ls->section = nmalloc(strlen(section) + 1);
266   strcpy(ls->section, section);
267   ls->lang = NULL;
268   ls->next = NULL;
269 
270   /* Connect to existing list of sections */
271   if (ols)
272     ols->next = ls;
273   else
274     langsection = ls;
275   debug1("(language.c->add_lang_section) LANG: Section loaded: %s", section);
276 
277   /* Always load base language */
278   langfile = get_specific_langfile(BASELANG, ls);
279   if (langfile) {
280     read_lang(langfile);
281     nfree(langfile);
282     ok = 1;
283   }
284   /* Now overwrite base language with a more preferred one */
285   langfile = get_langfile(ls);
286   if (!langfile) {
287     if (!ok)
288       debug1("(language.c->add_lang_section) LANG: No lang files found for section %s.",
289              section);
290     return;
291   }
292   read_lang(langfile);
293   nfree(langfile);
294 }
295 
296 int del_lang_section(char *section)
297 {
298   lang_sec *ls, *ols;
299 
300   for (ls = langsection, ols = NULL; ls; ols = ls, ls = ls->next)
301     if (ls->section && !strcmp(ls->section, section)) {
302       if (ols)
303         ols->next = ls->next;
304       else
305         langsection = ls->next;
306       nfree(ls->section);
307       if (ls->lang)
308         nfree(ls->lang);
309       nfree(ls);
310       debug1("(language.c->del_lang_section) LANG: Section unloaded: %s", section);
311       return 1;
312     }
313   return 0;
314 }
315 
316 static char *get_specific_langfile(char *language, lang_sec *sec)
317 {
318   char *ldir = getenv("EVANGELINE_LANGDIR");
319   char *langfile;
320 
321   if (!ldir)
322     ldir = LANGDIR;
323   langfile = nmalloc(strlen(ldir) + strlen(sec->section) + strlen(language) +
324              8);
325   sprintf(langfile, "%s/%s.%s.lang", ldir, sec->section, language);
326 
327   if (file_readable(langfile)) {
328     /* Save language used for this section */
329     sec->lang = nrealloc(sec->lang, strlen(language) + 1);
330     strcpy(sec->lang, language);
331     return langfile;
332   }
333 
334   nfree(langfile);
335   return NULL;
336 }
337 
338 /* Searches for available language files and returns the file with the
339  * most preferred language.
340  */
341 static char *get_langfile(lang_sec *sec)
342 {
343   char *langfile;
344   lang_pri *lp;
345 
346   for (lp = langpriority; lp; lp = lp->next) {
347     /* There is no need to reload the same language */
348     if (sec->lang && !strcmp(sec->lang, lp->lang))
349       return NULL;
350     langfile = get_specific_langfile(lp->lang, sec);
351     if (langfile)
352       return langfile;
353   }
354   /* We did not find any files, clear the language field */
355   if (sec->lang)
356     nfree(sec->lang);
357   sec->lang = NULL;
358   return NULL;
359 }
360 
361 /* Split up a string /path/<section>.<language>.lang into the
362  * needed information for the new language system.
363  * Only needed for compability functions.
364  */
365 static int split_lang(char *par, char **lang, char **section)
366 {
367   char *p;
368 
369   p = strrchr(par, '/');
370   /* path attached? */
371   if (p)
372     *section = p + 1;
373   else
374     *section = par;
375   p = strchr(*section, '.');
376   if (p)
377     p[0] = 0;
378   else
379     return 0;
380   *lang = p + 1;
381   p = strstr(*lang, ".lang");
382   if (p)
383     p[0] = 0;
384   return 1;
385 }
386 
387 /* Compability function to allow users/modules to use the old command.
388  */
389 int cmd_loadlanguage(struct userrec *u, int idx, char *par)
390 {
391   char *section, *lang, *buf;
392 
393   dprintf(idx, "Note: This command is obsoleted by +lang.\n");
394   if (!par || !par[0]) {
395     dprintf(idx, "Usage: language <section>.<language>\n");
396     return 0;
397   }
398   if (idx != DP_LOG)
399     putlog(LOG_CMDS, "*", "#%s# language %s", dcc[idx].nick, par);
400   buf = nmalloc(strlen(par) + 1);
401   strcpy(buf, par);
402   if (!split_lang(buf, &lang, &section)) {
403     nfree(buf);
404     dprintf(idx, "Invalid parameter %s.\n", par);
405     return 0;
406   }
407   add_lang(lang);
408   add_lang_section(section);
409   nfree(buf);
410   recheck_lang_sections();
411   return 0;
412 }
413 
414 static int cmd_plslang(struct userrec *u, int idx, char *par)
415 {
416   if (!par || !par[0]) {
417     dprintf(idx, "Usage: +lang <language>\n");
418     return 0;
419   }
420   putlog(LOG_CMDS, "*", "#%s# +lang %s", dcc[idx].nick, par);
421   add_lang(par);
422   recheck_lang_sections();
423   return 0;
424 }
425 
426 static int cmd_mnslang(struct userrec *u, int idx, char *par)
427 {
428   if (!par || !par[0]) {
429     dprintf(idx, "Usage: -lang <language>\n");
430     return 0;
431   }
432   putlog(LOG_CMDS, "*", "#%s# -lang %s", dcc[idx].nick, par);
433   if (!del_lang(par))
434     dprintf(idx, "Language %s not found.\n", par);
435   else
436     recheck_lang_sections();
437   return 0;
438 }
439 
440 static int cmd_plslsec(struct userrec *u, int idx, char *par)
441 {
442   if (!par || !par[0]) {
443     dprintf(idx, "Usage: +lsec <section>\n");
444     return 0;
445   }
446   putlog(LOG_CMDS, "*", "#%s# +lsec %s", dcc[idx].nick, par);
447   add_lang_section(par);
448   return 0;
449 }
450 
451 static int cmd_mnslsec(struct userrec *u, int idx, char *par)
452 {
453   if (!par || !par[0]) {
454     dprintf(idx, "Usage: -lsec <section>\n");
455     return 0;
456   }
457   putlog(LOG_CMDS, "*", "#%s# -lsec %s", dcc[idx].nick, par);
458   if (!del_lang_section(par))
459     dprintf(idx, "Section %s not found.\n", par);
460   return 0;
461 }
462 
463 static int cmd_relang(struct userrec *u, int idx, char *par)
464 {
465   dprintf(idx, "Rechecking language sections...\n");
466   recheck_lang_sections();
467   return 0;
468 }
469 
470 static int cmd_languagedump(struct userrec *u, int idx, char *par)
471 {
472   lang_tab *l;
473   char ltext2[512];
474   int idx2, i;
475 
476   putlog(LOG_CMDS, "*", "#%s# ldump %s", dcc[idx].nick, par);
477   if (par[0]) {
478     /* atoi (hence strtol) don't work right here for hex */
479     if (strlen(par) > 2 && par[0] == '0' && par[1] == 'x')
480       sscanf(par, "%x", &idx2);
481     else
482       idx2 = (int) strtol(par, (char **) NULL, 10);
483     strcpy(ltext2, get_language(idx2));
484     dprintf(idx, "0x%x: %s\n", idx2, ltext2);
485     return 0;
486   }
487   dprintf(idx, " LANGIDX TEXT\n");
488   for (i = 0; i < 64; i++)
489     for (l = langtab[i]; l; l = l->next)
490       dprintf(idx, "0x%x   %s\n", l->idx, l->text);
491   return 0;
492 }
493 
494 static char text[512];
495 char *get_language(int idx)
496 {
497   lang_tab *l;
498 
499   if (!idx)
500     return "MSG-0-";
501   for (l = langtab[idx & 63]; l; l = l->next)
502     if (idx == l->idx)
503       return l->text;
504   EvangelineSnprintf(text, sizeof text, "MSG%03X", idx);
505   return text;
506 }
507 
508 int expmem_language()
509 {
510   lang_tab *l;
511   lang_sec *ls;
512   lang_pri *lp;
513   int i, size = 0;
514 
515   for (i = 0; i < 64; i++)
516     for (l = langtab[i]; l; l = l->next) {
517       size += sizeof(lang_tab);
518       size += (strlen(l->text) + 1);
519     }
520   for (ls = langsection; ls; ls = ls->next) {
521     size += sizeof(lang_sec);
522     if (ls->section)
523       size += strlen(ls->section) + 1;
524     if (ls->lang)
525       size += strlen(ls->lang) + 1;
526   }
527   for (lp = langpriority; lp; lp = lp->next) {
528     size += sizeof(lang_pri);
529     if (lp->lang)
530       size += strlen(lp->lang) + 1;
531   }
532   return size;
533 }
534 
535 /* A report on the module status - only for debugging purposes
536  */
537 static int cmd_languagestatus(struct userrec *u, int idx, char *par)
538 {
539   int ltexts = 0;
540   register int i, c, maxdepth = 0, used = 0, empty = 0;
541   lang_tab *l;
542   lang_sec *ls = langsection;
543   lang_pri *lp = langpriority;
544 
545   putlog(LOG_CMDS, "*", "#%s# lstat %s", dcc[idx].nick, par);
546   for (i = 0; i < 64; i++) {
547     c = 0;
548     for (l = langtab[i]; l; l = l->next)
549       c++;
550     if (c > maxdepth)
551       maxdepth = c;
552     if (c)
553       used++;
554     else
555       empty++;
556     ltexts += c;
557   }
558   dprintf(idx, "Language code report:\n");
559   dprintf(idx, "   Table size   : %d bytes\n", expmem_language());
560   dprintf(idx, "   Text messages: %d\n", ltexts);
561   dprintf(idx, "   %d used, %d unused, maxdepth %d, avg %f\n",
562           used, empty, maxdepth, (float) ltexts / 64.0);
563   if (lp) {
564     int c = 0;
565 
566     dprintf(idx, "   Supported languages:");
567     for (; lp; lp = lp->next) {
568       dprintf(idx, "%s %s", c ? "," : "", lp->lang);
569       c = 1;
570     }
571     dprintf(idx, "\n");
572   }
573   if (ls) {
574     dprintf(idx, "\n   SECTION              LANG\n");
575     dprintf(idx, "   ==============================\n");
576     for (; ls; ls = ls->next)
577       dprintf(idx, "   %-20s %s\n", ls->section,
578               ls->lang ? ls->lang : "<none>");
579   }
580   return 0;
581 }
582 
583 /* Compability function to allow scripts to use the old command.
584  */
585 static int tcl_language STDVAR
586 {
587   char *lang, *section, *buf;
588 
589   putlog(LOG_MISC, "*", "The Tcl command 'language' is obsolete. Use "
590          "'addlang' instead.");
591   BADARGS(2, 2, " language");
592 
593   buf = nmalloc(strlen(argv[1]) + 1);
594   strcpy(buf, argv[1]);
595 
596   if (!split_lang(buf, &lang, &section)) {
597     Tcl_AppendResult(irp, "Invalid parameter", NULL);
598     nfree(buf);
599     return TCL_ERROR;
600   }
601   add_lang(lang);
602 
603   add_lang_section(section);
604   nfree(buf);
605   recheck_lang_sections();
606   return TCL_OK;
607 }
608 
609 static int tcl_plslang STDVAR
610 {
611   BADARGS(2, 2, " language");
612 
613   add_lang(argv[1]);
614   recheck_lang_sections();
615 
616   return TCL_OK;
617 }
618 
619 static int tcl_mnslang STDVAR
620 {
621   BADARGS(2, 2, " language");
622 
623   if (!del_lang(argv[1])) {
624     Tcl_AppendResult(irp, "Language not found.", NULL);
625     return TCL_ERROR;
626   }
627   recheck_lang_sections();
628 
629   return TCL_OK;
630 }
631 
632 static int tcl_addlangsection STDVAR
633 {
634   BADARGS(2, 2, " section");
635 
636   add_lang_section(argv[1]);
637   return TCL_OK;
638 }
639 
640 static int tcl_dellangsection STDVAR
641 {
642   BADARGS(2, 2, " section");
643 
644   if (!del_lang_section(argv[1])) {
645     Tcl_AppendResult(irp, "Section not found", NULL);
646     return TCL_ERROR;
647   }
648   return TCL_OK;
649 }
650 
651 static int tcl_relang STDVAR
652 {
653   recheck_lang_sections();
654   return TCL_OK;
655 }
656 
657 static cmd_t langdcc[] = {
658   {"language", "n",  cmd_loadlanguage,   NULL},
659   {"+lang",    "n",  cmd_plslang,        NULL},
660   {"-lang",    "n",  cmd_mnslang,        NULL},
661   {"+lsec",    "n",  cmd_plslsec,        NULL},
662   {"-lsec",    "n",  cmd_mnslsec,        NULL},
663   {"ldump",    "n",  cmd_languagedump,   NULL},
664   {"lstat",    "n",  cmd_languagestatus, NULL},
665   {"relang",   "n",  cmd_relang,         NULL},
666   {NULL,       NULL, NULL,               NULL}
667 };
668 
669 static tcl_cmds langtcls[] = {
670   {"language",             tcl_language},
671   {"addlang",               tcl_plslang},
672   {"dellang",               tcl_mnslang},
673   {"addlangsection", tcl_addlangsection},
674   {"dellangsection", tcl_dellangsection},
675   {"relang",                 tcl_relang},
676   {NULL,                           NULL}
677 };
678 
679 void init_language(int flag)
680 {
681   int i;
682   char *deflang;
683 
684   if (flag) {
685     for (i = 0; i < 32; i++)
686       langtab[i] = 0;
687     /* The default language is always BASELANG as language files are
688      * gauranteed to exist in that language.
689      */
690     add_lang(BASELANG);
691     /* Let the user choose a different, preferred language */
692     deflang = getenv("EVANGELINE_LANG");
693     if (deflang)
694       add_lang(deflang);
695   } else {
696     add_tcl_commands(langtcls);
697     add_builtins(H_dcc, langdcc);
698   }
699 }
700