/* Multi-language support. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include "language.h" /* Needed for NickGroupInfo structure definition (used by getstring() and * strftime_lang()) */ #include "modules/nickserv/nickserv.h" /*************************************************************************/ /* Indexes of available languages (exported), terminated by -1: */ int langlist[NUM_LANGS+1]; /* The list of lists of messages. */ static char **langtexts[NUM_LANGS]; /* Order in which languages should be displayed: (alphabetical in English) */ static int langorder[] = { LANG_EN_US, /* English (US) */ LANG_NL, /* Dutch */ LANG_FR, /* French */ LANG_DE, /* German */ LANG_HU, /* Hungarian */ LANG_IT, /* Italian */ /* LANG_JA_JIS,*/ /* Japanese (JIS encoding) */ LANG_JA_EUC, /* Japanese (EUC encoding) */ LANG_JA_SJIS, /* Japanese (SJIS encoding) */ LANG_PT, /* Portugese */ LANG_RU, /* Russian */ LANG_ES, /* Spanish */ LANG_TR, /* Turkish */ }; /* Filenames for language files: */ static struct { int num; const char *filename; } filenames[] = { { LANG_EN_US, "en_us" }, { LANG_NL, "nl" }, { LANG_FR, "fr" }, { LANG_DE, "de" }, { LANG_HU, "hu" }, { LANG_IT, "it" }, /* { LANG_JA_JIS, "ja_jis" },*/ { LANG_JA_EUC, "ja_euc" }, { LANG_JA_SJIS, "ja_sjis" }, { LANG_PT, "pt" }, { LANG_RU, "ru" }, { LANG_ES, "es" }, { LANG_TR, "tr" }, { -1, NULL } }; /* Mapping of language strings (to allow on-the-fly replacement of strings) */ static int *langmap; /* Array indicating which languages were actually loaded (needed since NULL * langtexts[] pointers are redirected to DEF_LANGUAGE) */ static int is_loaded[NUM_LANGS]; /*************************************************************************/ /* Load a language file. */ static int read_int32(int32 *ptr, FILE *f) { int a = fgetc(f); int b = fgetc(f); int c = fgetc(f); int d = fgetc(f); if (a == EOF || b == EOF || c == EOF || d == EOF) return -1; *ptr = a<<24 | b<<16 | c<<8 | d; return 0; } static void load_lang(int index, const char *filename) { char buf[256]; FILE *f; int32 num, size, i; char *data = NULL; if (debug) { log("debug: Loading language %d from file `languages/%s'", index, filename); } snprintf(buf, sizeof(buf), "languages/%s", filename); if (!(f = fopen(buf, "r"))) { log_perror("Failed to load language %d (%s)", index, filename); return; } else if (read_int32(&num, f) < 0) { log("Failed to read number of strings for language %d (%s)", index, filename); return; } else if (read_int32(&size, f) < 0) { log("Failed to read data size for language %d (%s)", index, filename); return; } else if (num != NUM_STRINGS) { log("Warning: Bad number of strings (%d, wanted %d) " "for language %d (%s)", num, NUM_STRINGS, index, filename); } langtexts[index] = scalloc(sizeof(char *), NUM_STRINGS+1); if (num > NUM_STRINGS) num = NUM_STRINGS; langtexts[index][NUM_STRINGS] = data = smalloc(size); if (fread(data, size, 1, f) != 1) { log("Failed to read language data for language %d (%s)", index, filename); goto fail; } for (i = 0; i < num; i++) { int32 pos; if (read_int32(&pos, f) < 0) { log("Failed to read entry %d in language %d (%s) TOC", i, index, filename); goto fail; } if (pos == -1) { langtexts[index][i] = NULL; } else { langtexts[index][i] = data + pos; } } fclose(f); is_loaded[index] = 1; return; fail: free(data); free(langtexts[index]); langtexts[index] = NULL; return; } /*************************************************************************/ /* Initialize list of lists. */ int lang_init() { int i, j, n = 0; for (i = 0; i < lenof(langorder); i++) { for (j = 0; filenames[j].num >= 0; j++) { if (filenames[j].num == langorder[i]) break; } if (filenames[j].num >= 0) load_lang(langorder[i], filenames[j].filename); else log("BUG: lang_init(): no filename entry for language %d!", langorder[i]); } langmap = scalloc(sizeof(int), NUM_STRINGS); for (i = 0; i < NUM_STRINGS; i++) langmap[i] = i; if (!langtexts[DEF_LANGUAGE]) { log("Unable to load default language"); return 0; } for (i = 0; i < lenof(langorder); i++) { if (langtexts[langorder[i]] != NULL) { for (j = 0; j < NUM_STRINGS; j++) { if (!langtexts[langorder[i]][j]) langtexts[langorder[i]][j] = langtexts[DEF_LANGUAGE][j]; if (!langtexts[langorder[i]][j]) langtexts[langorder[i]][j] = langtexts[LANG_EN_US][j]; } langlist[n++] = langorder[i]; } } while (n < lenof(langlist)) langlist[n++] = -1; for (i = 0; i < NUM_LANGS; i++) { if (!langtexts[i]) langtexts[i] = langtexts[DEF_LANGUAGE]; } return 1; } /*************************************************************************/ /* Clean up language data. */ void lang_cleanup(void) { int i; /* First clear out any languages borrowing from the default language */ for (i = 0; i < NUM_LANGS; i++) { if (i != DEF_LANGUAGE && langtexts[i] == langtexts[DEF_LANGUAGE]) langtexts[i] = NULL; } /* Now free everything left */ for (i = 0; i < NUM_LANGS; i++) { if (langtexts[i]) { free(langtexts[i][NUM_STRINGS]); free(langtexts[i]); langtexts[i] = NULL; } } free(langmap); } /*************************************************************************/ /*************************************************************************/ /* Return true if the given language is loaded, false otherwise. */ int have_language(int language) { return language >= 0 && language < NUM_LANGS && is_loaded[language]; } /*************************************************************************/ /* Retrieve a message text using the language selected for the given * NickGroupInfo (if NickGroupInfo is NULL, use DEF_LANGUAGE). */ const char *getstring(const NickGroupInfo *ngi, int index) { int language; if (index < 0 || index >= NUM_STRINGS) { log("getstring(): BUG: index (%d) out of range!", index); return NULL; } language = (ngi && ngi != NICKGROUPINFO_INVALID && ngi->language != LANG_DEFAULT) ? ngi->language : DEF_LANGUAGE; return langtexts[language][langmap[index]]; } /*************************************************************************/ /* Retrieve a message text using the given language. */ const char *getstring_lang(int language, int index) { if (language < 0 || language >= NUM_LANGS) { log("getstring_lang(): BUG: language (%d) out of range!", language); language = DEF_LANGUAGE; } else if (index < 0 || index >= NUM_STRINGS) { log("getstring(): BUG: index (%d) out of range!", index); return NULL; } return langtexts[language][langmap[index]]; } /*************************************************************************/ /* Set string number `old' to number `new' and return the number of the * message that used to be stored there. */ int setstring(int old, int new) { int prev = langmap[old]; langmap[old] = langmap[new]; return prev; } /*************************************************************************/ /* Format a string in a strftime()-like way, but heed the nick group's * language setting for month and day names and adjust the time for the * nick group's time zone setting. The string stored in the buffer will * always be null-terminated, even if the actual string was longer than the * buffer size. Also note that the format parameter is a message number * rather than a literal string, and the time parameter is a time_t, not a * struct tm *. * Assumption: No month or day name has a length (including trailing null) * greater than BUFSIZE or contains the '%' character. */ int strftime_lang(char *buf, int size, const NickGroupInfo *ngi, int format, time_t time) { int ngi_is_valid = (ngi && ngi != NICKGROUPINFO_INVALID); int language = (ngi_is_valid && ngi->language != LANG_DEFAULT) ? ngi->language : DEF_LANGUAGE; char tmpbuf[BUFSIZE], buf2[BUFSIZE]; char *s; int i, ret; struct tm *tm; strscpy(tmpbuf, langtexts[language][format], sizeof(tmpbuf)); if (ngi_is_valid && ngi->timezone != TIMEZONE_DEFAULT) { time += ngi->timezone*60; tm = gmtime(&time); /* Remove "%Z" (timezone) specifiers */ while ((s = strstr(tmpbuf, "%Z")) != NULL) { char *end = s+2; while (s > tmpbuf && s[-1] == ' ') s--; strmove(s, end); } } else { tm = localtime(&time); } if ((s = langtexts[language][STRFTIME_DAYS_SHORT]) != NULL) { for (i = 0; i < tm->tm_wday; i++) s += strcspn(s, "\n")+1; i = strcspn(s, "\n"); strncpy(buf2, s, i); buf2[i] = 0; strnrepl(tmpbuf, sizeof(tmpbuf), "%a", buf2); } if ((s = langtexts[language][STRFTIME_DAYS_LONG]) != NULL) { for (i = 0; i < tm->tm_wday; i++) s += strcspn(s, "\n")+1; i = strcspn(s, "\n"); strncpy(buf2, s, i); buf2[i] = 0; strnrepl(tmpbuf, sizeof(tmpbuf), "%A", buf2); } if ((s = langtexts[language][STRFTIME_MONTHS_SHORT]) != NULL) { for (i = 0; i < tm->tm_mon; i++) s += strcspn(s, "\n")+1; i = strcspn(s, "\n"); strncpy(buf2, s, i); buf2[i] = 0; strnrepl(tmpbuf, sizeof(tmpbuf), "%b", buf2); } if ((s = langtexts[language][STRFTIME_MONTHS_LONG]) != NULL) { for (i = 0; i < tm->tm_mon; i++) s += strcspn(s, "\n")+1; i = strcspn(s, "\n"); strncpy(buf2, s, i); buf2[i] = 0; strnrepl(tmpbuf, sizeof(tmpbuf), "%B", buf2); } ret = strftime(buf, size, tmpbuf, tm); if (ret == size) buf[size-1] = 0; return ret; } /*************************************************************************/ /* Generates a string describing the given length of time to one unit * (e.g. "3 days" or "10 hours"), or two units (e.g. "5 hours 25 minutes") * if the MT_DUALUNIT flag is specified. The minimum resolution is one * minute, unless the MT_SECONDS flag is specified; the returned time is * rounded up if in the minimum unit, else rounded to the nearest integer. * The returned buffer is a static buffer which will be overwritten on the * next call to this routine. * * The MT_* flags (passed in the `flags' parameter) are defined in * language.h. */ char *maketime(const NickGroupInfo *ngi, time_t time, int flags) { static char buf[BUFSIZE]; int unit; if (time < 1) /* Enforce a minimum of one second */ time = 1; if ((flags & MT_SECONDS) && time <= 59) { unit = (time==1 ? STR_SECOND : STR_SECONDS); snprintf(buf, sizeof(buf), "%ld%s", (long)time, getstring(ngi,unit)); } else if (!(flags & MT_SECONDS) && time <= 59*60) { time = (time+59) / 60; unit = (time==1 ? STR_MINUTE : STR_MINUTES); snprintf(buf, sizeof(buf), "%ld%s", (long)time, getstring(ngi,unit)); } else if (flags & MT_DUALUNIT) { time_t time2; int unit2; if (time <= 59*60+59) { /* 59 minutes, 59 seconds */ time2 = time % 60; unit2 = (time2==1 ? STR_SECOND : STR_SECONDS); time = time / 60; unit = (time==1 ? STR_MINUTE : STR_MINUTES); } else if (time <= (23*60+59)*60+30) { /* 23 hours, 59.5 minutes */ time = (time+30) / 60; time2 = time % 60; unit2 = (time2==1 ? STR_MINUTE : STR_MINUTES); time = time / 60; unit = (time==1 ? STR_HOUR : STR_HOURS); } else { time = (time+(30*60)) / (60*60); time2 = time % 24; unit2 = (time2==1 ? STR_HOUR : STR_HOURS); time = time / 24; unit = (time==1 ? STR_DAY : STR_DAYS); } if (time2) snprintf(buf, sizeof(buf), "%ld%s%s%ld%s", (long)time, getstring(ngi,unit), getstring(ngi,STR_TIMESEP), (long)time2, getstring(ngi,unit2)); else snprintf(buf, sizeof(buf), "%ld%s", (long)time, getstring(ngi,unit)); } else { /* single unit */ if (time <= 59*60+30) { /* 59 min 30 sec; MT_SECONDS known true */ time = (time+30) / 60; unit = (time==1 ? STR_MINUTE : STR_MINUTES); } else if (time <= (23*60+30)*60) { /* 23 hours, 30 minutes */ time = (time+(30*60)) / (60*60); unit = (time==1 ? STR_HOUR : STR_HOURS); } else { time = (time+(12*60*60)) / (24*60*60); unit = (time==1 ? STR_DAY : STR_DAYS); } snprintf(buf, sizeof(buf), "%ld%s", (long)time, getstring(ngi,unit)); } return buf; } /*************************************************************************/ /* Generates a description for the given expiration time in the form of * days, hours, minutes, seconds and/or a combination thereof. May also * return "does not expire" or "expires soon" messages if the expiration * time given is zero or earlier than the current time, respectively. * String is truncated if it would exceed `size' bytes (including trailing * null byte). */ void expires_in_lang(char *buf, int size, const NickGroupInfo *ngi, time_t expires) { time_t seconds = expires - time(NULL); if (expires == 0) { strscpy(buf, getstring(ngi,EXPIRES_NONE), size); } else if (seconds <= 0) { strscpy(buf, getstring(ngi,EXPIRES_SOON), size); } else { snprintf(buf, size, getstring(ngi,EXPIRES_IN), maketime(ngi,seconds,MT_DUALUNIT)); } } /*************************************************************************/ /*************************************************************************/ /* Send a syntax-error message to the user. */ void syntax_error(const char *service, const User *u, const char *command, int msgnum) { char buf[BUFSIZE]; snprintf(buf, sizeof(buf), getstring(u->ngi, msgnum), command); notice_lang(service, u, SYNTAX_ERROR, buf); notice_lang(service, u, MORE_INFO, service, command); } /*************************************************************************/