ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/hybrid-ircservices-1/language.c
Revision: 1209
Committed: Thu Aug 25 19:05:49 2011 UTC (14 years, 11 months ago) by michael
Content type: text/x-csrc
File size: 30211 byte(s)
Log Message:
- run everything thru indent
  "-bli0 -di1 -npcs -nut -cdw -bls -nbbo -bap"

File Contents

# User Rev Content
1 michael 1194 /* Multi-language support.
2     *
3     * IRC Services is copyright (c) 1996-2009 Andrew Church.
4     * E-mail: <achurch@achurch.org>
5     * Parts written by Andrew Kempe and others.
6     * This program is free but copyrighted software; see the file GPL.txt for
7     * details.
8     */
9    
10 michael 1209 #define LANGSTR_ARRAY /* define langstrs[] in langstrs.h, via language.h */
11 michael 1194
12     #include "services.h"
13     #include "language.h"
14    
15     /* Needed for NickGroupInfo structure definition (used by getstring() and
16     * strftime_lang()) */
17     #include "modules/nickserv/nickserv.h"
18    
19     /*************************************************************************/
20    
21     /* Indexes of available languages (exported), terminated by -1: */
22 michael 1209 int langlist[NUM_LANGS + 1];
23 michael 1194
24     /* List and count of available strings: */
25     static char **langstrs;
26     int num_strings;
27    
28     /* The list of lists of messages. */
29     static char **langtexts[NUM_LANGS];
30     /* Pointers to the original data, in case external files are loaded later. */
31     static char **origtexts[NUM_LANGS];
32    
33     /* Order in which languages should be displayed: (alphabetical in English) */
34     static int langorder[] = {
35 michael 1209 LANG_EN_US, /* English (US) */
36     LANG_NL, /* Dutch */
37     LANG_FR, /* French */
38     LANG_DE, /* German */
39     LANG_HU, /* Hungarian */
40     /* LANG_IT, *//* Italian */
41     LANG_JA_EUC, /* Japanese (EUC encoding) */
42     LANG_JA_SJIS, /* Japanese (SJIS encoding) */
43     /* LANG_PT, *//* Portugese */
44     LANG_RU, /* Russian */
45     LANG_ES, /* Spanish */
46     LANG_TR, /* Turkish */
47 michael 1194 };
48    
49     /* Filenames for language files: */
50 michael 1209 static struct
51     {
52     int num;
53     const char *filename;
54     } filenames[] =
55     {
56     {
57     LANG_EN_US, "en_us"},
58     {
59     LANG_NL, "nl"},
60     {
61     LANG_FR, "fr"},
62     {
63     LANG_DE, "de"},
64     {
65     LANG_HU, "hu"},
66     {
67     LANG_IT, "it"},
68     {
69     LANG_JA_EUC, "ja_euc"},
70     {
71     LANG_JA_SJIS, "ja_sjis"},
72     {
73     LANG_PT, "pt"},
74     {
75     LANG_RU, "ru"},
76     {
77     LANG_ES, "es"},
78     {
79     LANG_TR, "tr"},
80     {
81     -1, NULL}
82 michael 1194 };
83    
84     /* Mapping of language strings (to allow on-the-fly replacement of strings) */
85     static int *langmap;
86    
87     /* Array indicating which languages were actually loaded (needed since NULL
88     * langtexts[] pointers are redirected to DEF_LANGUAGE) */
89     static int is_loaded[NUM_LANGS];
90    
91     /* Index of the first extra (non-base) string */
92     #define FIRST_EXTRA_STRING (NUM_BASE_STRINGS)
93     /* Is the given string index a base string index? */
94     #define IS_BASE_STRING(n) ((n) < FIRST_EXTRA_STRING)
95    
96     /*************************************************************************/
97     /*************************************************************************/
98    
99     /* Helper functions. */
100    
101 michael 1209 static inline int
102     read_int32(int32 * ptr, FILE * f)
103 michael 1194 {
104 michael 1209 int a = fgetc(f);
105     int b = fgetc(f);
106     int c = fgetc(f);
107     int d = fgetc(f);
108     if (a == EOF || b == EOF || c == EOF || d == EOF)
109     return -1;
110     *ptr = a << 24 | b << 16 | c << 8 | d;
111     return 0;
112 michael 1194 }
113    
114 michael 1209 static inline int
115     read_uint32(uint32 * ptr, FILE * f)
116 michael 1194 {
117 michael 1209 return read_int32((int32 *) ptr, f);
118 michael 1194 }
119    
120     /*************************************************************************/
121     /*************************************************************************/
122    
123     /* Load a language file, storing the data in origtexts[index]. */
124    
125 michael 1209 static void
126     load_lang(int index, const char *filename)
127 michael 1194 {
128 michael 1209 char buf[256];
129     FILE *f;
130     uint32 num, size, i;
131     char *data = NULL;
132 michael 1194
133 michael 1209 log_debug(1, "Loading language %d from file `languages/%s'",
134 michael 1194 index, filename);
135 michael 1209 snprintf(buf, sizeof(buf), "languages/%s", filename);
136     if (!(f = fopen(buf, "r")))
137     {
138     log_perror("Failed to load language %d (%s)", index, filename);
139     return;
140     }
141     else if (read_uint32(&num, f) < 0)
142     {
143     log("Failed to read number of strings for language %d (%s)",
144     index, filename);
145     return;
146     }
147     else if (read_uint32(&size, f) < 0)
148     {
149     log("Failed to read data size for language %d (%s)", index, filename);
150     return;
151     }
152     else if (num != NUM_BASE_STRINGS)
153     {
154     log("Warning: Bad number of strings (%d, wanted %d) "
155     "for language %d (%s)", num, NUM_BASE_STRINGS, index, filename);
156     }
157     origtexts[index] = scalloc(sizeof(char *), NUM_BASE_STRINGS + 1);
158     if (num > NUM_BASE_STRINGS)
159     num = NUM_BASE_STRINGS;
160     origtexts[index][0] = data = smalloc(size + 4);
161     *((uint32 *) data) = size;
162     data += 4;
163     if (fread(data, size, 1, f) != 1)
164     {
165     log("Failed to read language data for language %d (%s)", index, filename);
166     goto fail;
167     }
168     for (i = 0; i < num; i++)
169     {
170     int32 pos;
171     if (read_int32(&pos, f) < 0)
172     {
173     log("Failed to read entry %d in language %d (%s) TOC",
174     i, index, filename);
175     goto fail;
176 michael 1194 }
177 michael 1209 if (pos == -1)
178     {
179     origtexts[index][i + 1] = NULL;
180 michael 1194 }
181 michael 1209 else
182     {
183     origtexts[index][i + 1] = data + pos;
184 michael 1194 }
185 michael 1209 }
186     fclose(f);
187     is_loaded[index] = 1;
188     return;
189 michael 1194
190 michael 1209 fail:
191     free(data);
192     free(origtexts[index]);
193     origtexts[index] = NULL;
194     return;
195 michael 1194 }
196    
197     /*************************************************************************/
198    
199     /* Initialize list of lists. */
200    
201 michael 1209 int
202     lang_init(void)
203 michael 1194 {
204 michael 1209 int i, j, n = 0;
205 michael 1194
206 michael 1209 /* Set up the string list */
207     langstrs = malloc(sizeof(char *) * NUM_BASE_STRINGS);
208     if (!langstrs)
209     {
210     log_perror("malloc(langstrs)");
211     return 0;
212     }
213     memcpy(langstrs, base_langstrs, sizeof(char *) * NUM_BASE_STRINGS);
214     num_strings = NUM_BASE_STRINGS;
215 michael 1194
216 michael 1209 /* Set up the string remap list */
217     langmap = smalloc(sizeof(int) * num_strings);
218     for (i = 0; i < num_strings; i++)
219     langmap[i] = i;
220 michael 1194
221 michael 1209 /* Load language files */
222     memset(is_loaded, 0, sizeof(is_loaded));
223     for (i = 0; i < lenof(langorder); i++)
224     {
225     for (j = 0; filenames[j].num >= 0; j++)
226     {
227     if (filenames[j].num == langorder[i])
228     break;
229 michael 1194 }
230 michael 1209 if (filenames[j].num >= 0)
231     {
232     load_lang(langorder[i], filenames[j].filename);
233 michael 1194 }
234 michael 1209 else
235     {
236     log("BUG: lang_init(): no filename entry for language %d!",
237     langorder[i]);
238 michael 1194 }
239 michael 1209 }
240 michael 1194
241 michael 1209 /* Make sure the default language has all strings available */
242     if (!origtexts[DEF_LANGUAGE])
243     {
244     log("Unable to load default language");
245     return 0;
246     }
247     for (i = 0; i < num_strings; i++)
248     {
249     if (!origtexts[DEF_LANGUAGE][i + 1])
250     {
251     if (is_loaded[LANG_EN_US] && origtexts[LANG_EN_US][i + 1])
252     {
253     uint32 oldsize = *((uint32 *) origtexts[DEF_LANGUAGE][0]);
254     uint32 newsize = strlen(origtexts[LANG_EN_US][i + 1]) + 1;
255     origtexts[DEF_LANGUAGE][0] =
256     realloc(origtexts[DEF_LANGUAGE][0], oldsize + newsize + 4);
257     if (!origtexts[DEF_LANGUAGE][0])
258     {
259     log("Out of memory while loading languages");
260     return 0;
261     }
262     *((uint32 *) origtexts[DEF_LANGUAGE][0]) = oldsize + newsize;
263     origtexts[DEF_LANGUAGE][i + 1] =
264     origtexts[DEF_LANGUAGE][0] + 4 + oldsize;
265     strcpy(origtexts[DEF_LANGUAGE][i + 1], origtexts[LANG_EN_US][i + 1]);
266     }
267     else
268     {
269     log("String %s missing from default language", langstrs[i]);
270     return 0;
271     }
272 michael 1194 }
273 michael 1209 }
274 michael 1194
275 michael 1209 /* Set up the list of available languages in langlist[] */
276     for (i = 0; i < lenof(langorder); i++)
277     {
278     if (is_loaded[langorder[i]])
279     langlist[n++] = langorder[i];
280     }
281     while (n < lenof(langlist))
282     langlist[n++] = -1;
283 michael 1194
284 michael 1209 /* Initialize the active string tables in langtexts[] */
285     reset_ext_lang();
286    
287     return 1;
288 michael 1194 }
289    
290     /*************************************************************************/
291    
292     /* Clean up language data. */
293    
294 michael 1209 void
295     lang_cleanup(void)
296 michael 1194 {
297 michael 1209 int i;
298 michael 1194
299 michael 1209 for (i = 0; i < NUM_LANGS; i++)
300     {
301     if (langtexts[i])
302     {
303     free(langtexts[i][0]);
304     free(langtexts[i]);
305     langtexts[i] = NULL;
306 michael 1194 }
307 michael 1209 if (origtexts[i])
308     {
309     free(origtexts[i][0]);
310     free(origtexts[i]);
311     langtexts[i] = NULL;
312     }
313     }
314     free(langmap);
315     free(langstrs);
316 michael 1194 }
317    
318     /*************************************************************************/
319    
320     /* Load an external language file. External language files are formatted
321     * the same way as the standard language files, except that a language name
322     * follows the string name; for example:
323     *
324     * UNKNOWN_COMMAND<lwsp+>en_us<lwsp*><nl>
325     * <tab>The command %s is not recognized.<nl>
326     *
327     * where <lwsp> is "linear white space" (space or tab); <nl> is either LF
328     * or CRLF; and <tab> is the tab character. Empty lines and lines
329     * beginning with '#' are ignored. Note that any lines longer than
330     * BUFSIZE-1 characters (including the trailing <nl>) will be truncated.
331     *
332     * Returns 0 on failure, nonzero on success.
333     */
334    
335 michael 1209 int
336     load_ext_lang(const char *filename)
337 michael 1194 {
338 michael 1209 FILE *f;
339     char buf[BUFSIZE], *s;
340     char **newtexts[NUM_LANGS];
341     uint32 newsizes[NUM_LANGS];
342     int i, curstr, curlang, line;
343     int retval = 1, firstline = 1;
344 michael 1194
345 michael 1209 memset(newtexts, 0, sizeof(newtexts));
346     memset(newsizes, 0, sizeof(newsizes));
347     f = fopen(filename, "r");
348     if (!f)
349     {
350     log_perror("load_ext_lang(): Unable to open file %s", filename);
351     return 0;
352     }
353    
354     curstr = curlang = -1;
355     line = 0;
356     while (fgets(buf, sizeof(buf), f) && *buf)
357     {
358     line++;
359     s = buf + strlen(buf) - 1;
360     if (*s == '\r')
361     { /* in case we get half a CRLF */
362     *s = fgetc(f);
363     if (*s != '\n')
364     {
365     ungetc(*s, f);
366     *s = '\r';
367     }
368 michael 1194 }
369 michael 1209 if (*s == '\n')
370     {
371     *s = 0;
372     if (s > buf && s[-1] == '\r')
373     s[-1] = 0;
374     }
375     else
376     {
377     char buf2[BUFSIZE];
378     log("load_ext_lang(): %s:%d: Line too long (maximum %d"
379     " characters)", filename, line, sizeof(buf) - 2);
380     retval = 0;
381     while (fgets(buf2, sizeof(buf2), f)
382     && *buf2 && buf2[strlen(buf2) - 1] != '\n')
383     ;
384     }
385     if (*buf == '#')
386     continue;
387     if (*buf != '\t')
388     {
389     curstr = curlang = -1;
390     s = strtok(buf, " \t\r\n");
391     if (!s)
392     continue; /* empty line */
393     if ((curstr = lookup_string(s)) < 0)
394     {
395     log("load_ext_lang: %s:%d: Unknown string ID `%s'",
396     filename, line, s);
397     retval = 0;
398     }
399     else
400     {
401     s = strtok(NULL, " \t\r\n");
402     if (!s)
403     {
404     log("load_ext_lang: %s:%d: Missing language ID\n", filename, line);
405     retval = 0;
406     curstr = -1;
407 michael 1194 }
408 michael 1209 else if ((curlang = lookup_language(s)) < 0)
409     {
410     log("load_ext_lang: %s:%d: Unknown language ID `%s'",
411     filename, line, s);
412     retval = 0;
413     curstr = -1;
414 michael 1194 }
415 michael 1209 else if (!is_loaded[curlang])
416     {
417     log("load_ext_lang: %s:%d: Language `%s' is not"
418     " available", filename, line, s);
419     retval = 0;
420     curstr = -1;
421 michael 1194 }
422 michael 1209 }
423     if (curstr >= 0 && curlang >= 0)
424     {
425     /* Valid string/language ID--set up storage space if not
426     * yet done */
427     if (!newtexts[curlang])
428     {
429     newtexts[curlang] = calloc(num_strings + 1, sizeof(char *));
430     if (!newtexts[curlang])
431     {
432     log_perror("load_ext_lang: %s:%d", filename, line);
433     goto fail;
434     }
435     newsizes[curlang] = 0;
436     }
437     /* Point to location of string in data buffer; we add 1
438     * to the offset to differentiate the value from 0 (not
439     * present) */
440     newtexts[curlang][curstr + 1] = (char *) newsizes[curlang] + 1;
441     /* Set first-line flag (signals whether to insert a
442     * newline) */
443     firstline = 1;
444     }
445     }
446     else
447     { /* line begins with tab -> text line */
448     if (curstr >= 0 && curlang >= 0)
449     {
450     int oldsize = newsizes[curlang];
451     if (!firstline)
452     oldsize--; // overwrite the trailing \0 with a newline
453     newsizes[curlang] += strlen(buf + 1) + 1; // skip initial tab
454     newtexts[curlang][0] =
455     realloc(newtexts[curlang][0], newsizes[curlang]);
456     sprintf(newtexts[curlang][0] + oldsize, "%s%s",
457     firstline ? "" : "\n", buf + 1);
458     firstline = 0;
459     }
460     }
461     } /* for each line */
462     fclose(f);
463 michael 1194
464 michael 1209 if (retval)
465     {
466     /* Success, install new strings. First set up new string arrays in
467     * newtexts[], then, when all succeeds (i.e. no ENOMEM), move the
468     * new data to langtexts[]. */
469     for (curlang = 0; curlang < NUM_LANGS; curlang++)
470     {
471     char *newbuf;
472     uint32 oldlen, newlen;
473     if (!newtexts[curlang])
474     continue;
475     oldlen = *((uint32 *) langtexts[curlang][0]);
476     newlen = newsizes[curlang];
477     newbuf = malloc(4 + oldlen + newlen);
478     if (!newbuf)
479     {
480     log_perror("load_ext_lang: %s:%d", filename, line);
481     goto fail;
482     }
483     *((uint32 *) newbuf) = oldlen + newlen;
484     memcpy(newbuf + 4, langtexts[curlang][0] + 4, oldlen);
485     memcpy(newbuf + 4 + oldlen, newtexts[curlang][0], newlen);
486     free(newtexts[curlang][0]);
487     newtexts[curlang][0] = newbuf;
488     for (i = 0; i < num_strings; i++)
489     {
490     if (newtexts[curlang][i + 1])
491     {
492     int ofs = (int) newtexts[curlang][i + 1] - 1;
493     newtexts[curlang][i + 1] = newbuf + 4 + oldlen + ofs;
494 michael 1194 }
495 michael 1209 else if (langtexts[curlang][i + 1])
496     {
497     int ofs = langtexts[curlang][i + 1] - langtexts[curlang][0];
498     newtexts[curlang][i + 1] = newbuf + ofs;
499     }
500     else
501     {
502     newtexts[curlang][i + 1] = NULL;
503     }
504     }
505     } /* for each language */
506     /* All okay, actually install the data */
507     for (curlang = 0; curlang < NUM_LANGS; curlang++)
508     {
509     if (!newtexts[curlang])
510     continue;
511     free(langtexts[curlang][0]);
512     free(langtexts[curlang]);
513     langtexts[curlang] = newtexts[curlang];
514     }
515     } /* if (retval) */
516 michael 1194
517 michael 1209 return retval;
518 michael 1194
519 michael 1209 fail:
520     for (curlang = 0; curlang < NUM_LANGS; curlang++)
521     {
522     if (newtexts[curlang])
523     {
524     free(newtexts[curlang][0]);
525     free(newtexts[curlang]);
526 michael 1194 }
527 michael 1209 }
528     fclose(f);
529     return 0;
530 michael 1194 }
531    
532     /*************************************************************************/
533    
534     /* Clear all data loaded from external language files or set with
535     * setstring(), restoring to the default data sets. Strings added with
536     * addstring() are retained but reset to empty.
537     */
538    
539 michael 1209 void
540     reset_ext_lang(void)
541 michael 1194 {
542 michael 1209 int i, j;
543 michael 1194
544 michael 1209 for (i = 0; i < NUM_LANGS; i++)
545     {
546     if (is_loaded[i])
547     {
548     uint32 datasize = *((uint32 *) origtexts[i][0]);
549     if (langtexts[i])
550     {
551     free(langtexts[i][0]);
552     free(langtexts[i]);
553     }
554     langtexts[i] = smalloc(sizeof(char *) * (num_strings + 1));
555     langtexts[i][0] = smalloc(datasize + 4);
556     memcpy(langtexts[i][0], origtexts[i][0], datasize + 4);
557     for (j = 0; j < num_strings; j++)
558     {
559     if (IS_BASE_STRING(j))
560     {
561     if (origtexts[i][j + 1])
562     {
563     langtexts[i][j + 1] = origtexts[i][j + 1]
564     - origtexts[i][0] + langtexts[i][0];
565     }
566     else
567     {
568     langtexts[i][j + 1] = NULL;
569     }
570 michael 1194 }
571 michael 1209 else if (i == DEF_LANGUAGE)
572     {
573     langtexts[i][j + 1] = langtexts[i][0] + datasize - 1;
574     }
575     else
576     {
577     langtexts[i][j + 1] = NULL;
578     }
579     }
580 michael 1194 }
581 michael 1209 }
582 michael 1194 }
583    
584     /*************************************************************************/
585     /*************************************************************************/
586    
587     /* Return the language number for the given language name. If the language
588     * is not found, returns -1.
589     */
590    
591 michael 1209 int
592     lookup_language(const char *name)
593 michael 1194 {
594 michael 1209 int i;
595 michael 1194
596 michael 1209 for (i = 0; filenames[i].num >= 0; i++)
597     {
598     if (stricmp(filenames[i].filename, name) == 0)
599     return filenames[i].num;
600     }
601     return -1;
602 michael 1194 }
603    
604     /*************************************************************************/
605    
606     /* Return true if the given language is loaded, false otherwise. */
607    
608 michael 1209 int
609     have_language(int language)
610 michael 1194 {
611 michael 1209 return language >= 0 && language < NUM_LANGS && is_loaded[language];
612 michael 1194 }
613    
614     /*************************************************************************/
615    
616     /* Return the index of the given string name. If the name is not found,
617     * returns -1. Note that string names are case sensitive.
618     */
619    
620 michael 1209 int
621     lookup_string(const char *name)
622 michael 1194 {
623 michael 1209 int i;
624 michael 1194
625 michael 1209 for (i = 0; i < num_strings; i++)
626     {
627     if (strcmp(langstrs[i], name) == 0)
628     return i;
629     }
630     return -1;
631 michael 1194 }
632    
633     /*************************************************************************/
634    
635     /* Retrieve a message text using the language selected for the given
636     * NickGroupInfo (if NickGroupInfo is NULL, use DEF_LANGUAGE).
637     */
638    
639 michael 1209 const char *
640     getstring(const NickGroupInfo * ngi, int index)
641 michael 1194 {
642 michael 1209 int language;
643     const char *text;
644 michael 1194
645 michael 1209 if (index < 0 || index >= num_strings)
646     {
647     log("getstring(): BUG: index (%d) out of range!", index);
648     return NULL;
649     }
650     language = (ngi && ngi != NICKGROUPINFO_INVALID
651     && ngi->language != LANG_DEFAULT)
652     ? ngi->language : DEF_LANGUAGE;
653     text = langtexts[language][langmap[index] + 1];
654     if (!text)
655     text = langtexts[DEF_LANGUAGE][langmap[index] + 1];
656     return text;
657 michael 1194 }
658    
659     /*************************************************************************/
660    
661     /* Retrieve a message text using the given language. */
662    
663 michael 1209 const char *
664     getstring_lang(int language, int index)
665 michael 1194 {
666 michael 1209 const char *text;
667 michael 1194
668 michael 1209 if (language < 0 || language >= NUM_LANGS)
669     {
670     log("getstring_lang(): BUG: language (%d) out of range!", language);
671     language = DEF_LANGUAGE;
672     }
673     else if (index < 0 || index >= num_strings)
674     {
675     log("getstring_lang(): BUG: index (%d) out of range!", index);
676     return NULL;
677     }
678     text = langtexts[language][langmap[index] + 1];
679     if (!text)
680     text = langtexts[DEF_LANGUAGE][langmap[index] + 1];
681     return text;
682 michael 1194 }
683    
684     /*************************************************************************/
685    
686     /* Set the contents of the given string in the given language. Returns
687     * nonzero on success, zero on error (invalid parameters or language not
688     * available). The text is copied to a separate location, so does not need
689     * to be saved by the caller.
690     */
691    
692 michael 1209 int
693     setstring(int language, int index, const char *text)
694 michael 1194 {
695 michael 1209 uint32 oldlen, newlen;
696     char *oldtext, *newtext;
697     int i;
698 michael 1194
699 michael 1209 if (language < 0 || language >= NUM_LANGS)
700     {
701     log("setstring(): language (%d) out of range!", language);
702     return 0;
703     }
704     else if (index < 0 || index >= num_strings)
705     {
706     log("setstring(): index (%d) out of range!", index);
707     return 0;
708     }
709     else if (IS_BASE_STRING(index))
710     {
711     log("setstring(): index (%d) is a base string and cannot be set", index);
712     return 0;
713     }
714     else if (!text)
715     {
716     log("setstring(): text is NULL!");
717     return 0;
718     }
719     else if (!is_loaded[language])
720     {
721     log_debug(1, "setstring(): language %d not available", language);
722     return 0;
723     }
724     oldtext = langtexts[language][0];
725     oldlen = *((uint32 *) oldtext);
726     newlen = oldlen + strlen(text) + 1;
727     newtext = malloc(newlen + 4);
728     if (!newtext)
729     {
730     log("setstring(): out of memory");
731     return 0;
732     }
733     *((uint32 *) newtext) = newlen;
734     memcpy(newtext + 4, oldtext + 4, oldlen);
735     strcpy(newtext + 4 + oldlen, text);
736     for (i = 0; i < num_strings; i++)
737     {
738     if (i == index)
739     {
740     langtexts[language][i + 1] = newtext + 4 + oldlen;
741 michael 1194 }
742 michael 1209 else
743     {
744     int ofs = langtexts[language][i + 1] - oldtext;
745     langtexts[language][i + 1] = newtext + ofs;
746 michael 1194 }
747 michael 1209 }
748     langtexts[language][0] = newtext;
749     free(oldtext);
750     return 1;
751 michael 1194 }
752    
753     /*************************************************************************/
754    
755     /* Map string number `old' to number `new' and return the number of the
756     * message that used to be stored there. Returns -1 on error (invalid
757     * parameter).
758     */
759    
760 michael 1209 int
761     mapstring(int old, int new)
762 michael 1194 {
763 michael 1209 int prev;
764 michael 1194
765 michael 1209 if (old < 0 || old >= num_strings)
766     {
767     log("mapstring(): old string index (%d) is out of range!", old);
768     return -1;
769     }
770     else if (new < 0 || new >= num_strings)
771     {
772     log("mapstring(): new string index (%d) is out of range!", new);
773     return -1;
774     }
775     prev = langmap[old];
776     langmap[old] = langmap[new];
777     return prev;
778 michael 1194 }
779    
780     /*************************************************************************/
781    
782     /* Add a new string to the global list with the given (non-empty) name.
783     * The string is initially empty in all languages. If the string has
784     * already been added, clears the string's text but does not add a new copy
785     * of the string. Returns the string index on success, -1 on failure
786     * (invalid parameters or attempting to re-add a base string).
787     */
788    
789 michael 1209 int
790     addstring(const char *name)
791 michael 1194 {
792 michael 1209 int index, i;
793     char **new_langstrs, **new_langtexts[NUM_LANGS];
794     int *new_langmap;
795 michael 1194
796 michael 1209 if (!name)
797     {
798     log("addstring(): BUG: name is NULL!");
799     return -1;
800     }
801     if (!*name)
802     {
803     log("addstring(): name is the empty string!");
804     return -1;
805     }
806     if ((index = lookup_string(name)) >= 0)
807     {
808     if (IS_BASE_STRING(index))
809     {
810     log("addstring(): attempt to re-add base string `%s'!", name);
811     return -1;
812 michael 1194 }
813 michael 1209 else
814     {
815     return index;
816 michael 1194 }
817 michael 1209 }
818     new_langstrs = malloc(sizeof(char *) * (num_strings + 1));
819     new_langmap = malloc(sizeof(int) * (num_strings + 1));
820     if (!new_langstrs || !new_langmap)
821     {
822     log("addstring(): Out of memory!");
823     free(new_langmap);
824     free(new_langstrs);
825     return -1;
826     }
827     memcpy(new_langstrs, langstrs, sizeof(char *) * num_strings);
828     memcpy(new_langmap, langmap, sizeof(int) * num_strings);
829     new_langstrs[num_strings] = strdup(name);
830     if (!new_langstrs[num_strings])
831     {
832     log("addstring(): Out of memory!");
833     free(new_langmap);
834     free(new_langstrs);
835     return -1;
836     }
837     new_langmap[num_strings] = num_strings;
838     for (i = 0; i < NUM_LANGS; i++)
839     {
840     if (!is_loaded[i])
841     continue;
842     new_langtexts[i] = malloc(sizeof(char *) * (num_strings + 2));
843     if (!new_langtexts[i])
844     {
845     log("addstring(): Out of memory!");
846     while (--i >= 0)
847     free(new_langtexts[i]);
848     free(new_langstrs[num_strings]);
849     free(new_langmap);
850     free(new_langstrs);
851     return -1;
852 michael 1194 }
853 michael 1209 memcpy(new_langtexts[i], langtexts[i],
854     sizeof(char *) * (num_strings + 1));
855     if (i == DEF_LANGUAGE)
856     {
857     /* Point at the \0 ending the last string in the text buffer */
858     new_langtexts[i][num_strings + 1] =
859     new_langtexts[i][0] + 4 + *((uint32 *) new_langtexts[i][0]) - 1;
860 michael 1194 }
861 michael 1209 else
862     {
863     new_langtexts[i][num_strings + 1] = NULL;
864 michael 1194 }
865 michael 1209 }
866     free(langstrs);
867     langstrs = new_langstrs;
868     free(langmap);
869     langmap = new_langmap;
870     for (i = 0; i < NUM_LANGS; i++)
871     {
872     if (is_loaded[i])
873     {
874     free(langtexts[i]);
875     langtexts[i] = new_langtexts[i];
876 michael 1194 }
877 michael 1209 }
878     return num_strings++;
879 michael 1194 }
880    
881     /*************************************************************************/
882     /*************************************************************************/
883    
884     /* Format a string in a strftime()-like way, but heed the nick group's
885     * language setting for month and day names and adjust the time for the
886     * nick group's time zone setting. The string stored in the buffer will
887     * always be null-terminated, even if the actual string was longer than the
888     * buffer size. Also note that the format parameter is a message number
889     * rather than a literal string, and the time parameter is a time_t, not a
890     * struct tm *.
891     * Assumption: No month or day name has a length (including trailing null)
892     * greater than BUFSIZE or contains the '%' character.
893     */
894    
895 michael 1209 int
896     strftime_lang(char *buf, int size, const NickGroupInfo * ngi,
897     int format, time_t time)
898 michael 1194 {
899 michael 1209 int ngi_is_valid = (ngi && ngi != NICKGROUPINFO_INVALID);
900     int language = (ngi_is_valid && ngi->language != LANG_DEFAULT)
901     ? ngi->language : DEF_LANGUAGE;
902     char tmpbuf[BUFSIZE], buf2[BUFSIZE];
903     char *s;
904     int i, ret;
905     struct tm *tm;
906 michael 1194
907 michael 1209 strbcpy(tmpbuf, getstring_lang(language, format));
908     if (ngi_is_valid && ngi->timezone != TIMEZONE_DEFAULT)
909     {
910     time += ngi->timezone * 60;
911     tm = gmtime(&time);
912     /* Remove "%Z" (timezone) specifiers */
913     while ((s = strstr(tmpbuf, "%Z")) != NULL)
914     {
915     char *end = s + 2;
916     while (s > tmpbuf && s[-1] == ' ')
917     s--;
918     strmove(s, end);
919 michael 1194 }
920 michael 1209 }
921     else
922     {
923     tm = localtime(&time);
924     }
925     if ((s = langtexts[language][STRFTIME_DAYS_SHORT + 1]) != NULL)
926     {
927     for (i = 0; i < tm->tm_wday; i++)
928     s += strcspn(s, "\n") + 1;
929     i = strcspn(s, "\n");
930     strncpy(buf2, s, i);
931     buf2[i] = 0;
932     strnrepl(tmpbuf, sizeof(tmpbuf), "%a", buf2);
933     }
934     if ((s = langtexts[language][STRFTIME_DAYS_LONG + 1]) != NULL)
935     {
936     for (i = 0; i < tm->tm_wday; i++)
937     s += strcspn(s, "\n") + 1;
938     i = strcspn(s, "\n");
939     strncpy(buf2, s, i);
940     buf2[i] = 0;
941     strnrepl(tmpbuf, sizeof(tmpbuf), "%A", buf2);
942     }
943     if ((s = langtexts[language][STRFTIME_MONTHS_SHORT + 1]) != NULL)
944     {
945     for (i = 0; i < tm->tm_mon; i++)
946     s += strcspn(s, "\n") + 1;
947     i = strcspn(s, "\n");
948     strncpy(buf2, s, i);
949     buf2[i] = 0;
950     strnrepl(tmpbuf, sizeof(tmpbuf), "%b", buf2);
951     }
952     if ((s = langtexts[language][STRFTIME_MONTHS_LONG + 1]) != NULL)
953     {
954     for (i = 0; i < tm->tm_mon; i++)
955     s += strcspn(s, "\n") + 1;
956     i = strcspn(s, "\n");
957     strncpy(buf2, s, i);
958     buf2[i] = 0;
959     strnrepl(tmpbuf, sizeof(tmpbuf), "%B", buf2);
960     }
961     ret = strftime(buf, size, tmpbuf, tm);
962     if (ret >= size) // buffer overflow, should be impossible
963     return 0;
964     return ret;
965 michael 1194 }
966    
967     /*************************************************************************/
968    
969     /* Generates a string describing the given length of time to one unit
970     * (e.g. "3 days" or "10 hours"), or two units (e.g. "5 hours 25 minutes")
971     * if the MT_DUALUNIT flag is specified. The minimum resolution is one
972     * minute, unless the MT_SECONDS flag is specified; the returned time is
973     * rounded up if in the minimum unit, else rounded to the nearest integer.
974     * The returned buffer is a static buffer which will be overwritten on the
975     * next call to this routine.
976     *
977     * The MT_* flags (passed in the `flags' parameter) are defined in
978     * language.h.
979     */
980    
981 michael 1209 char *
982     maketime(const NickGroupInfo * ngi, time_t time, int flags)
983 michael 1194 {
984 michael 1209 static char buf[BUFSIZE];
985     int unit;
986 michael 1194
987 michael 1209 if (time < 1) /* Enforce a minimum of one second */
988     time = 1;
989 michael 1194
990 michael 1209 if ((flags & MT_SECONDS) && time <= 59)
991     {
992 michael 1194
993 michael 1209 unit = (time == 1 ? STR_SECOND : STR_SECONDS);
994     snprintf(buf, sizeof(buf), "%ld%s", (long) time, getstring(ngi, unit));
995 michael 1194
996 michael 1209 }
997     else if (!(flags & MT_SECONDS) && time <= 59 * 60)
998     {
999 michael 1194
1000 michael 1209 time = (time + 59) / 60;
1001     unit = (time == 1 ? STR_MINUTE : STR_MINUTES);
1002     snprintf(buf, sizeof(buf), "%ld%s", (long) time, getstring(ngi, unit));
1003 michael 1194
1004    
1005 michael 1209 }
1006     else if (flags & MT_DUALUNIT)
1007     {
1008 michael 1194
1009 michael 1209 time_t time2;
1010     int unit2;
1011 michael 1194
1012 michael 1209 if (time <= 59 * 60 + 59)
1013     { /* 59 minutes, 59 seconds */
1014     time2 = time % 60;
1015     unit2 = (time2 == 1 ? STR_SECOND : STR_SECONDS);
1016     time = time / 60;
1017     unit = (time == 1 ? STR_MINUTE : STR_MINUTES);
1018     }
1019     else if (time <= (23 * 60 + 59) * 60 + 30)
1020     { /* 23 hours, 59.5 minutes */
1021     time = (time + 30) / 60;
1022     time2 = time % 60;
1023     unit2 = (time2 == 1 ? STR_MINUTE : STR_MINUTES);
1024     time = time / 60;
1025     unit = (time == 1 ? STR_HOUR : STR_HOURS);
1026     }
1027     else
1028     {
1029     time = (time + (30 * 60)) / (60 * 60);
1030     time2 = time % 24;
1031     unit2 = (time2 == 1 ? STR_HOUR : STR_HOURS);
1032     time = time / 24;
1033     unit = (time == 1 ? STR_DAY : STR_DAYS);
1034     }
1035     if (time2)
1036     snprintf(buf, sizeof(buf), "%ld%s%s%ld%s", (long) time,
1037     getstring(ngi, unit), getstring(ngi, STR_TIMESEP),
1038     (long) time2, getstring(ngi, unit2));
1039     else
1040     snprintf(buf, sizeof(buf), "%ld%s", (long) time, getstring(ngi, unit));
1041 michael 1194
1042 michael 1209 }
1043     else
1044     { /* single unit */
1045 michael 1194
1046 michael 1209 if (time <= 59 * 60 + 30)
1047     { /* 59 min 30 sec; MT_SECONDS known true */
1048     time = (time + 30) / 60;
1049     unit = (time == 1 ? STR_MINUTE : STR_MINUTES);
1050 michael 1194 }
1051 michael 1209 else if (time <= (23 * 60 + 30) * 60)
1052     { /* 23 hours, 30 minutes */
1053     time = (time + (30 * 60)) / (60 * 60);
1054     unit = (time == 1 ? STR_HOUR : STR_HOURS);
1055     }
1056     else
1057     {
1058     time = (time + (12 * 60 * 60)) / (24 * 60 * 60);
1059     unit = (time == 1 ? STR_DAY : STR_DAYS);
1060     }
1061     snprintf(buf, sizeof(buf), "%ld%s", (long) time, getstring(ngi, unit));
1062 michael 1194
1063 michael 1209 }
1064    
1065     return buf;
1066 michael 1194 }
1067    
1068     /*************************************************************************/
1069    
1070     /* Generates a description for the given expiration time in the form of
1071     * days, hours, minutes, seconds and/or a combination thereof. May also
1072     * return "does not expire" or "already expired" messages if the expiration
1073     * time given is zero or earlier than the current time, respectively.
1074     * String is truncated if it would exceed `size' bytes (including trailing
1075     * null byte).
1076     */
1077    
1078 michael 1209 void
1079     expires_in_lang(char *buf, int size, const NickGroupInfo * ngi,
1080     time_t expires)
1081 michael 1194 {
1082 michael 1209 time_t seconds = expires - time(NULL);
1083 michael 1194
1084 michael 1209 if (expires == 0)
1085     {
1086     strscpy(buf, getstring(ngi, EXPIRES_NONE), size);
1087     }
1088     else if (seconds <= 0 && noexpire)
1089     {
1090     strscpy(buf, getstring(ngi, EXPIRES_NOW), size);
1091     }
1092     else
1093     {
1094     if (seconds <= 0)
1095     {
1096     /* Already expired--it will be cleared out by the next get() */
1097     seconds = 1;
1098 michael 1194 }
1099 michael 1209 snprintf(buf, size, getstring(ngi, EXPIRES_IN),
1100     maketime(ngi, seconds, MT_DUALUNIT));
1101     }
1102 michael 1194 }
1103    
1104     /*************************************************************************/
1105     /*************************************************************************/
1106    
1107     /* Send a syntax-error message to the user. */
1108    
1109 michael 1209 void
1110     syntax_error(const char *service, const User * u, const char *command,
1111     int msgnum)
1112 michael 1194 {
1113 michael 1209 char buf[BUFSIZE];
1114     snprintf(buf, sizeof(buf), getstring(u->ngi, msgnum), command);
1115     notice_lang(service, u, SYNTAX_ERROR, buf);
1116     notice_lang(service, u, MORE_INFO, service, command);
1117 michael 1194 }
1118    
1119     /*************************************************************************/
1120    
1121     /*
1122     * Local variables:
1123     * c-file-style: "stroustrup"
1124     * c-file-offsets: ((case-label . *) (statement-case-intro . *))
1125     * indent-tabs-mode: nil
1126     * End:
1127     *
1128     * vim: expandtab shiftwidth=4:
1129     */