ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/vendor/ircservices-5.1.24/language.c
Revision: 3389
Committed: Fri Apr 25 14:12:15 2014 UTC (9 years, 10 months ago) by michael
Content type: text/x-csrc
File size: 32067 byte(s)
Log Message:
- Imported ircservices-5.1.24

File Contents

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