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 |
*/ |