ircservices5/language.c
2019-01-23 09:42:02 +01:00

961 lines
31 KiB
C

/* Multi-language support.
*
* IRC Services is copyright (c) 1996-2009 Andrew Church.
* E-mail: <achurch@achurch.org>
* Parts written by Andrew Kempe and others.
* This program is free but copyrighted software; see the file GPL.txt for
* details.
*/
#define LANGSTR_ARRAY /* define langstrs[] in langstrs.h, via language.h */
#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];
/* List and count of available strings: */
static char **langstrs;
int num_strings;
/* The list of lists of messages. */
static char **langtexts[NUM_LANGS];
/* Pointers to the original data, in case external files are loaded later. */
static char **origtexts[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_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_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];
/* Index of the first extra (non-base) string */
#define FIRST_EXTRA_STRING (NUM_BASE_STRINGS)
/* Is the given string index a base string index? */
#define IS_BASE_STRING(n) ((n) < FIRST_EXTRA_STRING)
/*************************************************************************/
/*************************************************************************/
/* Helper functions. */
static inline 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 inline int read_uint32(uint32 *ptr, FILE *f)
{
return read_int32((int32 *)ptr, f);
}
/*************************************************************************/
/*************************************************************************/
/* Load a language file, storing the data in origtexts[index]. */
static void load_lang(int index, const char *filename)
{
char buf[256];
FILE *f;
uint32 num, size, i;
char *data = NULL;
log_debug(1, "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_uint32(&num, f) < 0) {
log("Failed to read number of strings for language %d (%s)",
index, filename);
return;
} else if (read_uint32(&size, f) < 0) {
log("Failed to read data size for language %d (%s)",
index, filename);
return;
} else if (num != NUM_BASE_STRINGS) {
log("Warning: Bad number of strings (%d, wanted %d) "
"for language %d (%s)", num, NUM_BASE_STRINGS, index, filename);
}
origtexts[index] = scalloc(sizeof(char *), NUM_BASE_STRINGS+1);
if (num > NUM_BASE_STRINGS)
num = NUM_BASE_STRINGS;
origtexts[index][0] = data = smalloc(size+4);
*((uint32 *)data) = size;
data += 4;
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) {
origtexts[index][i+1] = NULL;
} else {
origtexts[index][i+1] = data + pos;
}
}
fclose(f);
is_loaded[index] = 1;
return;
fail:
free(data);
free(origtexts[index]);
origtexts[index] = NULL;
return;
}
/*************************************************************************/
/* Initialize list of lists. */
int lang_init(void)
{
int i, j, n = 0;
/* Set up the string list */
langstrs = malloc(sizeof(char *) * NUM_BASE_STRINGS);
if (!langstrs) {
log_perror("malloc(langstrs)");
return 0;
}
memcpy(langstrs, base_langstrs, sizeof(char *) * NUM_BASE_STRINGS);
num_strings = NUM_BASE_STRINGS;
/* Set up the string remap list */
langmap = smalloc(sizeof(int) * num_strings);
for (i = 0; i < num_strings; i++)
langmap[i] = i;
/* Load language files */
memset(is_loaded, 0, sizeof(is_loaded));
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]);
}
}
/* Make sure the default language has all strings available */
if (!origtexts[DEF_LANGUAGE]) {
log("Unable to load default language");
return 0;
}
for (i = 0; i < num_strings; i++) {
if (!origtexts[DEF_LANGUAGE][i+1]) {
if (is_loaded[LANG_EN_US] && origtexts[LANG_EN_US][i+1]) {
uint32 oldsize = *((uint32 *)origtexts[DEF_LANGUAGE][0]);
uint32 newsize = strlen(origtexts[LANG_EN_US][i+1]) + 1;
origtexts[DEF_LANGUAGE][0] =
realloc(origtexts[DEF_LANGUAGE][0], oldsize + newsize + 4);
if (!origtexts[DEF_LANGUAGE][0]) {
log("Out of memory while loading languages");
return 0;
}
*((uint32 *)origtexts[DEF_LANGUAGE][0]) = oldsize + newsize;
origtexts[DEF_LANGUAGE][i+1] =
origtexts[DEF_LANGUAGE][0] + 4 + oldsize;
strcpy(origtexts[DEF_LANGUAGE][i+1],
origtexts[LANG_EN_US][i+1]);
} else {
log("String %s missing from default language", langstrs[i]);
return 0;
}
}
}
/* Set up the list of available languages in langlist[] */
for (i = 0; i < lenof(langorder); i++) {
if (is_loaded[langorder[i]])
langlist[n++] = langorder[i];
}
while (n < lenof(langlist))
langlist[n++] = -1;
/* Initialize the active string tables in langtexts[] */
reset_ext_lang();
return 1;
}
/*************************************************************************/
/* Clean up language data. */
void lang_cleanup(void)
{
int i;
for (i = 0; i < NUM_LANGS; i++) {
if (langtexts[i]) {
free(langtexts[i][0]);
free(langtexts[i]);
langtexts[i] = NULL;
}
if (origtexts[i]) {
free(origtexts[i][0]);
free(origtexts[i]);
langtexts[i] = NULL;
}
}
free(langmap);
free(langstrs);
}
/*************************************************************************/
/* Load an external language file. External language files are formatted
* the same way as the standard language files, except that a language name
* follows the string name; for example:
*
* UNKNOWN_COMMAND<lwsp+>en_us<lwsp*><nl>
* <tab>The command %s is not recognized.<nl>
*
* where <lwsp> is "linear white space" (space or tab); <nl> is either LF
* or CRLF; and <tab> is the tab character. Empty lines and lines
* beginning with '#' are ignored. Note that any lines longer than
* BUFSIZE-1 characters (including the trailing <nl>) will be truncated.
*
* Returns 0 on failure, nonzero on success.
*/
int load_ext_lang(const char *filename)
{
FILE *f;
char buf[BUFSIZE], *s;
char **newtexts[NUM_LANGS];
uint32 newsizes[NUM_LANGS];
int i, curstr, curlang, line;
int retval = 1, firstline = 1;
memset(newtexts, 0, sizeof(newtexts));
memset(newsizes, 0, sizeof(newsizes));
f = fopen(filename, "r");
if (!f) {
log_perror("load_ext_lang(): Unable to open file %s", filename);
return 0;
}
curstr = curlang = -1;
line = 0;
while (fgets(buf, sizeof(buf), f) && *buf) {
line++;
s = buf + strlen(buf) - 1;
if (*s == '\r') { /* in case we get half a CRLF */
*s = fgetc(f);
if (*s != '\n') {
ungetc(*s, f);
*s = '\r';
}
}
if (*s == '\n') {
*s = 0;
if (s > buf && s[-1] == '\r')
s[-1] = 0;
} else {
char buf2[BUFSIZE];
log("load_ext_lang(): %s:%d: Line too long (maximum %d"
" characters)", filename, line, sizeof(buf)-2);
retval = 0;
while (fgets(buf2, sizeof(buf2), f)
&& *buf2 && buf2[strlen(buf2)-1] != '\n')
;
}
if (*buf == '#')
continue;
if (*buf != '\t') {
curstr = curlang = -1;
s = strtok(buf, " \t\r\n");
if (!s)
continue; /* empty line */
if ((curstr = lookup_string(s)) < 0) {
log("load_ext_lang: %s:%d: Unknown string ID `%s'",
filename, line, s);
retval = 0;
} else {
s = strtok(NULL, " \t\r\n");
if (!s) {
log("load_ext_lang: %s:%d: Missing language ID\n",
filename, line);
retval = 0;
curstr = -1;
} else if ((curlang = lookup_language(s)) < 0) {
log("load_ext_lang: %s:%d: Unknown language ID `%s'",
filename, line, s);
retval = 0;
curstr = -1;
} else if (!is_loaded[curlang]) {
log("load_ext_lang: %s:%d: Language `%s' is not"
" available", filename, line, s);
retval = 0;
curstr = -1;
}
}
if (curstr >= 0 && curlang >= 0) {
/* Valid string/language ID--set up storage space if not
* yet done */
if (!newtexts[curlang]) {
newtexts[curlang] = calloc(num_strings+1, sizeof(char *));
if (!newtexts[curlang]) {
log_perror("load_ext_lang: %s:%d", filename, line);
goto fail;
}
newsizes[curlang] = 0;
}
/* Point to location of string in data buffer; we add 1
* to the offset to differentiate the value from 0 (not
* present) */
newtexts[curlang][curstr+1] = (char *)newsizes[curlang] + 1;
/* Set first-line flag (signals whether to insert a
* newline) */
firstline = 1;
}
} else { /* line begins with tab -> text line */
if (curstr >= 0 && curlang >= 0) {
int oldsize = newsizes[curlang];
if (!firstline)
oldsize--; // overwrite the trailing \0 with a newline
newsizes[curlang] += strlen(buf+1)+1; // skip initial tab
newtexts[curlang][0] =
realloc(newtexts[curlang][0], newsizes[curlang]);
sprintf(newtexts[curlang][0]+oldsize, "%s%s",
firstline ? "" : "\n", buf+1);
firstline = 0;
}
}
} /* for each line */
fclose(f);
if (retval) {
/* Success, install new strings. First set up new string arrays in
* newtexts[], then, when all succeeds (i.e. no ENOMEM), move the
* new data to langtexts[]. */
for (curlang = 0; curlang < NUM_LANGS; curlang++) {
char *newbuf;
uint32 oldlen, newlen;
if (!newtexts[curlang])
continue;
oldlen = *((uint32 *)langtexts[curlang][0]);
newlen = newsizes[curlang];
newbuf = malloc(4 + oldlen + newlen);
if (!newbuf) {
log_perror("load_ext_lang: %s:%d", filename, line);
goto fail;
}
*((uint32 *)newbuf) = oldlen + newlen;
memcpy(newbuf+4, langtexts[curlang][0]+4, oldlen);
memcpy(newbuf+4+oldlen, newtexts[curlang][0], newlen);
free(newtexts[curlang][0]);
newtexts[curlang][0] = newbuf;
for (i = 0; i < num_strings; i++) {
if (newtexts[curlang][i+1]) {
int ofs = (int)newtexts[curlang][i+1] - 1;
newtexts[curlang][i+1] = newbuf+4 + oldlen + ofs;
} else if (langtexts[curlang][i+1]) {
int ofs = langtexts[curlang][i+1]
- langtexts[curlang][0];
newtexts[curlang][i+1] = newbuf + ofs;
} else {
newtexts[curlang][i+1] = NULL;
}
}
} /* for each language */
/* All okay, actually install the data */
for (curlang = 0; curlang < NUM_LANGS; curlang++) {
if (!newtexts[curlang])
continue;
free(langtexts[curlang][0]);
free(langtexts[curlang]);
langtexts[curlang] = newtexts[curlang];
}
} /* if (retval) */
return retval;
fail:
for (curlang = 0; curlang < NUM_LANGS; curlang++) {
if (newtexts[curlang]) {
free(newtexts[curlang][0]);
free(newtexts[curlang]);
}
}
fclose(f);
return 0;
}
/*************************************************************************/
/* Clear all data loaded from external language files or set with
* setstring(), restoring to the default data sets. Strings added with
* addstring() are retained but reset to empty.
*/
void reset_ext_lang(void)
{
int i, j;
for (i = 0; i < NUM_LANGS; i++) {
if (is_loaded[i]) {
uint32 datasize = *((uint32 *)origtexts[i][0]);
if (langtexts[i]) {
free(langtexts[i][0]);
free(langtexts[i]);
}
langtexts[i] = smalloc(sizeof(char *) * (num_strings+1));
langtexts[i][0] = smalloc(datasize+4);
memcpy(langtexts[i][0], origtexts[i][0], datasize+4);
for (j = 0; j < num_strings; j++) {
if (IS_BASE_STRING(j)) {
if (origtexts[i][j+1]) {
langtexts[i][j+1] = origtexts[i][j+1]
- origtexts[i][0]
+ langtexts[i][0];
} else {
langtexts[i][j+1] = NULL;
}
} else if (i == DEF_LANGUAGE) {
langtexts[i][j+1] = langtexts[i][0] + datasize - 1;
} else {
langtexts[i][j+1] = NULL;
}
}
}
}
}
/*************************************************************************/
/*************************************************************************/
/* Return the language number for the given language name. If the language
* is not found, returns -1.
*/
int lookup_language(const char *name)
{
int i;
for (i = 0; filenames[i].num >= 0; i++) {
if (stricmp(filenames[i].filename, name) == 0)
return filenames[i].num;
}
return -1;
}
/*************************************************************************/
/* Return true if the given language is loaded, false otherwise. */
int have_language(int language)
{
return language >= 0 && language < NUM_LANGS && is_loaded[language];
}
/*************************************************************************/
/* Return the index of the given string name. If the name is not found,
* returns -1. Note that string names are case sensitive.
*/
int lookup_string(const char *name)
{
int i;
for (i = 0; i < num_strings; i++) {
if (strcmp(langstrs[i], name) == 0)
return i;
}
return -1;
}
/*************************************************************************/
/* 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;
const char *text;
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;
text = langtexts[language][langmap[index]+1];
if (!text)
text = langtexts[DEF_LANGUAGE][langmap[index]+1];
return text;
}
/*************************************************************************/
/* Retrieve a message text using the given language. */
const char *getstring_lang(int language, int index)
{
const char *text;
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_lang(): BUG: index (%d) out of range!", index);
return NULL;
}
text = langtexts[language][langmap[index]+1];
if (!text)
text = langtexts[DEF_LANGUAGE][langmap[index]+1];
return text;
}
/*************************************************************************/
/* Set the contents of the given string in the given language. Returns
* nonzero on success, zero on error (invalid parameters or language not
* available). The text is copied to a separate location, so does not need
* to be saved by the caller.
*/
int setstring(int language, int index, const char *text)
{
uint32 oldlen, newlen;
char *oldtext, *newtext;
int i;
if (language < 0 || language >= NUM_LANGS) {
log("setstring(): language (%d) out of range!", language);
return 0;
} else if (index < 0 || index >= num_strings) {
log("setstring(): index (%d) out of range!", index);
return 0;
} else if (IS_BASE_STRING(index)) {
log("setstring(): index (%d) is a base string and cannot be set",
index);
return 0;
} else if (!text) {
log("setstring(): text is NULL!");
return 0;
} else if (!is_loaded[language]) {
log_debug(1, "setstring(): language %d not available", language);
return 0;
}
oldtext = langtexts[language][0];
oldlen = *((uint32 *)oldtext);
newlen = oldlen + strlen(text) + 1;
newtext = malloc(newlen + 4);
if (!newtext) {
log("setstring(): out of memory");
return 0;
}
*((uint32 *)newtext) = newlen;
memcpy(newtext+4, oldtext+4, oldlen);
strcpy(newtext+4+oldlen, text);
for (i = 0; i < num_strings; i++) {
if (i == index) {
langtexts[language][i+1] = newtext+4 + oldlen;
} else {
int ofs = langtexts[language][i+1] - oldtext;
langtexts[language][i+1] = newtext + ofs;
}
}
langtexts[language][0] = newtext;
free(oldtext);
return 1;
}
/*************************************************************************/
/* Map string number `old' to number `new' and return the number of the
* message that used to be stored there. Returns -1 on error (invalid
* parameter).
*/
int mapstring(int old, int new)
{
int prev;
if (old < 0 || old >= num_strings) {
log("mapstring(): old string index (%d) is out of range!", old);
return -1;
} else if (new < 0 || new >= num_strings) {
log("mapstring(): new string index (%d) is out of range!", new);
return -1;
}
prev = langmap[old];
langmap[old] = langmap[new];
return prev;
}
/*************************************************************************/
/* Add a new string to the global list with the given (non-empty) name.
* The string is initially empty in all languages. If the string has
* already been added, clears the string's text but does not add a new copy
* of the string. Returns the string index on success, -1 on failure
* (invalid parameters or attempting to re-add a base string).
*/
int addstring(const char *name)
{
int index, i;
char **new_langstrs, **new_langtexts[NUM_LANGS];
int *new_langmap;
if (!name) {
log("addstring(): BUG: name is NULL!");
return -1;
}
if (!*name) {
log("addstring(): name is the empty string!");
return -1;
}
if ((index = lookup_string(name)) >= 0) {
if (IS_BASE_STRING(index)) {
log("addstring(): attempt to re-add base string `%s'!", name);
return -1;
} else {
return index;
}
}
new_langstrs = malloc(sizeof(char *) * (num_strings+1));
new_langmap = malloc(sizeof(int) * (num_strings+1));
if (!new_langstrs || !new_langmap) {
log("addstring(): Out of memory!");
free(new_langmap);
free(new_langstrs);
return -1;
}
memcpy(new_langstrs, langstrs, sizeof(char *) * num_strings);
memcpy(new_langmap, langmap, sizeof(int) * num_strings);
new_langstrs[num_strings] = strdup(name);
if (!new_langstrs[num_strings]) {
log("addstring(): Out of memory!");
free(new_langmap);
free(new_langstrs);
return -1;
}
new_langmap[num_strings] = num_strings;
for (i = 0; i < NUM_LANGS; i++) {
if (!is_loaded[i])
continue;
new_langtexts[i] = malloc(sizeof(char *) * (num_strings+2));
if (!new_langtexts[i]) {
log("addstring(): Out of memory!");
while (--i >= 0)
free(new_langtexts[i]);
free(new_langstrs[num_strings]);
free(new_langmap);
free(new_langstrs);
return -1;
}
memcpy(new_langtexts[i], langtexts[i],
sizeof(char *) * (num_strings+1));
if (i == DEF_LANGUAGE) {
/* Point at the \0 ending the last string in the text buffer */
new_langtexts[i][num_strings+1] =
new_langtexts[i][0]+4 + *((uint32 *)new_langtexts[i][0]) - 1;
} else {
new_langtexts[i][num_strings+1] = NULL;
}
}
free(langstrs);
langstrs = new_langstrs;
free(langmap);
langmap = new_langmap;
for (i = 0; i < NUM_LANGS; i++) {
if (is_loaded[i]) {
free(langtexts[i]);
langtexts[i] = new_langtexts[i];
}
}
return num_strings++;
}
/*************************************************************************/
/*************************************************************************/
/* 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;
strbcpy(tmpbuf, getstring_lang(language,format));
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+1]) != 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+1]) != 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+1]) != 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+1]) != 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) // buffer overflow, should be impossible
return 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 "already expired" 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 && noexpire) {
strscpy(buf, getstring(ngi,EXPIRES_NOW), size);
} else {
if (seconds <= 0) {
/* Already expired--it will be cleared out by the next get() */
seconds = 1;
}
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);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/