/* Multi-language support.
*
* IRC Services is copyright (c) 1996-2007 Andrew Church.
* E-mail: <achurch@achurch.org>
* 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);
}
/*************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1